diff options
author | Jiří Klimeš <jklimes@redhat.com> | 2014-10-14 15:53:34 +0200 |
---|---|---|
committer | Jiří Klimeš <jklimes@redhat.com> | 2014-11-03 14:35:26 +0100 |
commit | 7363356e1fe0d52e5cd9d394f9bb056557d53b96 (patch) | |
tree | ec5b9bd00df41284db893a5382b82512e938fd7c | |
parent | 39aecaaaac9ff9ddab77e11dacaf5fe91bb5badf (diff) | |
download | NetworkManager-jk/nmcli-secret-agent.tar.gz |
cli: add 'passwd-file' option for 'nmcli connection up' to provide passwordsjk/nmcli-secret-agent
It is useful for running nmcli without --ask option, i.e non-interactively.
Example contents of the file:
wifi.psk: s e c r e t 12345
802-1x.password:kili manjaro
802-1x.pin:987654321
-rw-r--r-- | clients/cli/connections.c | 179 | ||||
-rw-r--r-- | clients/cli/nmcli-completion | 7 | ||||
-rw-r--r-- | clients/cli/nmcli.c | 4 | ||||
-rw-r--r-- | clients/cli/nmcli.h | 2 | ||||
-rw-r--r-- | man/nmcli.1.in | 21 |
5 files changed, 182 insertions, 31 deletions
diff --git a/clients/cli/connections.c b/clients/cli/connections.c index 58aaa24161..7cddfabba1 100644 --- a/clients/cli/connections.c +++ b/clients/cli/connections.c @@ -251,9 +251,9 @@ usage (void) "COMMAND := { show | up | down | add | modify | edit | delete | reload | load }\n\n" " show [--active] [[--show-secrets] [id | uuid | path | apath] <ID>] ...\n\n" #if WITH_WIMAX - " up [[id | uuid | path] <ID>] [ifname <ifname>] [ap <BSSID>] [nsp <name>]\n\n" + " up [[id | uuid | path] <ID>] [ifname <ifname>] [ap <BSSID>] [nsp <name>] [passwd-file <file with passwords>]\n\n" #else - " up [[id | uuid | path] <ID>] [ifname <ifname>] [ap <BSSID>]\n\n" + " up [[id | uuid | path] <ID>] [ifname <ifname>] [ap <BSSID>] [passwd-file <file with passwords>]\n\n" #endif " down [id | uuid | path | apath] <ID>\n\n" " add COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS IP_OPTIONS\n\n" @@ -291,19 +291,20 @@ usage_connection_up (void) { g_printerr (_("Usage: nmcli connection up { ARGUMENTS | help }\n" "\n" - "ARGUMENTS := [id | uuid | path] <ID> [ifname <ifname>] [ap <BSSID>] [nsp <name>]\n" + "ARGUMENTS := [id | uuid | path] <ID> [ifname <ifname>] [ap <BSSID>] [nsp <name>] [passwd-file <file with passwords>]\n" "\n" "Activate a connection on a device. The profile to activate is identified by its\n" "name, UUID or D-Bus path.\n" "\n" - "ARGUMENTS := ifname <ifname> [ap <BSSID>] [nsp <name>]\n" + "ARGUMENTS := ifname <ifname> [ap <BSSID>] [nsp <name>] [passwd-file <file with passwords>]\n" "\n" "Activate a device with a connection. The connection profile is selected\n" "automatically by NetworkManager.\n" "\n" - "ifname - specifies the device to active the connection on\n" - "ap - specifies AP to connect to (only valid for Wi-Fi)\n" - "nsp - specifies NSP to connect to (only valid for WiMAX)\n\n")); + "ifname - specifies the device to active the connection on\n" + "ap - specifies AP to connect to (only valid for Wi-Fi)\n" + "nsp - specifies NSP to connect to (only valid for WiMAX)\n" + "passwd-file - file with password(s) required to activate the connection\n\n")); } static void @@ -1942,26 +1943,129 @@ activate_connection_cb (GObject *client, GAsyncResult *result, gpointer user_dat g_free (info); } +/** + * parse_passwords: + * @passwd_file: file with passwords to parse + * @error: location to store error, or %NULL + * + * Parse passwords given in @passwd_file and insert them into a hash table. + * Example of @passwd_file contents: + * wifi.psk:tajne heslo + * 802-1x.password:krakonos + * 802-11-wireless-security:leap-password:my leap password + * + * Returns: hash table with parsed passwords, or %NULL on an error + */ +static GHashTable * +parse_passwords (const char *passwd_file, GError **error) +{ + GHashTable *pwds_hash; + char *contents = NULL; + gsize len = 0; + GError *local_err = NULL; + char **lines, **iter; + char *pwd_spec, *pwd, *prop; + const char *setting; + + pwds_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + if (!passwd_file) + return pwds_hash; + + /* Read the passwords file */ + if (!g_file_get_contents (passwd_file, &contents, &len, &local_err)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("failed to read passwd-file '%s': %s"), + passwd_file, local_err->message); + g_error_free (local_err); + g_hash_table_destroy (pwds_hash); + return NULL; + } + + lines = nmc_strsplit_set (contents, "\r\n", -1); + for (iter = lines; *iter; iter++) { + pwd = strchr (*iter, ':'); + if (!pwd) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("missing colon in 'password' entry '%s'"), *iter); + goto failure; + } + *(pwd++) = '\0'; + + prop = strchr (*iter, '.'); + if (!prop) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("missing dot in 'password' entry '%s'"), *iter); + goto failure; + } + *(prop++) = '\0'; + + setting = *iter; + while (g_ascii_isspace (*setting)) + setting++; + /* Accept wifi-sec or wifi instead of cumbersome '802-11-wireless-security' */ + if (!strcmp (setting, "wifi-sec") || !strcmp (setting, "wifi")) + setting = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME; + if (nm_setting_lookup_type (setting) == G_TYPE_INVALID) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("invalid setting name in 'password' entry '%s'"), setting); + goto failure; + } + + pwd_spec = g_strdup_printf ("%s.%s", setting, prop); + g_hash_table_insert (pwds_hash, pwd_spec, g_strdup (pwd)); + } + g_strfreev (lines); + g_free (contents); + return pwds_hash; + +failure: + g_strfreev (lines); + g_free (contents); + g_hash_table_destroy (pwds_hash); + return NULL; +} + static gboolean get_secrets_from_user (const char *request_id, const char *title, const char *msg, + gboolean ask, + GHashTable *pwds_hash, GPtrArray *secrets) { int i; - char *pwd; - g_print ("%s\n", msg); for (i = 0; i < secrets->len; i++) { NmSecretAgentSimpleSecret *secret = secrets->pdata[i]; - if (secret->value) { - /* Prefill the password if we have it. */ - rl_startup_hook = set_deftext; - pre_input_deftext = g_strdup (secret->value); + char *pwd = NULL; + + /* First try to find the password in provided passwords file, + * then ask user. */ + if (pwds_hash && (pwd = g_hash_table_lookup (pwds_hash, secret->prop_name))) { + pwd = g_strdup (pwd); + } else { + g_print ("%s\n", msg); + if (ask) { + if (secret->value) { + /* Prefill the password if we have it. */ + rl_startup_hook = set_deftext; + pre_input_deftext = g_strdup (secret->value); + } + pwd = nmc_readline ("%s (%s): ", secret->name, secret->prop_name); + if (!pwd) + pwd = g_strdup (""); + } else { + g_printerr (_("Warning: password for '%s' not given in 'passwd-file' " + "and nmcli cannot ask without '--ask' option.\n"), + secret->prop_name); + } } - pwd = nmc_readline ("%s (%s): ", secret->name, secret->prop_name); + /* No password provided, cancel the secrets. */ + if (!pwd) + return FALSE; g_free (secret->value); - secret->value = pwd ? pwd : g_strdup (""); + secret->value = pwd; } return TRUE; } @@ -1980,17 +2084,18 @@ secrets_requested (NmSecretAgentSimple *agent, if (nmc->print_output == NMC_PRINT_PRETTY) nmc_terminal_erase_line (); - if (nmc->ask) { - success = get_secrets_from_user (request_id, title, msg, secrets); - } else { - g_print ("%s\n", msg); - g_print ("%s\n", _("Warning: nmcli does not ask for password without '--ask' argument.")); - } - + success = get_secrets_from_user (request_id, title, msg, nmc->in_editor || nmc->ask, + nmc->pwds_hash, secrets); if (success) nm_secret_agent_simple_response (agent, request_id, secrets); - else - nm_secret_agent_simple_response (agent, request_id, NULL); + else { + /* Unregister our secret agent on failure, so that another agent + * may be tried */ + if (nmc->secret_agent) { + nm_secret_agent_unregister (nmc->secret_agent, NULL, NULL); + g_clear_object (&nmc->secret_agent); + } + } } static gboolean @@ -1999,10 +2104,12 @@ nmc_activate_connection (NmCli *nmc, const char *ifname, const char *ap, const char *nsp, + const char *pwds, GAsyncReadyCallback callback, GError **error) { ActivateConnectionInfo *info; + GHashTable *pwds_hash; NMDevice *device = NULL; const char *spec_object = NULL; gboolean device_found; @@ -2034,6 +2141,16 @@ nmc_activate_connection (NmCli *nmc, return FALSE; } + /* Parse passwords given in passwords file */ + pwds_hash = parse_passwords (pwds, &local); + if (local) { + g_propagate_error (error, local); + return FALSE; + } + if (nmc->pwds_hash) + g_hash_table_destroy (nmc->pwds_hash); + nmc->pwds_hash = pwds_hash; + /* Create secret agent */ nmc->secret_agent = nm_secret_agent_simple_new ("nmcli-connect"); if (nmc->secret_agent) @@ -2060,6 +2177,7 @@ do_connection_up (NmCli *nmc, int argc, char **argv) const char *ifname = NULL; const char *ap = NULL; const char *nsp = NULL; + const char *pwds = NULL; GError *error = NULL; const char *selector = NULL; const char *name = NULL; @@ -2127,6 +2245,15 @@ do_connection_up (NmCli *nmc, int argc, char **argv) nsp = *argv; } #endif + else if (strcmp (*argv, "passwd-file") == 0) { + if (next_arg (&argc, &argv) != 0) { + g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1)); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + pwds = *argv; + } else { g_printerr (_("Unknown parameter: %s\n"), *argv); } @@ -2142,7 +2269,7 @@ do_connection_up (NmCli *nmc, int argc, char **argv) nmc->nowait_flag = (nmc->timeout == 0); nmc->should_wait = TRUE; - if (!nmc_activate_connection (nmc, connection, ifname, ap, nsp, activate_connection_cb, &error)) { + if (!nmc_activate_connection (nmc, connection, ifname, ap, nsp, pwds, activate_connection_cb, &error)) { g_string_printf (nmc->return_text, _("Error: %s."), error ? error->message : _("unknown error")); nmc->return_value = error ? error->code : NMC_RESULT_ERROR_CON_ACTIVATION; @@ -7713,7 +7840,7 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t nmc->nowait_flag = FALSE; nmc->should_wait = TRUE; nmc->print_output = NMC_PRINT_PRETTY; - if (!nmc_activate_connection (nmc, NM_CONNECTION (rem_con), ifname, ap_nsp, ap_nsp, + if (!nmc_activate_connection (nmc, NM_CONNECTION (rem_con), ifname, ap_nsp, ap_nsp, NULL, activate_connection_editor_cb, &tmp_err)) { g_print (_("Error: Cannot activate connection: %s.\n"), tmp_err->message); g_clear_error (&tmp_err); diff --git a/clients/cli/nmcli-completion b/clients/cli/nmcli-completion index 2080828339..6931ba86b9 100644 --- a/clients/cli/nmcli-completion +++ b/clients/cli/nmcli-completion @@ -502,7 +502,8 @@ _nmcli_compl_ARGS() user| \ username| \ service| \ - password) + password| \ + passwd-file) if [[ "${#words[@]}" -eq 2 ]]; then return 0 fi @@ -839,9 +840,9 @@ _nmcli() _nmcli_compl_ARGS_CONNECTION && return 0 if [[ "$COMMAND_CONNECTION_TYPE" = "ifname" ]]; then - OPTIONS=(ap nsp) + OPTIONS=(ap nsp passwd-file) else - OPTIONS=(ifname ap nsp) + OPTIONS=(ifname ap nsp passwd-file) fi _nmcli_compl_ARGS fi diff --git a/clients/cli/nmcli.c b/clients/cli/nmcli.c index 74029438f1..8410020fee 100644 --- a/clients/cli/nmcli.c +++ b/clients/cli/nmcli.c @@ -499,7 +499,9 @@ nmc_init (NmCli *nmc) nmc->timeout = -1; nmc->connections = NULL; + nmc->secret_agent = NULL; + nmc->pwds_hash = NULL; nmc->should_wait = FALSE; nmc->nowait_flag = TRUE; @@ -531,6 +533,8 @@ nmc_cleanup (NmCli *nmc) nm_secret_agent_unregister (nmc->secret_agent, NULL, NULL); g_object_unref (nmc->secret_agent); } + if (nmc->pwds_hash) + g_hash_table_destroy (nmc->pwds_hash); g_free (nmc->required_fields); nmc_empty_output_fields (nmc); diff --git a/clients/cli/nmcli.h b/clients/cli/nmcli.h index 6fe39392ff..a18de18546 100644 --- a/clients/cli/nmcli.h +++ b/clients/cli/nmcli.h @@ -111,7 +111,9 @@ typedef struct _NmCli { int timeout; /* Operation timeout */ const GPtrArray *connections; /* List of connections */ + NMSecretAgent *secret_agent; /* Secret agent */ + GHashTable *pwds_hash; /* Hash table with passwords in passwd-file */ gboolean should_wait; /* Indication that nmcli should not end yet */ gboolean nowait_flag; /* '--nowait' option; used for passing to callbacks */ diff --git a/man/nmcli.1.in b/man/nmcli.1.in index 109317f768..e2ac9d5aa7 100644 --- a/man/nmcli.1.in +++ b/man/nmcli.1.in @@ -327,10 +327,10 @@ When no command is given to the \fIconnection\fP object, the default action is 'nmcli connection show'. .RE .TP -.B up [ id | uuid | path ] <ID> [ifname <ifname>] [ap <BSSID>] [nsp <name>] +.B up [ id | uuid | path ] <ID> [ifname <ifname>] [ap <BSSID>] [nsp <name>] [passwd <file with passwords>] .RE .RS -.B up ifname <ifname> [ap <BSSID>] [nsp <name>] +.B up ifname <ifname> [ap <BSSID>] [nsp <name>] [passwd <file with passwords>] .RS .br Activate a connection. The connection is identified by its name, UUID or D-Bus @@ -355,6 +355,23 @@ Available options are: \(en BSSID of the AP which the command should connect to (for Wi\(hyFi connections) .IP \fInsp\fP 13 \(en NSP (Network Service Provider) which the command should connect to (for WiMAX connections) +.IP \fIpasswd-file\fP 13 +\(en some networks may require credentials during activation. You can give these +credentials using this option. +Each line of the file should contain one password in the form of +.br +\fBsetting_name.property_name:the password\fP +.br +For example, for WPA Wi-Fi with PSK, the line would be +.br +\fI802-11-wireless-security.psk:secret12345\fP +.br +For 802.1X password, the line would be +.br +\fI802-1x.password:my 1X password\fP +.br +nmcli also accepts "wifi-sec" and "wifi" strings instead of "802-11-wireless-security". +When a required password is not given, nmcli will ask for it when run with --ask. .RE .RE .TP |