cli: add 'passwd-file' option for 'nmcli connection up' to provide passwords

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
This commit is contained in:
Jiří Klimeš
2014-10-14 15:53:34 +02:00
parent de7f85bdec
commit 3c9b8671fa
5 changed files with 182 additions and 31 deletions

View File

@@ -251,9 +251,9 @@ usage (void)
"COMMAND := { show | up | down | add | modify | edit | delete | reload | load }\n\n" "COMMAND := { show | up | down | add | modify | edit | delete | reload | load }\n\n"
" show [--active] [[--show-secrets] [id | uuid | path | apath] <ID>] ...\n\n" " show [--active] [[--show-secrets] [id | uuid | path | apath] <ID>] ...\n\n"
#if WITH_WIMAX #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 #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 #endif
" down [id | uuid | path | apath] <ID>\n\n" " down [id | uuid | path | apath] <ID>\n\n"
" add COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS IP_OPTIONS\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" g_printerr (_("Usage: nmcli connection up { ARGUMENTS | help }\n"
"\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" "\n"
"Activate a connection on a device. The profile to activate is identified by its\n" "Activate a connection on a device. The profile to activate is identified by its\n"
"name, UUID or D-Bus path.\n" "name, UUID or D-Bus path.\n"
"\n" "\n"
"ARGUMENTS := ifname <ifname> [ap <BSSID>] [nsp <name>]\n" "ARGUMENTS := ifname <ifname> [ap <BSSID>] [nsp <name>] [passwd-file <file with passwords>]\n"
"\n" "\n"
"Activate a device with a connection. The connection profile is selected\n" "Activate a device with a connection. The connection profile is selected\n"
"automatically by NetworkManager.\n" "automatically by NetworkManager.\n"
"\n" "\n"
"ifname - specifies the device to active the connection on\n" "ifname - specifies the device to active the connection on\n"
"ap - specifies AP to connect to (only valid for Wi-Fi)\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")); "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 static void
@@ -1941,26 +1942,129 @@ activate_connection_cb (GObject *client, GAsyncResult *result, gpointer user_dat
g_free (info); 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 static gboolean
get_secrets_from_user (const char *request_id, get_secrets_from_user (const char *request_id,
const char *title, const char *title,
const char *msg, const char *msg,
gboolean ask,
GHashTable *pwds_hash,
GPtrArray *secrets) GPtrArray *secrets)
{ {
int i; int i;
char *pwd;
g_print ("%s\n", msg);
for (i = 0; i < secrets->len; i++) { for (i = 0; i < secrets->len; i++) {
NMSecretAgentSimpleSecret *secret = secrets->pdata[i]; NMSecretAgentSimpleSecret *secret = secrets->pdata[i];
if (secret->value) { char *pwd = NULL;
/* Prefill the password if we have it. */
rl_startup_hook = set_deftext; /* First try to find the password in provided passwords file,
pre_input_deftext = g_strdup (secret->value); * 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); g_free (secret->value);
secret->value = pwd ? pwd : g_strdup (""); secret->value = pwd;
} }
return TRUE; return TRUE;
} }
@@ -1979,17 +2083,18 @@ secrets_requested (NMSecretAgentSimple *agent,
if (nmc->print_output == NMC_PRINT_PRETTY) if (nmc->print_output == NMC_PRINT_PRETTY)
nmc_terminal_erase_line (); nmc_terminal_erase_line ();
if (nmc->ask) { success = get_secrets_from_user (request_id, title, msg, nmc->in_editor || nmc->ask,
success = get_secrets_from_user (request_id, title, msg, secrets); nmc->pwds_hash, secrets);
} else {
g_print ("%s\n", msg);
g_print ("%s\n", _("Warning: nmcli does not ask for password without '--ask' argument."));
}
if (success) if (success)
nm_secret_agent_simple_response (agent, request_id, secrets); nm_secret_agent_simple_response (agent, request_id, secrets);
else else {
nm_secret_agent_simple_response (agent, request_id, NULL); /* 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 static gboolean
@@ -1998,10 +2103,12 @@ nmc_activate_connection (NmCli *nmc,
const char *ifname, const char *ifname,
const char *ap, const char *ap,
const char *nsp, const char *nsp,
const char *pwds,
GAsyncReadyCallback callback, GAsyncReadyCallback callback,
GError **error) GError **error)
{ {
ActivateConnectionInfo *info; ActivateConnectionInfo *info;
GHashTable *pwds_hash;
NMDevice *device = NULL; NMDevice *device = NULL;
const char *spec_object = NULL; const char *spec_object = NULL;
gboolean device_found; gboolean device_found;
@@ -2033,6 +2140,16 @@ nmc_activate_connection (NmCli *nmc,
return FALSE; 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 */ /* Create secret agent */
nmc->secret_agent = nm_secret_agent_simple_new ("nmcli-connect"); nmc->secret_agent = nm_secret_agent_simple_new ("nmcli-connect");
if (nmc->secret_agent) if (nmc->secret_agent)
@@ -2059,6 +2176,7 @@ do_connection_up (NmCli *nmc, int argc, char **argv)
const char *ifname = NULL; const char *ifname = NULL;
const char *ap = NULL; const char *ap = NULL;
const char *nsp = NULL; const char *nsp = NULL;
const char *pwds = NULL;
GError *error = NULL; GError *error = NULL;
const char *selector = NULL; const char *selector = NULL;
const char *name = NULL; const char *name = NULL;
@@ -2126,6 +2244,15 @@ do_connection_up (NmCli *nmc, int argc, char **argv)
nsp = *argv; nsp = *argv;
} }
#endif #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 { else {
g_printerr (_("Unknown parameter: %s\n"), *argv); g_printerr (_("Unknown parameter: %s\n"), *argv);
} }
@@ -2141,7 +2268,7 @@ do_connection_up (NmCli *nmc, int argc, char **argv)
nmc->nowait_flag = (nmc->timeout == 0); nmc->nowait_flag = (nmc->timeout == 0);
nmc->should_wait = TRUE; 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."), g_string_printf (nmc->return_text, _("Error: %s."),
error ? error->message : _("unknown error")); error ? error->message : _("unknown error"));
nmc->return_value = error ? error->code : NMC_RESULT_ERROR_CON_ACTIVATION; nmc->return_value = error ? error->code : NMC_RESULT_ERROR_CON_ACTIVATION;
@@ -7712,7 +7839,7 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t
nmc->nowait_flag = FALSE; nmc->nowait_flag = FALSE;
nmc->should_wait = TRUE; nmc->should_wait = TRUE;
nmc->print_output = NMC_PRINT_PRETTY; 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)) { activate_connection_editor_cb, &tmp_err)) {
g_print (_("Error: Cannot activate connection: %s.\n"), tmp_err->message); g_print (_("Error: Cannot activate connection: %s.\n"), tmp_err->message);
g_clear_error (&tmp_err); g_clear_error (&tmp_err);

View File

@@ -502,7 +502,8 @@ _nmcli_compl_ARGS()
user| \ user| \
username| \ username| \
service| \ service| \
password) password| \
passwd-file)
if [[ "${#words[@]}" -eq 2 ]]; then if [[ "${#words[@]}" -eq 2 ]]; then
return 0 return 0
fi fi
@@ -845,9 +846,9 @@ _nmcli()
_nmcli_compl_ARGS_CONNECTION && return 0 _nmcli_compl_ARGS_CONNECTION && return 0
if [[ "$COMMAND_CONNECTION_TYPE" = "ifname" ]]; then if [[ "$COMMAND_CONNECTION_TYPE" = "ifname" ]]; then
OPTIONS=(ap nsp) OPTIONS=(ap nsp passwd-file)
else else
OPTIONS=(ifname ap nsp) OPTIONS=(ifname ap nsp passwd-file)
fi fi
_nmcli_compl_ARGS _nmcli_compl_ARGS
fi fi

