summary refs log tree commit diff
path: root/clients/cli/connections.c
diff options
context:
space:
mode:
Diffstat (limited to 'clients/cli/connections.c')
-rw-r--r--clients/cli/connections.c466
1 files changed, 308 insertions, 158 deletions
diff --git a/clients/cli/connections.c b/clients/cli/connections.c
index 9206d9da..8d138bdf 100644
--- a/clients/cli/connections.c
+++ b/clients/cli/connections.c
@@ -49,7 +49,8 @@
 #define PROMPT_BOND_MASTER _("Bond master: ")
 #define PROMPT_TEAM_MASTER _("Team master: ")
 #define PROMPT_BRIDGE_MASTER _("Bridge master: ")
-#define PROMPT_CONNECTION _("Connection (name, UUID, or path): ")
+#define PROMPT_CONNECTION  _("Connection (name, UUID, or path): ")
+#define PROMPT_CONNECTIONS _("Connection(s) (name, UUID, or path): ")
 
 static const char *nmc_known_vpns[] =
 	{ "openvpn", "vpnc", "pptp", "openconnect", "openswan", "libreswan",
@@ -237,6 +238,7 @@ typedef struct {
 	char *con_type;
 	NMConnection *connection;
 	NMSetting *setting;
+	const char *property;
 } TabCompletionInfo;
 static TabCompletionInfo nmc_tab_completion = {NULL, NULL, NULL, NULL};
 
@@ -1567,6 +1569,7 @@ find_device_for_connection (NmCli *nmc,
 	int i, j;
 
 	g_return_val_if_fail (nmc != NULL, FALSE);
+	g_return_val_if_fail (iface || ap || nsp, FALSE);
 	g_return_val_if_fail (device != NULL && *device == NULL, FALSE);
 	g_return_val_if_fail (spec_object != NULL && *spec_object == NULL, FALSE);
 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
@@ -1726,7 +1729,7 @@ device_state_cb (NMDevice *device, GParamSpec *pspec, gpointer user_data)
 		         nm_object_get_path (NM_OBJECT (active)));
 		quit ();
 	} else if (   ac_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATING
-	           && state == NM_DEVICE_STATE_IP_CONFIG) {
+	           && state >= NM_DEVICE_STATE_IP_CONFIG) {
 		if (nmc->print_output == NMC_PRINT_PRETTY)
 			nmc_terminal_erase_line ();
 		g_print (_("Connection successfully activated (master waiting for slaves) (D-Bus active path: %s)\n"),
@@ -2052,7 +2055,7 @@ nmc_activate_connection (NmCli *nmc,
 	g_return_val_if_fail (nmc != NULL, FALSE);
 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 
-	if (connection) {
+	if (connection && (ifname || ap || nsp)) {
 		device_found = find_device_for_connection (nmc, connection, ifname, ap, nsp, &device, &spec_object, &local);
 
 		/* Virtual connection may not have their interfaces created yet */
@@ -2070,7 +2073,7 @@ nmc_activate_connection (NmCli *nmc,
 			             _("unknown device '%s'."), ifname);
 			return FALSE;
 		}
-	} else {
+	} else if (!connection) {
 		g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_NOT_FOUND,
 		                     _("neither a valid connection nor device given"));
 		return FALSE;
@@ -2239,25 +2242,26 @@ typedef struct {
 	NmCli *nmc;
 	GSList *queue;
 	guint timeout_id;
-} DeactivateConnectionInfo;
+} ConnectionCbInfo;
 
-static void deactivate_connection_info_finish (DeactivateConnectionInfo *info,
-                                               NMActiveConnection *active);
+static void connection_cb_info_finish (ConnectionCbInfo *info,
+                                       gpointer connection);
 
-static gboolean
-down_timeout_cb (gpointer user_data)
+static void
+connection_removed_cb (NMClient *client, NMConnection *connection, ConnectionCbInfo *info)
 {
-	DeactivateConnectionInfo *info = user_data;
-
-	timeout_cb (info->nmc);
-	deactivate_connection_info_finish (info, NULL);
-	return G_SOURCE_REMOVE;
+	if (!g_slist_find (info->queue, connection))
+		return;
+	g_print (_("Connection '%s' (%s) successfully deleted.\n"),
+	         nm_connection_get_id (connection),
+	         nm_connection_get_uuid (connection));
+	connection_cb_info_finish (info, connection);
 }
 
 static void
 down_active_connection_state_cb (NMActiveConnection *active,
                                  GParamSpec *pspec,
-                                 DeactivateConnectionInfo *info)
+                                 ConnectionCbInfo *info)
 {
 	if (nm_active_connection_get_state (active) < NM_ACTIVE_CONNECTION_STATE_DEACTIVATED)
 		return;
@@ -2267,7 +2271,20 @@ down_active_connection_state_cb (NMActiveConnection *active,
 	g_print (_("Connection '%s' successfully deactivated (D-Bus active path: %s)\n"),
 	         nm_active_connection_get_id (active), nm_object_get_path (NM_OBJECT (active)));
 
-	deactivate_connection_info_finish (info, active);
+	g_signal_handlers_disconnect_by_func (G_OBJECT (active),
+	                                      down_active_connection_state_cb,
+	                                      info);
+	connection_cb_info_finish (info, active);
+}
+
+static gboolean
+connection_op_timeout_cb (gpointer user_data)
+{
+	ConnectionCbInfo *info = user_data;
+
+	timeout_cb (info->nmc);
+	connection_cb_info_finish (info, NULL);
+	return G_SOURCE_REMOVE;
 }
 
 static void
@@ -2279,15 +2296,11 @@ destroy_queue_element (gpointer data)
 }
 
 static void
-deactivate_connection_info_finish (DeactivateConnectionInfo *info,
-                                   NMActiveConnection *active)
-{
-	if (active) {
-		info->queue = g_slist_remove (info->queue, active);
-		g_signal_handlers_disconnect_by_func (active,
-		                                      down_active_connection_state_cb,
-		                                      info);
-		g_object_unref (active);
+connection_cb_info_finish (ConnectionCbInfo *info, gpointer connection)
+{
+	if (connection) {
+		info->queue = g_slist_remove (info->queue, connection);
+		g_object_unref (G_OBJECT (connection));
 	} else {
 		g_slist_free_full (info->queue, destroy_queue_element);
 		info->queue = NULL;
@@ -2298,7 +2311,8 @@ deactivate_connection_info_finish (DeactivateConnectionInfo *info,
 
 	if (info->timeout_id)
 		g_source_remove (info->timeout_id);
-	g_slice_free (DeactivateConnectionInfo, info);
+	g_signal_handlers_disconnect_by_func (info->nmc->client, connection_removed_cb, info);
+	g_slice_free (ConnectionCbInfo, info);
 	quit ();
 }
 
@@ -2306,7 +2320,7 @@ static NMCResultCode
 do_connection_down (NmCli *nmc, int argc, char **argv)
 {
 	NMActiveConnection *active;
-	DeactivateConnectionInfo *info = NULL;
+	ConnectionCbInfo *info = NULL;
 	const GPtrArray *active_cons;
 	GSList *queue = NULL, *iter;
 	char *line = NULL;
@@ -2315,10 +2329,13 @@ do_connection_down (NmCli *nmc, int argc, char **argv)
 	int arg_num = argc;
 	int idx = 0;
 
+	if (nmc->timeout == -1)
+		nmc->timeout = 10;
+
 	if (argc == 0) {
 		if (nmc->ask) {
-			line = nmc_readline (PROMPT_CONNECTION);
-			nmc_string_to_arg_array (line, "", &arg_arr, &arg_num);
+			line = nmc_readline (PROMPT_CONNECTIONS);
+			nmc_string_to_arg_array (line, NULL, TRUE, &arg_arr, &arg_num);
 			g_free (line);
 			arg_ptr = arg_arr;
 		}
@@ -2329,9 +2346,6 @@ do_connection_down (NmCli *nmc, int argc, char **argv)
 		}
 	}
 
-	if (nmc->timeout == -1)
-		nmc->timeout = 10;
-
 	/* Get active connections */
 	active_cons = nm_client_get_active_connections (nmc->client);
 	while (arg_num > 0) {
@@ -2351,9 +2365,13 @@ do_connection_down (NmCli *nmc, int argc, char **argv)
 		}
 
 		active = find_active_connection (active_cons, nmc->connections, selector, *arg_ptr, &idx);
-		if (active)
-			queue = g_slist_prepend (queue, active);
-		else {
+		if (active) {
+			/* Check if the connection is unique. */
+			/* Calling down for the same connection repeatedly would result in
+			 * NM responding for the last D-Bus call only and we would stall. */
+			if (!g_slist_find (queue, active))
+				queue = g_slist_prepend (queue, g_object_ref (active));
+		} else {
 			g_printerr (_("Error: '%s' is not an active connection.\n"), *arg_ptr);
 			g_string_printf (nmc->return_text, _("Error: not all active connections found."));
 			nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND;
@@ -2368,27 +2386,25 @@ do_connection_down (NmCli *nmc, int argc, char **argv)
 		nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND;
 		goto error;
 	}
-
 	queue = g_slist_reverse (queue);
 
 	if (nmc->timeout > 0) {
 		nmc->should_wait = TRUE;
 
-		info = g_slice_new0 (DeactivateConnectionInfo);
+		info = g_slice_new0 (ConnectionCbInfo);
 		info->nmc = nmc;
-		info->timeout_id = g_timeout_add_seconds (nmc->timeout, down_timeout_cb, info);
+		info->queue = queue;
+		info->timeout_id = g_timeout_add_seconds (nmc->timeout, connection_op_timeout_cb, info);
 	}
 
 	for (iter = queue; iter; iter = g_slist_next (iter)) {
 		active = iter->data;
 
-		if (info) {
-			info->queue = g_slist_prepend (info->queue, g_object_ref (active));
+		if (info)
 			g_signal_connect (active,
 			                  "notify::" NM_ACTIVE_CONNECTION_STATE,
 			                  G_CALLBACK (down_active_connection_state_cb),
 			                  info);
-		}
 
 		/* Now deactivate the connection */
 		nm_client_deactivate_connection (nmc->client, active, NULL, NULL);
@@ -2396,7 +2412,6 @@ do_connection_down (NmCli *nmc, int argc, char **argv)
 
 error:
 	g_strfreev (arg_arr);
-	g_slist_free (queue);
 	return nmc->return_value;
 }
 
@@ -2862,7 +2877,7 @@ check_valid_enumeration (char **str,
 	} else {
 		char *options;
 
-		options = nmc_util_strv_for_display (strings);
+		options = nmc_util_strv_for_display (strings, TRUE);
 		g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT,
 		             _("Error: '%s': '%s' is not a valid %s %s."),
 		             what, *str, what_desc, options);
@@ -5732,7 +5747,6 @@ uuid_display_hook (char **array, int len, int max_len)
 	int i, max = 0;
 	char *tmp;
 	const char *id;
-
 	for (i = 1; i <= len; i++) {
 		con = nmc_find_connection (nmc_tab_completion.nmc->connections, "uuid", array[i], NULL);
 		id = con ? nm_connection_get_id (con) : NULL;
@@ -6087,48 +6101,105 @@ should_complete_cmd (const char *line, int end, const char *cmd,
 	return ret;
 }
 
-static char *
-extract_property_name (const char *prompt, const char *line)
+/*
+ * extract_setting_and_property:
+ * prompt: (in) (allow-none): prompt string, or NULL
+ * line: (in) (allow-none): line, or NULL
+ * setting: (out) (transfer full) (array zero-terminated=1):
+ *   return location for setting name
+ * property: (out) (transfer full) (array zero-terminated=1):
+ *   return location for property name
+ *
+ * Extract setting and property names from prompt and/or line.
+ */
+static void
+extract_setting_and_property (const char *prompt, const char *line,
+                              char **setting, char **property)
 {
 	char *prop = NULL;
+	char *sett = NULL;
 
-	/* If prompt is set take the property name from it, else extract it from line */
-	if (!prompt) {
-		const char *p1;
-		size_t num;
-		p1 = strchr (line, '.');
-		if (p1) {
-			p1++;
-		} else {
-			size_t n1, n2, n3;
-			n1 = strspn  (line,    " \t");
-			n2 = strcspn (line+n1, " \t\0") + n1;
-			n3 = strspn  (line+n2, " \t")   + n2;
-			p1 = line + n3;
-		}
-		num = strcspn (p1, " \t\0");
-		prop = g_strndup (p1, num);
-	} else {
-		const char *p1, *dot;
-		size_t num;
+	if (prompt) {
+		/* prompt looks like this:
+		  "nmcli 802-1x>" or "nmcli 802-1x.pac-file>" */
+		const char *p1, *p2, *dot;
+		size_t num1, num2;
 		p1 = strchr (prompt, ' ');
-		/* prompt looks like this: "nmcli 802-1x>" or "nmcli 802-1x.pac-file>" */
 		if (p1) {
-			dot = strchr (p1 + 1, '.');
-			p1 = dot ? dot + 1 : p1;
-			num = strcspn  (p1, ">");
-			prop = g_strndup (p1, num);
+			dot = strchr (++p1, '.');
+			if (dot) {
+				p2 = dot + 1;
+				num1 = strcspn (p1, ".");
+				num2 = strcspn (p2, ">");
+				sett = num1 > 0 ? g_strndup (p1, num1) : NULL;
+				prop = num2 > 0 ? g_strndup (p2, num2) : NULL;
+			} else {
+				num1 = strcspn (p1, ">");
+				sett = num1 > 0 ? g_strndup (p1, num1) : NULL;
+			}
 		}
 	}
 
-	return prop;
+	if (line) {
+		/* line looks like this:
+		  " set 802-1x.pac-file ..." or " set pac-file ..." */
+		const char *p1, *p2, *dot;
+		size_t n1, n2, n3, n4;
+		size_t num1, num2, len;
+		n1 = strspn  (line,    " \t");         /* white-space */
+		n2 = strcspn (line+n1, " \t\0") + n1;  /* command */
+		n3 = strspn  (line+n2, " \t")   + n2;  /* white-space */
+		n4 = strcspn (line+n3, " \t\0") + n3;  /* setting/property */
+		p1 = line + n3;
+		len = n4 - n3;
+
+		dot = strchr (p1, '.');
+		if (dot && dot < p1 + len) {
+			p2 = dot + 1;
+			num1 = strcspn (p1, ".");
+			num2 = len > num1 + 1 ? len - num1 - 1 : 0;
+			sett = num1 > 0 ? g_strndup (p1, num1) : sett;
+			prop = num2 > 0 ? g_strndup (p2, num2) : prop;
+		} else {
+			if (!prop)
+				prop = len > 0 ? g_strndup (p1, len) : NULL;
+		}
+	}
+
+	if (setting)
+		*setting = sett;
+	else
+		g_free (sett);
+	if (property)
+		*property = prop;
+	else
+		g_free (prop);
 }
 
 static gboolean
-should_complete_files (const char *prompt, const char *line)
+_get_and_check_property (const char *prompt,
+                         const char *line,
+                         const char **array,
+                         const char **array_multi,
+                         gboolean *multi)
 {
 	char *prop;
 	gboolean found = FALSE;
+
+	extract_setting_and_property (prompt, line, NULL, &prop);
+	if (prop) {
+		if (array)
+			found = !!nmc_string_is_valid (prop, array, NULL);
+		if (array_multi && multi)
+			*multi = !!nmc_string_is_valid (prop, array_multi, NULL);
+		g_free (prop);
+	}
+	return found;
+}
+
+static gboolean
+should_complete_files (const char *prompt, const char *line)
+{
 	const char *file_properties[] = {
 		/* '802-1x' properties */
 		"ca-cert",
@@ -6144,32 +6215,86 @@ should_complete_files (const char *prompt, const char *line)
 		"config",
 		NULL
 	};
-
-	prop = extract_property_name (prompt, line);
-	if (prop) {
-		found = !!nmc_string_is_valid (prop, file_properties, NULL);
-		g_free (prop);
-	}
-	return found;
+	return _get_and_check_property (prompt, line, file_properties, NULL, NULL);
 }
 
 static gboolean
 should_complete_vpn_uuids (const char *prompt, const char *line)
 {
-	char *prop;
-	gboolean found = FALSE;
 	const char *uuid_properties[] = {
 		/* 'connection' properties */
 		"secondaries",
 		NULL
 	};
+	return _get_and_check_property (prompt, line, uuid_properties, NULL, NULL);
+}
 
-	prop = extract_property_name (prompt, line);
-	if (prop) {
-		found = !!nmc_string_is_valid (prop, uuid_properties, NULL);
-		g_free (prop);
-	}
-	return found;
+static char *is_property_valid (NMSetting *setting, const char *property, GError **error);
+static const char **
+get_allowed_property_values (void)
+{
+	const NameItem *valid_settings_arr;
+	const char *setting_name;
+	NMSetting *setting = NULL;
+	char *property = NULL;
+	char *sett = NULL, *prop = NULL;
+	const char **avals = NULL;
+
+	extract_setting_and_property (rl_prompt, rl_line_buffer, &sett, &prop);
+	if (sett) {
+		valid_settings_arr = get_valid_settings_array (nmc_tab_completion.con_type);
+		setting_name = check_valid_name (sett, valid_settings_arr, NULL);
+		setting = nmc_setting_new_for_name (setting_name);
+	} else
+		setting = nmc_tab_completion.setting ? g_object_ref (nmc_tab_completion.setting) : NULL;
+
+	if (setting && prop)
+		property = is_property_valid (setting, prop, NULL);
+	else
+		property = g_strdup (nmc_tab_completion.property);
+
+	if (setting && property)
+		avals = nmc_setting_get_property_allowed_values (setting, property);
+
+	g_free (sett);
+	g_free (prop);
+	if (setting)
+		g_object_unref (setting);
+	g_free (property);
+	return avals;
+}
+
+static gboolean
+should_complete_property_values (const char *prompt, const char *line, gboolean *multi)
+{
+	/* properties allowing multiple values */
+	const char *multi_props[] = {
+		/* '802-1x' properties */
+		NM_SETTING_802_1X_EAP,
+		/* '802-11-wireless-security' properties */
+		NM_SETTING_WIRELESS_SECURITY_PROTO,
+		NM_SETTING_WIRELESS_SECURITY_PAIRWISE,
+		NM_SETTING_WIRELESS_SECURITY_GROUP,
+		/* 'bond' properties */
+		NM_SETTING_BOND_OPTIONS,
+		/* 'ethernet' properties */
+		NM_SETTING_WIRED_S390_OPTIONS,
+		NULL
+	};
+	_get_and_check_property (prompt, line, NULL, multi_props, multi);
+	return get_allowed_property_values () != NULL;
+}
+
+static char *
+gen_property_values (const char *text, int state)
+{
+	char *ret = NULL;
+	const char **avals;
+
+	avals = get_allowed_property_values ();
+	if (avals)
+		ret = nmc_rl_gen_func_basic (text, state, avals);
+	return ret;
 }
 
 /* from readline */
@@ -6239,6 +6364,7 @@ nmcli_editor_tab_completion (const char *text, int start, int end)
 		if (!strchr (prompt_tmp, '.')) {
 			int level = g_str_has_prefix (prompt_tmp, "nmcli>") ? 0 : 1;
 			const char *dot = strchr (line, '.');
+			gboolean multi;
 
 			/* Main menu  - level 0,1 */
 			if (start == n1)
@@ -6259,9 +6385,12 @@ nmcli_editor_tab_completion (const char *text, int start, int end)
 					} else if (num >= 3) {
 						if (num == 3 && should_complete_files (NULL, line))
 							rl_attempted_completion_over = 0;
-						if (should_complete_vpn_uuids (NULL, line)) {
+						else if (should_complete_vpn_uuids (NULL, line)) {
 							rl_completion_display_matches_hook = uuid_display_hook;
 							generator_func = gen_vpn_uuids;
+						} else if (   should_complete_property_values (NULL, line, &multi)
+							   && (num == 3 || multi)) {
+							generator_func = gen_property_values;
 						}
 					}
 				} else if (  (   should_complete_cmd (line, end, "remove", &num, NULL)
@@ -6296,6 +6425,8 @@ nmcli_editor_tab_completion (const char *text, int start, int end)
 			if (start == n1)
 				generator_func = gen_nmcli_cmds_submenu;
 			else {
+				gboolean multi;
+
 				if (   should_complete_cmd (line, end, "add", &num, NULL)
 				    || should_complete_cmd (line, end, "set", &num, NULL)) {
 					if (num <= 2 && should_complete_files (prompt_tmp, line))
@@ -6303,6 +6434,9 @@ nmcli_editor_tab_completion (const char *text, int start, int end)
 					else if (should_complete_vpn_uuids (prompt_tmp, line)) {
 						rl_completion_display_matches_hook = uuid_display_hook;
 						generator_func = gen_vpn_uuids;
+					} else if (   should_complete_property_values (prompt_tmp, NULL, &multi)
+						   && (num <= 2 || multi)) {
+						generator_func = gen_property_values;
 					}
 				}
 				if (should_complete_cmd (line, end, "print", &num, NULL) && num <= 2)
@@ -7016,6 +7150,9 @@ property_edit_submenu (NmCli *nmc,
 	gboolean temp_changes;
 	gboolean removed;
 
+	/* Set global variable for use in TAB completion */
+	nmc_tab_completion.property = prop_name;
+
 	prompt = nmc_colorize (nmc->editor_prompt_color, "nmcli %s.%s> ",
 	                       nm_setting_get_name (curr_setting), prop_name);
 
@@ -7047,9 +7184,16 @@ property_edit_submenu (NmCli *nmc,
 			 *                   ADD adds the new value(s)
 			 * single values:  : both SET and ADD sets the new value
 			 */
-			if (!cmd_property_arg)
+			if (!cmd_property_arg) {
+				const char **avals = nmc_setting_get_property_allowed_values (curr_setting, prop_name);
+				if (avals) {
+					char *avals_str = nmc_util_strv_for_display (avals, FALSE);
+					g_print (_("Allowed values for '%s' property: %s\n"),
+					         prop_name, avals_str);
+					g_free (avals_str);
+				}
 				prop_val_user = nmc_readline (_("Enter '%s' value: "), prop_name);
-			else
+			} else
 				prop_val_user = g_strdup (cmd_property_arg);
 
 			/* nmc_setting_set_property() only adds new value, thus we have to
@@ -7078,7 +7222,7 @@ property_edit_submenu (NmCli *nmc,
 
 		case NMC_EDITOR_SUB_CMD_CHANGE:
 			rl_startup_hook = nmc_rl_set_deftext;
-			nmc_rl_pre_input_deftext = nmc_setting_get_property_out2in (curr_setting, prop_name, NULL);
+			nmc_rl_pre_input_deftext = nmc_setting_get_property_parsable (curr_setting, prop_name, NULL);
 			prop_val_user = nmc_readline (_("Edit '%s' value: "), prop_name);
 
 			nmc_property_get_gvalue (curr_setting, prop_name, &prop_g_value);
@@ -7144,6 +7288,8 @@ property_edit_submenu (NmCli *nmc,
 			break;
 
 		case NMC_EDITOR_SUB_CMD_BACK:
+			/* Set global variable for use in TAB completion */
+			nmc_tab_completion.property = NULL;
 			cmd_property_loop = FALSE;
 			break;
 
@@ -7456,7 +7602,7 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t
 				if (menu_ctx.level == 1) {
 					const char *prop_name;
 					char *prop_val_user = NULL;
-					const char *avals;
+					const char **avals;
 					GError *tmp_err = NULL;
 
 					prop_name = ask_check_property (cmd_arg,
@@ -7466,9 +7612,12 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t
 						break;
 
 					avals = nmc_setting_get_property_allowed_values (menu_ctx.curr_setting, prop_name);
-					if (avals)
-						g_print (_("Allowed values for '%s' property: %s\n"), prop_name, avals);
-
+					if (avals) {
+						char *avals_str = nmc_util_strv_for_display (avals, FALSE);
+						g_print (_("Allowed values for '%s' property: %s\n"),
+						         prop_name, avals_str);
+						g_free (avals_str);
+					}
 					prop_val_user = nmc_readline (_("Enter '%s' value: "), prop_name);
 
 					/* Set property value */
@@ -7520,10 +7669,13 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t
 
 				/* Ask for value */
 				if (!cmd_arg_v) {
-					const char *avals = nmc_setting_get_property_allowed_values (ss, prop_name);
-					if (avals)
-						g_print (_("Allowed values for '%s' property: %s\n"), prop_name, avals);
-
+					const char **avals = nmc_setting_get_property_allowed_values (ss, prop_name);
+					if (avals) {
+						char *avals_str = nmc_util_strv_for_display (avals, FALSE);
+						g_print (_("Allowed values for '%s' property: %s\n"),
+						         prop_name, avals_str);
+						g_free (avals_str);
+					}
 					cmd_arg_v = nmc_readline (_("Enter '%s' value: "), prop_name);
 				}
 
@@ -8646,52 +8798,44 @@ finish:
 	return nmc->return_value;
 }
 
-
-typedef struct {
-	NmCli *nmc;
-	int counter;
-} DeleteStateInfo;
-
 static void
 delete_cb (GObject *con, GAsyncResult *result, gpointer user_data)
 {
-	DeleteStateInfo *info = (DeleteStateInfo *) user_data;
+	ConnectionCbInfo *info = (ConnectionCbInfo *) user_data;
 	GError *error = NULL;
 
 	if (!nm_remote_connection_delete_finish (NM_REMOTE_CONNECTION (con), result, &error)) {
-		g_string_printf (info->nmc->return_text, _("Error: Connection deletion failed: %s"),
-		                 error->message);
+		g_string_printf (info->nmc->return_text, _("Error: not all connections deleted."));
+		g_printerr (_("Error: Connection deletion failed: %s"),
+		            error->message);
 		g_error_free (error);
 		info->nmc->return_value = NMC_RESULT_ERROR_CON_DEL;
-	}
-
-	info->counter--;
-	if (info->counter == 0) {
-		g_free (info);
-		quit ();
+		connection_cb_info_finish (info, con);
+	} else {
+		if (info->nmc->nowait_flag)
+			connection_cb_info_finish (info, con);
 	}
 }
 
 static NMCResultCode
 do_connection_delete (NmCli *nmc, int argc, char **argv)
 {
-	NMConnection *connection = NULL;
-	DeleteStateInfo *del_info = NULL;
-	char *line = NULL;
+	NMConnection *connection;
+	ConnectionCbInfo *info = NULL;
+	GSList *queue = NULL, *iter;
 	char **arg_arr = NULL;
 	char **arg_ptr = argv;
 	int arg_num = argc;
 	GString *invalid_cons = NULL;
-	gboolean del_info_free = FALSE;
 	int pos = 0;
 
-	nmc->return_value = NMC_RESULT_SUCCESS;
-	nmc->should_wait = FALSE;
+	if (nmc->timeout == -1)
+		nmc->timeout = 10;
 
 	if (argc == 0) {
 		if (nmc->ask) {
-			line = nmc_readline (PROMPT_CONNECTION);
-			nmc_string_to_arg_array (line, "", &arg_arr, &arg_num);
+			char *line = nmc_readline (PROMPT_CONNECTIONS);
+			nmc_string_to_arg_array (line, NULL, TRUE, &arg_arr, &arg_num);
 			g_free (line);
 			arg_ptr = arg_arr;
 		}
@@ -8702,11 +8846,6 @@ do_connection_delete (NmCli *nmc, int argc, char **argv)
 		}
 	}
 
-	del_info = g_malloc0 (sizeof (DeleteStateInfo));
-	del_info->nmc = nmc;
-	del_info->counter = 0;
-	del_info_free = TRUE;
-
 	while (arg_num > 0) {
 		const char *selector = NULL;
 
@@ -8722,43 +8861,50 @@ do_connection_delete (NmCli *nmc, int argc, char **argv)
 		}
 
 		connection = nmc_find_connection (nmc->connections, selector, *arg_ptr, &pos);
-		if (!connection) {
-			if (nmc->print_output != NMC_PRINT_TERSE)
-				g_print (_("Error: unknown connection: %s\n"), *arg_ptr);
-
+		if (connection) {
+			/* Check if the connection is unique. */
+			/* Calling delete for the same connection repeatedly would result in
+			 * NM responding for the last D-Bus call only and we would stall. */
+			if (!g_slist_find (queue, connection))
+				queue = g_slist_prepend (queue, g_object_ref (connection));
+		} else {
+			g_printerr (_("Error: unknown connection '%s'\n"), *arg_ptr);
+			g_string_printf (nmc->return_text, _("Error: not all active connections found."));
+			nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND;
 			if (!invalid_cons)
 				invalid_cons = g_string_new (NULL);
 			g_string_append_printf (invalid_cons, "'%s', ", *arg_ptr);
+		}
 
-			/* take the next argument and continue */
+		/* Take next argument (if there's no other connection of the same name) */
+		if (!pos)
 			next_arg (&arg_num, &arg_ptr);
-			continue;
-		}
+	}
 
-		/* We need to wait a bit so that nmcli's permissions can be checked.
-		 * We will exit when D-Bus return (error) messages are received.
-		 */
-		nmc->should_wait = TRUE;
+	if (!queue) {
+		g_string_printf (nmc->return_text, _("Error: no connection provided."));
+		nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND;
+		goto finish;
+	}
+	queue = g_slist_reverse (queue);
 
-		/* del_info deallocation is handled in delete_cb() */
-		del_info_free = FALSE;
+	info = g_slice_new0 (ConnectionCbInfo);
+	info->nmc = nmc;
+	info->queue = queue;
+	info->timeout_id = g_timeout_add_seconds (nmc->timeout, connection_op_timeout_cb, info);
 
-		del_info->counter++;
+	nmc->nowait_flag = (nmc->timeout == 0);
+	nmc->should_wait = TRUE;
 
-		/* Delete the connection */
-		nm_remote_connection_delete_async (NM_REMOTE_CONNECTION (connection),
-		                                   NULL, delete_cb, del_info);
+	g_signal_connect (nmc->client, NM_CLIENT_CONNECTION_REMOVED,
+	                  G_CALLBACK (connection_removed_cb), info);
 
-		/* Take next argument (if there's no other connection of the same name) */
-		if (!pos)
-			next_arg (&arg_num, &arg_ptr);
-	}
+	/* Now delete the connections */
+	for (iter = queue; iter; iter = g_slist_next (iter))
+		nm_remote_connection_delete_async (NM_REMOTE_CONNECTION (iter->data),
+		                                   NULL, delete_cb, info);
 
 finish:
-	if (del_info_free)
-		g_free (del_info);
-	g_strfreev (arg_arr);
-
 	if (invalid_cons) {
 		g_string_truncate (invalid_cons, invalid_cons->len-2);  /* truncate trailing ", " */
 		g_string_printf (nmc->return_text, _("Error: cannot delete unknown connection(s): %s."),
@@ -8766,6 +8912,7 @@ finish:
 		nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND;
 		g_string_free (invalid_cons, TRUE);
 	}
+	g_strfreev (arg_arr);
 	return nmc->return_value;
 }
 
@@ -8881,7 +9028,7 @@ gen_func_connection_names (const char *text, int state)
 	for (i = 0; i < nm_cli.connections->len; i++) {
 		NMConnection *con = NM_CONNECTION (nm_cli.connections->pdata[i]);
 		const char *id = nm_connection_get_id (con);
-		connections[i++] = id;
+		connections[i] = id;
 	}
 	connections[i] = NULL;
 
@@ -8900,14 +9047,17 @@ nmcli_con_tab_completion (const char *text, int start, int end)
 	/* Disable readline's default filename completion */
 	rl_attempted_completion_over = 1;
 
-	/* Disable appending space after completion */
-	rl_completion_append_character = '\0';
+	if (g_strcmp0 (rl_prompt, PROMPT_CONNECTION) == 0) {
+		/* Disable appending space after completion */
+		rl_completion_append_character = '\0';
 
-	if (!is_single_word (rl_line_buffer))
-		return NULL;
+		if (!is_single_word (rl_line_buffer))
+			return NULL;
 
-	if (g_strcmp0 (rl_prompt, PROMPT_CONNECTION) == 0)
 		generator_func = gen_func_connection_names;
+	} else if (g_strcmp0 (rl_prompt, PROMPT_CONNECTIONS) == 0) {
+		generator_func = gen_func_connection_names;
+	}
 
 	if (generator_func)
 		match_array = rl_completion_matches (text, generator_func);