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"
" 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"));
"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
@@ -1941,26 +1942,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];
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);
}
}
/* 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;
}
@@ -1979,17 +2083,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
@@ -1998,10 +2103,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;
@@ -2033,6 +2140,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)
@@ -2059,6 +2176,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;
@@ -2126,6 +2244,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);
}
@@ -2141,7 +2268,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;
@@ -7712,7 +7839,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);

View File

@@ -502,7 +502,8 @@ _nmcli_compl_ARGS()
user| \
username| \
service| \
password)
password| \
passwd-file)
if [[ "${#words[@]}" -eq 2 ]]; then
return 0
fi
@@ -845,9 +846,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

View File

@@ -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);

View File

@@ -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 */

View File

@@ -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