View File

@@ -499,7 +499,9 @@ nmc_init (NmCli *nmc)
nmc->timeout = -1; nmc->timeout = -1;
nmc->connections = NULL; nmc->connections = NULL;
nmc->secret_agent = NULL; nmc->secret_agent = NULL;
nmc->pwds_hash = NULL;
nmc->should_wait = FALSE; nmc->should_wait = FALSE;
nmc->nowait_flag = TRUE; nmc->nowait_flag = TRUE;
@@ -531,6 +533,8 @@ nmc_cleanup (NmCli *nmc)
nm_secret_agent_unregister (nmc->secret_agent, NULL, NULL); nm_secret_agent_unregister (nmc->secret_agent, NULL, NULL);
g_object_unref (nmc->secret_agent); g_object_unref (nmc->secret_agent);
} }
if (nmc->pwds_hash)
g_hash_table_destroy (nmc->pwds_hash);
g_free (nmc->required_fields); g_free (nmc->required_fields);
nmc_empty_output_fields (nmc); nmc_empty_output_fields (nmc);

View File

@@ -111,7 +111,9 @@ typedef struct _NmCli {
int timeout; /* Operation timeout */ int timeout; /* Operation timeout */
const GPtrArray *connections; /* List of connections */ const GPtrArray *connections; /* List of connections */
NMSecretAgent *secret_agent; /* Secret agent */ 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 should_wait; /* Indication that nmcli should not end yet */
gboolean nowait_flag; /* '--nowait' option; used for passing to callbacks */ gboolean nowait_flag; /* '--nowait' option; used for passing to callbacks */

View File

@@ -327,10 +327,10 @@ When no command is given to the \fIconnection\fP object, the default action
is 'nmcli connection show'. is 'nmcli connection show'.
.RE .RE
.TP .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 .RE
.RS .RS
.B up ifname <ifname> [ap <BSSID>] [nsp <name>] .B up ifname <ifname> [ap <BSSID>] [nsp <name>] [passwd <file with passwords>]
.RS .RS
.br .br
Activate a connection. The connection is identified by its name, UUID or D-Bus 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) \(en BSSID of the AP which the command should connect to (for Wi\(hyFi connections)
.IP \fInsp\fP 13 .IP \fInsp\fP 13
\(en NSP (Network Service Provider) which the command should connect to (for WiMAX connections) \(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
.RE .RE
.TP .TP