about summary refs log tree commit diff
path: root/src/nmcli
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2022-05-04 15:35:24 +0200
committerMichael Biebl <biebl@debian.org>2022-05-04 15:35:24 +0200
commit9959fdb2e8ddd06f2161798ca0a39c77d67c652d (patch)
tree2ce24a336d2b1c5fd5dec3090db312eded6c78ba /src/nmcli
parent8c623dddbdebe354cb94bfc559a5371a14865317 (diff)
New upstream version 1.37.92 upstream/1.37.92
Diffstat (limited to 'src/nmcli')
-rw-r--r--src/nmcli/common.c8
-rw-r--r--src/nmcli/connections.c337
-rw-r--r--src/nmcli/devices.c155
-rw-r--r--src/nmcli/general.c10
-rw-r--r--src/nmcli/generate-docs-nm-settings-nmcli.xml8
-rw-r--r--src/nmcli/generate-docs-nm-settings-nmcli.xml.in8
-rw-r--r--src/nmcli/nmcli.c14
-rw-r--r--src/nmcli/utils.c6
8 files changed, 391 insertions, 155 deletions
diff --git a/src/nmcli/common.c b/src/nmcli/common.c
index 26398c84..f42f76e4 100644
--- a/src/nmcli/common.c
+++ b/src/nmcli/common.c
@@ -889,13 +889,7 @@ nmc_readline_helper(const NmcConfig *nmc_config, const char *prompt)
 
     nmc_set_in_readline(TRUE);
 
-    io_source = nm_g_unix_fd_source_new(STDIN_FILENO,
-                                        G_IO_IN,
-                                        G_PRIORITY_DEFAULT,
-                                        stdin_ready_cb,
-                                        NULL,
-                                        NULL);
-    g_source_attach(io_source, NULL);
+    io_source = nm_g_unix_fd_add_source(STDIN_FILENO, G_IO_IN, stdin_ready_cb, NULL);
 
 read_again:
     rl_string   = NULL;
diff --git a/src/nmcli/connections.c b/src/nmcli/connections.c
index 025f96a1..ecf8e2e2 100644
--- a/src/nmcli/connections.c
+++ b/src/nmcli/connections.c
@@ -513,6 +513,40 @@ _con_show_fcn_get_type(NMConnection *c, NMActiveConnection *ac, NMMetaAccessorGe
     return connection_type_to_display(s, get_type);
 }
 
+static const char *
+_connection_check_deprecated(NMConnection *c)
+{
+    NMSettingWirelessSecurity *s_wsec;
+    const char                *key_mgmt;
+    const char                *type;
+
+    type = nm_connection_get_connection_type(c);
+
+    if (strcmp(type, NM_SETTING_WIMAX_SETTING_NAME) == 0)
+        return _("WiMax is no longer supported");
+
+    s_wsec = nm_connection_get_setting_wireless_security(c);
+    if (s_wsec) {
+        key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec);
+        if (NM_IN_STRSET(key_mgmt, "ieee8021x", "none"))
+            return _("WEP encryption is known to be insecure");
+    }
+
+    return NULL;
+}
+
+static NMMetaColor
+_connection_to_color(NMConnection *c, NMActiveConnection *ac)
+{
+    if (ac)
+        return nmc_active_connection_state_to_color(ac);
+
+    if (_connection_check_deprecated(c))
+        return NM_META_COLOR_CONNECTION_DEPRECATED;
+
+    return NM_META_COLOR_CONNECTION_UNKNOWN;
+}
+
 static gconstpointer
 _metagen_con_show_get_fcn(NMC_META_GENERIC_INFO_GET_FCN_ARGS)
 {
@@ -523,7 +557,7 @@ _metagen_con_show_get_fcn(NMC_META_GENERIC_INFO_GET_FCN_ARGS)
     const char                  *s;
     char                        *s_mut;
 
-    NMC_HANDLE_COLOR(nmc_active_connection_state_to_color(ac));
+    NMC_HANDLE_COLOR(_connection_to_color(c, ac));
 
     if (c)
         s_con = nm_connection_get_setting_connection(c);
@@ -1197,10 +1231,10 @@ usage_connection_delete(void)
 {
     g_printerr(_("Usage: nmcli connection delete { ARGUMENTS | help }\n"
                  "\n"
-                 "ARGUMENTS := [id | uuid | path] <ID>\n"
+                 "ARGUMENTS := [id | uuid | path] <ID>, ...\n"
                  "\n"
-                 "Delete a connection profile.\n"
-                 "The profile is identified by its name, UUID or D-Bus path.\n\n"));
+                 "Delete connection profiles.\n"
+                 "The profiles are identified by their name, UUID or D-Bus path.\n\n"));
 }
 
 static void
@@ -1261,6 +1295,17 @@ usage_connection_export(void)
 }
 
 static void
+usage_connection_migrate(void)
+{
+    g_printerr(_("Usage: nmcli connection migrate { ARGUMENTS | help }\n"
+                 "\n"
+                 "ARGUMENTS := [--plugin <plugin>] [id | uuid | path] <ID>, ...\n"
+                 "\n"
+                 "Migrate connection profiles to a different settings plugin,\n"
+                 "such as \"keyfile\" (default) or \"ifcfg-rh\".\n\n"));
+}
+
+static void
 quit(void)
 {
     if (nm_clear_g_source(&progress_id))
@@ -1410,13 +1455,13 @@ nmc_connection_profile_details(NMConnection *connection, NmCli *nmc)
                             TRUE,
                             &prop_array,
                             &error);
-    if (error) {
+    if (!print_settings_array) {
+        g_return_val_if_fail(error, FALSE);
         g_string_printf(nmc->return_text, _("Error: 'connection show': %s"), error->message);
         g_error_free(error);
         nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
         return FALSE;
     }
-    g_assert(print_settings_array);
 
     /* Main header */
     {
@@ -1478,9 +1523,6 @@ nmc_active_connection_state_to_color(NMActiveConnection *ac)
 {
     NMActiveConnectionState state;
 
-    if (!ac)
-        return NM_META_COLOR_CONNECTION_UNKNOWN;
-
     if (NM_FLAGS_HAS(nm_active_connection_get_state_flags(ac), NM_ACTIVATION_STATE_FLAG_EXTERNAL))
         return NM_META_COLOR_CONNECTION_EXTERNAL;
 
@@ -1520,13 +1562,13 @@ nmc_active_connection_details(NMActiveConnection *acon, NmCli *nmc)
         TRUE,
         &group_fields,
         &error);
-    if (error) {
+    if (!print_groups) {
+        g_return_val_if_fail(error, FALSE);
         g_string_printf(nmc->return_text, _("Error: 'connection show': %s"), error->message);
         g_error_free(error);
         nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
         return FALSE;
     }
-    g_assert(print_groups);
 
     /* Main header */
     {
@@ -1881,6 +1923,7 @@ con_show_get_items_cmp(gconstpointer pa, gconstpointer pb, gpointer user_data)
             }
         }
 
+        NM_CMP_DIRECT(!!_connection_check_deprecated(c_a), !!_connection_check_deprecated(c_b));
         NM_CMP_DIRECT_STRCMP0(nm_connection_get_uuid(c_a), nm_connection_get_uuid(c_b));
         NM_CMP_DIRECT_STRCMP0(nm_connection_get_path(c_a), nm_connection_get_path(c_b));
     }
@@ -2150,7 +2193,7 @@ do_connections_show(const NMCCommand *cmd, NmCli *nmc, int argc, const char *con
                 goto finish;
             break;
         default:
-            g_assert_not_reached();
+            g_return_if_reached();
             break;
         }
     }
@@ -2470,7 +2513,7 @@ find_device_for_connection(NmCli        *nmc,
     g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
 
     s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    g_return_val_if_fail(s_con, FALSE);
     con_type = nm_setting_connection_get_connection_type(s_con);
 
     if (nm_streq(con_type, NM_SETTING_VPN_SETTING_NAME)) {
@@ -2959,7 +3002,7 @@ do_connection_up(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const
         gs_free char *line = NULL;
 
         /* nmc_do_cmd() should not call this with argc=0. */
-        g_assert(!nmc->complete);
+        g_return_if_fail(!nmc->complete);
 
         line = nmc_readline(&nmc->nmc_config, PROMPT_CONNECTION);
         nmc_string_to_arg_array(line, NULL, TRUE, &arg_arr, &arg_num);
@@ -3215,7 +3258,7 @@ do_connection_down(const NMCCommand *cmd, NmCli *nmc, int argc, const char *cons
 
     if (argc == 0) {
         /* nmc_do_cmd() should not call this with argc=0. */
-        g_assert(!nmc->complete);
+        g_return_if_fail(!nmc->complete);
 
         if (nmc->ask) {
             gs_free char *line = NULL;
@@ -3660,7 +3703,8 @@ is_setting_mandatory(NMConnection *connection, NMSetting *setting)
     guint                                    i;
 
     s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    g_return_val_if_fail(s_con, FALSE);
+
     c_type = nm_setting_connection_get_connection_type(s_con);
     s_type = nm_setting_connection_get_slave_type(s_con);
 
@@ -3739,7 +3783,7 @@ normalized_master_for_slave(const GPtrArray *connections,
     for (i = 0; i < connections->len; i++) {
         connection = NM_CONNECTION(connections->pdata[i]);
         s_con      = nm_connection_get_setting_connection(connection);
-        g_assert(s_con);
+        g_return_val_if_fail(s_con, NULL);
         con_type = nm_setting_connection_get_connection_type(s_con);
         if (type && !nm_streq0(con_type, type))
             continue;
@@ -4243,7 +4287,7 @@ con_settings(NMConnection                             *connection,
     g_return_val_if_fail(slv_settings, FALSE);
 
     s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    g_return_val_if_fail(s_con, FALSE);
 
     con_type      = nm_setting_connection_get_slave_type(s_con);
     *slv_settings = nm_meta_setting_info_valid_parts_for_slave_type(con_type, NULL);
@@ -5209,14 +5253,52 @@ nmc_process_connection_properties(NmCli              *nmc,
 }
 
 static void
+connection_warnings(NmCli *nmc, NMConnection *connection)
+{
+    const GPtrArray *connections;
+    guint            i, found;
+    const char      *id;
+    const char      *deprecated;
+
+    deprecated = _connection_check_deprecated(NM_CONNECTION(connection));
+    if (deprecated)
+        g_printerr(_("Warning: %s.\n"), deprecated);
+
+    connections = nm_client_get_connections(nmc->client);
+    if (!connections)
+        return;
+
+    id    = nm_connection_get_id(connection);
+    found = 0;
+    for (i = 0; i < connections->len; i++) {
+        NMConnection *candidate = NM_CONNECTION(connections->pdata[i]);
+
+        if ((NMConnection *) connection == candidate)
+            continue;
+        if (nm_streq0(nm_connection_get_id(candidate), id))
+            found++;
+    }
+
+    if (found > 0) {
+        g_printerr(g_dngettext(GETTEXT_PACKAGE,
+                               "Warning: There is another connection with the name '%1$s'. "
+                               "Reference the connection by its uuid '%2$s'\n",
+                               "Warning: There are %3$u other connections with the name "
+                               "'%1$s'. Reference the connection by its uuid '%2$s'\n",
+                               found),
+                   id,
+                   nm_connection_get_uuid(NM_CONNECTION(connection)),
+                   found);
+    }
+}
+
+static void
 add_connection_cb(GObject *client, GAsyncResult *result, gpointer user_data)
 {
     nm_auto_free_add_connection_info AddConnectionInfo *info = user_data;
     NmCli                                              *nmc  = info->nmc;
     NMRemoteConnection                                 *connection;
     GError                                             *error = NULL;
-    const GPtrArray                                    *connections;
-    guint                                               i, found;
 
     connection = nm_client_add_connection2_finish(NM_CLIENT(client), result, NULL, &error);
     if (error) {
@@ -5227,29 +5309,7 @@ add_connection_cb(GObject *client, GAsyncResult *result, gpointer user_data)
         g_error_free(error);
         nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION;
     } else {
-        connections = nm_client_get_connections(nmc->client);
-        if (connections) {
-            found = 0;
-            for (i = 0; i < connections->len; i++) {
-                NMConnection *candidate = NM_CONNECTION(connections->pdata[i]);
-
-                if ((NMConnection *) connection == candidate)
-                    continue;
-                if (nm_streq0(nm_connection_get_id(candidate), info->new_id))
-                    found++;
-            }
-            if (found > 0) {
-                g_printerr(g_dngettext(GETTEXT_PACKAGE,
-                                       "Warning: There is another connection with the name '%1$s'. "
-                                       "Reference the connection by its uuid '%2$s'\n",
-                                       "Warning: There are %3$u other connections with the name "
-                                       "'%1$s'. Reference the connection by its uuid '%2$s'\n",
-                                       found),
-                           info->new_id,
-                           nm_connection_get_uuid(NM_CONNECTION(connection)),
-                           found);
-            }
-        }
+        connection_warnings(nmc, NM_CONNECTION(connection));
 
         /* We print here human readable text, but as scripts might parse this output
          * (with LANG=C), this is important to not change in the future. At least
@@ -7252,7 +7312,8 @@ editor_show_status_line(NMConnection *connection, gboolean dirty, gboolean temp)
     const char          *con_type, *con_id, *con_uuid;
 
     s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    g_return_if_fail(s_con);
+
     con_type = nm_setting_connection_get_connection_type(s_con);
     con_id   = nm_connection_get_id(connection);
     con_uuid = nm_connection_get_uuid(connection);
@@ -7595,15 +7656,18 @@ confirm_connection_saving(const NmcConfig *nmc_config, NMConnection *local, NMCo
     gboolean             confirmed = TRUE;
 
     s_con_loc = nm_connection_get_setting_connection(local);
-    g_assert(s_con_loc);
+    g_return_val_if_fail(s_con_loc, FALSE);
+
     ac_local = nm_setting_connection_get_autoconnect(s_con_loc);
 
     if (remote) {
         s_con_rem = nm_connection_get_setting_connection(remote);
-        g_assert(s_con_rem);
+        g_return_val_if_fail(s_con_rem, FALSE);
+
         ac_remote = nm_setting_connection_get_autoconnect(s_con_rem);
-    } else
+    } else {
         ac_remote = FALSE;
+    }
 
     if (ac_local && !ac_remote) {
         gs_free char *answer = NULL;
@@ -8280,9 +8344,12 @@ editor_menu_main(NmCli *nmc, NMConnection *connection, const char *connection_ty
                     connection_changed = FALSE;
                 }
 
-                source = g_timeout_source_new(10 * NM_UTILS_MSEC_PER_SEC);
-                g_source_set_callback(source, editor_save_timeout, &timeout, NULL);
-                g_source_attach(source, g_main_loop_get_context(loop));
+                source = nm_g_source_attach(nm_g_timeout_source_new(10 * NM_UTILS_MSEC_PER_SEC,
+                                                                    G_PRIORITY_DEFAULT,
+                                                                    editor_save_timeout,
+                                                                    &timeout,
+                                                                    NULL),
+                                            g_main_loop_get_context(loop));
 
                 while (!nmc_editor_cb_called && !timeout)
                     g_main_context_iteration(NULL, TRUE);
@@ -8527,7 +8594,8 @@ editor_init_new_connection(NmCli *nmc, NMConnection *connection, const char *sla
     const char          *con_type;
 
     s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    g_return_if_fail(s_con);
+
     con_type = nm_setting_connection_get_connection_type(s_con);
 
     /* Initialize new connection according to its type using sensible defaults. */
@@ -8851,6 +8919,8 @@ modify_connection_cb(GObject *connection, GAsyncResult *result, gpointer user_da
                         error->message);
         nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
     } else {
+        connection_warnings(nmc, NM_CONNECTION(connection));
+
         if (nmc->nmc_config.print_output == NMC_PRINT_PRETTY) {
             g_print(_("Connection '%s' (%s) successfully modified.\n"),
                     nm_connection_get_id(NM_CONNECTION(connection)),
@@ -8955,7 +9025,7 @@ do_connection_clone(const NMCCommand *cmd, NmCli *nmc, int argc, const char *con
         gs_free char *line = NULL;
 
         /* nmc_do_cmd() should not call this with argc=0. */
-        g_assert(!nmc->complete);
+        g_return_if_fail(!nmc->complete);
 
         line = nmc_readline(&nmc->nmc_config, PROMPT_CONNECTION);
         nmc_string_to_arg_array(line, NULL, TRUE, &arg_arr, &arg_num);
@@ -9054,7 +9124,7 @@ do_connection_delete(const NMCCommand *cmd, NmCli *nmc, int argc, const char *co
             gs_free char *line = NULL;
 
             /* nmc_do_cmd() should not call this with argc=0. */
-            g_assert(!nmc->complete);
+            g_return_if_fail(!nmc->complete);
 
             line = nmc_readline(&nmc->nmc_config, PROMPT_CONNECTIONS);
             nmc_string_to_arg_array(line, NULL, TRUE, &arg_arr, &arg_num);
@@ -9079,8 +9149,11 @@ do_connection_delete(const NMCCommand *cmd, NmCli *nmc, int argc, const char *co
             nmc->return_value = error->code;
             g_clear_error(&error);
 
-            if (nmc->return_value != NMC_RESULT_ERROR_NOT_FOUND)
+            if (nmc->return_value != NMC_RESULT_ERROR_NOT_FOUND) {
+                g_string_free(invalid_cons, TRUE);
+                invalid_cons = NULL;
                 goto finish;
+            }
 
             if (!invalid_cons)
                 invalid_cons = g_string_new(NULL);
@@ -9133,7 +9206,6 @@ finish:
         g_string_printf(nmc->return_text,
                         _("Error: cannot delete unknown connection(s): %s."),
                         invalid_cons->str);
-        nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND;
     }
 }
 
@@ -9192,7 +9264,7 @@ do_connection_monitor(const NMCCommand *cmd, NmCli *nmc, int argc, const char *c
         /* No connections specified. Monitor all. */
 
         /* nmc_do_cmd() should not call this with argc=0. */
-        g_assert(!nmc->complete);
+        g_return_if_fail(!nmc->complete);
 
         connections = nm_client_get_connections(nmc->client);
     } else {
@@ -9349,7 +9421,7 @@ do_connection_import(const NMCCommand *cmd, NmCli *nmc, int argc, const char *co
 
     if (argc == 0) {
         /* nmc_do_cmd() should not call this with argc=0. */
-        g_assert(!nmc->complete);
+        g_return_if_fail(!nmc->complete);
 
         if (nmc->ask) {
             type_ask =
@@ -9492,7 +9564,7 @@ do_connection_export(const NMCCommand *cmd, NmCli *nmc, int argc, const char *co
         gs_free char *line = NULL;
 
         /* nmc_do_cmd() should not call this with argc=0. */
-        g_assert(!nmc->complete);
+        g_return_if_fail(!nmc->complete);
 
         line = nmc_readline(&nmc->nmc_config, PROMPT_VPN_CONNECTION);
         nmc_string_to_arg_array(line, NULL, TRUE, &arg_arr, &arg_num);
@@ -9586,6 +9658,154 @@ finish:
         unlink(path);
 }
 
+static void
+migrate_cb(GObject *obj, GAsyncResult *result, gpointer user_data)
+{
+    ConnectionCbInfo          *info       = (ConnectionCbInfo *) user_data;
+    NMConnection              *connection = NM_CONNECTION(obj);
+    gs_unref_variant GVariant *res        = NULL;
+    GError                    *error      = NULL;
+
+    res = nm_remote_connection_update2_finish(NM_REMOTE_CONNECTION(obj), result, &error);
+    if (!res) {
+        g_string_printf(info->nmc->return_text, _("Error: not all connections migrated."));
+        g_printerr(_("Error: Connection migration failed: %s\n"), error->message);
+        g_error_free(error);
+        info->nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
+    } else {
+        g_print(_("Connection '%s' (%s) successfully migrated.\n"),
+                nm_connection_get_id(connection),
+                nm_connection_get_uuid(connection));
+    }
+    connection_cb_info_finish(info, obj);
+}
+
+static void
+do_connection_migrate(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv)
+{
+    NMConnection                 *connection;
+    ConnectionCbInfo             *info    = NULL;
+    gs_strfreev char            **arg_arr = NULL;
+    const char *const            *arg_ptr;
+    guint                         i;
+    int                           arg_num;
+    nm_auto_free_gstring GString *invalid_cons = NULL;
+    gs_unref_ptrarray GPtrArray  *found_cons   = NULL;
+    GError                       *error        = NULL;
+    const char                   *plugin       = "keyfile";
+    const GPtrArray              *connections  = NULL;
+    int                           option;
+
+    if (nmc->timeout == -1)
+        nmc->timeout = 10;
+
+    while ((option = next_arg(nmc, &argc, &argv, "--plugin", NULL)) > 0) {
+        switch (option) {
+        case 1: /* --plugin */
+            argc--;
+            argv++;
+            if (!argc) {
+                g_set_error_literal(&error, NMCLI_ERROR, 0, _("'--plugin' argument is missing"));
+                goto finish;
+            }
+            plugin = *argv;
+            break;
+        default:
+            g_return_if_reached();
+            break;
+        }
+    }
+
+    arg_ptr = argv;
+    arg_num = argc;
+    if (argc == 0) {
+        if (nmc->ask) {
+            gs_free char *line = NULL;
+
+            /* nmc_do_cmd() should not call this with argc=0. */
+            g_return_if_fail(!nmc->complete);
+
+            line = nmc_readline(&nmc->nmc_config, PROMPT_CONNECTIONS);
+            nmc_string_to_arg_array(line, NULL, TRUE, &arg_arr, &arg_num);
+            arg_ptr = (const char *const *) arg_arr;
+        }
+    }
+
+    while (arg_num > 0) {
+        const char *cur_selector, *cur_value;
+
+        connection =
+            get_connection(nmc, &arg_num, &arg_ptr, &cur_selector, &cur_value, &found_cons, &error);
+        if (!connection) {
+            if (!nmc->complete)
+                g_printerr(_("Error: %s.\n"), error->message);
+            g_string_printf(nmc->return_text, _("Error: not all connections found."));
+            nmc->return_value = error->code;
+            g_clear_error(&error);
+
+            if (nmc->return_value != NMC_RESULT_ERROR_NOT_FOUND) {
+                g_string_free(invalid_cons, TRUE);
+                invalid_cons = NULL;
+                goto finish;
+            }
+
+            if (!invalid_cons)
+                invalid_cons = g_string_new(NULL);
+            if (cur_selector)
+                g_string_append_printf(invalid_cons, "%s '%s', ", cur_selector, cur_value);
+            else
+                g_string_append_printf(invalid_cons, "'%s', ", cur_value);
+        }
+    }
+
+    if (nmc->complete)
+        goto finish;
+
+    if (invalid_cons)
+        goto finish;
+
+    if (!found_cons) {
+        /* No connections specified explicitly? Fine, add all. */
+        found_cons  = g_ptr_array_new();
+        connections = nm_client_get_connections(nmc->client);
+        for (i = 0; i < connections->len; i++) {
+            connection = connections->pdata[i];
+            g_ptr_array_add(found_cons, connection);
+        }
+    }
+
+    info           = g_slice_new0(ConnectionCbInfo);
+    info->nmc      = nmc;
+    info->obj_list = g_ptr_array_sized_new(found_cons->len);
+    for (i = 0; i < found_cons->len; i++) {
+        connection = found_cons->pdata[i];
+        g_ptr_array_add(info->obj_list, g_object_ref(connection));
+    }
+    info->timeout_id  = g_timeout_add_seconds(nmc->timeout, connection_op_timeout_cb, info);
+    info->cancellable = g_cancellable_new();
+
+    nmc->nowait_flag = (nmc->timeout == 0);
+    nmc->should_wait++;
+
+    for (i = 0; i < found_cons->len; i++) {
+        nm_remote_connection_update2(NM_REMOTE_CONNECTION(found_cons->pdata[i]),
+                                     NULL,
+                                     0,
+                                     g_variant_new_parsed("{'plugin': <%s>}", plugin),
+                                     info->cancellable,
+                                     migrate_cb,
+                                     info);
+    }
+
+finish:
+    if (invalid_cons) {
+        g_string_truncate(invalid_cons, invalid_cons->len - 2); /* truncate trailing ", " */
+        g_string_printf(nmc->return_text,
+                        _("Error: cannot migrate unknown connection(s): %s."),
+                        invalid_cons->str);
+    }
+}
+
 static char *
 gen_func_connection_names(const char *text, int state)
 {
@@ -9693,6 +9913,7 @@ nmc_command_func_connection(const NMCCommand *cmd, NmCli *nmc, int argc, const c
         {"clone", do_connection_clone, usage_connection_clone, TRUE, TRUE},
         {"import", do_connection_import, usage_connection_import, TRUE, TRUE},
         {"export", do_connection_export, usage_connection_export, TRUE, TRUE},
+        {"migrate", do_connection_migrate, usage_connection_migrate, TRUE, TRUE},
         {"monitor", do_connection_monitor, usage_connection_monitor, TRUE, TRUE},
         {NULL, do_connections_show, usage, TRUE, TRUE},
     };
diff --git a/src/nmcli/devices.c b/src/nmcli/devices.c
index e23ef681..2bfc4cec 100644
--- a/src/nmcli/devices.c
+++ b/src/nmcli/devices.c
@@ -1216,12 +1216,30 @@ get_device(NmCli *nmc, int *argc, const char *const **argv, GError **error)
     return devices[i];
 }
 
+static bool
+_ap_is_wep(NMAccessPoint *ap)
+{
+    NM80211ApFlags         flags     = nm_access_point_get_flags(ap);
+    NM80211ApSecurityFlags wpa_flags = nm_access_point_get_wpa_flags(ap);
+    NM80211ApSecurityFlags rsn_flags = nm_access_point_get_rsn_flags(ap);
+
+    if ((flags & NM_802_11_AP_FLAGS_PRIVACY) && (wpa_flags == NM_802_11_AP_SEC_NONE)
+        && (rsn_flags == NM_802_11_AP_SEC_NONE)) {
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
 static int
 compare_aps(gconstpointer a, gconstpointer b, gpointer user_data)
 {
     NMAccessPoint *apa = *(NMAccessPoint **) a;
     NMAccessPoint *apb = *(NMAccessPoint **) b;
 
+    /* Sort the deprecated WEP connections last. */
+    NM_CMP_DIRECT(_ap_is_wep(apb), _ap_is_wep(apa));
+
     NM_CMP_DIRECT(nm_access_point_get_strength(apb), nm_access_point_get_strength(apa));
     NM_CMP_DIRECT(nm_access_point_get_frequency(apa), nm_access_point_get_frequency(apb));
     NM_CMP_DIRECT(nm_access_point_get_max_bitrate(apb), nm_access_point_get_max_bitrate(apa));
@@ -1249,22 +1267,19 @@ sort_access_points(const GPtrArray *aps)
 }
 
 typedef struct {
-    NmCli      *nmc;
-    int         index;
-    guint32     output_flags;
-    const char *active_bssid;
-    const char *device;
-    GPtrArray  *output_data;
+    NmCli         *nmc;
+    NMAccessPoint *active_ap;
+    const char    *device;
+    GPtrArray     *output_data;
+    int           *p_index;
+    guint32        output_flags;
 } APInfo;
 
 static void
-fill_output_access_point(gpointer data, gpointer user_data)
+fill_output_access_point(NMAccessPoint *ap, const APInfo *info)
 {
-    NMAccessPoint         *ap   = NM_ACCESS_POINT(data);
-    APInfo                *info = (APInfo *) user_data;
     NmcOutputField        *arr;
-    gboolean               active = FALSE;
-    NM80211ApFlags         flags;
+    gboolean               active;
     NM80211ApSecurityFlags wpa_flags, rsn_flags;
     guint32                freq, bitrate;
     guint8                 strength;
@@ -1284,14 +1299,9 @@ fill_output_access_point(gpointer data, gpointer user_data)
     const char            *sig_bars;
     NMMetaColor            color;
 
-    if (info->active_bssid) {
-        const char *current_bssid = nm_access_point_get_bssid(ap);
-        if (current_bssid && !strcmp(current_bssid, info->active_bssid))
-            active = TRUE;
-    }
+    active = (info->active_ap == ap);
 
     /* Get AP properties */
-    flags     = nm_access_point_get_flags(ap);
     wpa_flags = nm_access_point_get_wpa_flags(ap);
     rsn_flags = nm_access_point_get_rsn_flags(ap);
     ssid      = nm_access_point_get_ssid(ap);
@@ -1320,26 +1330,27 @@ fill_output_access_point(gpointer data, gpointer user_data)
 
     security_str = g_string_new(NULL);
 
-    if ((flags & NM_802_11_AP_FLAGS_PRIVACY) && (wpa_flags == NM_802_11_AP_SEC_NONE)
-        && (rsn_flags == NM_802_11_AP_SEC_NONE)) {
+    if (_ap_is_wep(ap)) {
         g_string_append(security_str, "WEP ");
-    }
-    if (wpa_flags != NM_802_11_AP_SEC_NONE) {
-        g_string_append(security_str, "WPA1 ");
-    }
-    if ((rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)
-        || (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) {
-        g_string_append(security_str, "WPA2 ");
-    }
-    if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE) {
-        g_string_append(security_str, "WPA3 ");
-    }
-    if (NM_FLAGS_ANY(rsn_flags, NM_802_11_AP_SEC_KEY_MGMT_OWE | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)) {
-        g_string_append(security_str, "OWE ");
-    }
-    if ((wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)
-        || (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) {
-        g_string_append(security_str, "802.1X ");
+    } else {
+        if (wpa_flags != NM_802_11_AP_SEC_NONE) {
+            g_string_append(security_str, "WPA1 ");
+        }
+        if ((rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)
+            || (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) {
+            g_string_append(security_str, "WPA2 ");
+        }
+        if (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE) {
+            g_string_append(security_str, "WPA3 ");
+        }
+        if (NM_FLAGS_ANY(rsn_flags,
+                         NM_802_11_AP_SEC_KEY_MGMT_OWE | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)) {
+            g_string_append(security_str, "OWE ");
+        }
+        if ((wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)
+            || (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) {
+            g_string_append(security_str, "802.1X ");
+        }
     }
 
     if (security_str->len > 0)
@@ -1348,7 +1359,7 @@ fill_output_access_point(gpointer data, gpointer user_data)
     arr = nmc_dup_fields_array((const NMMetaAbstractInfo *const *) nmc_fields_dev_wifi_list,
                                info->output_flags);
 
-    ap_name = g_strdup_printf("AP[%d]", info->index++); /* AP */
+    ap_name = g_strdup_printf("AP[%d]", ++(*info->p_index)); /* AP */
     set_val_str(arr, 0, ap_name);
     set_val_str(arr, 1, ssid_str);
     set_val_str(arr, 2, ssid_hex_str);
@@ -1374,6 +1385,8 @@ fill_output_access_point(gpointer data, gpointer user_data)
 
     /* Set colors */
     color = wifi_signal_to_color(strength);
+    if (_ap_is_wep(ap))
+        color = NM_META_COLOR_WIFI_DEPRECATED;
     set_val_color_all(arr, color);
     if (active)
         arr[15].color = NM_META_COLOR_CONNECTION_ACTIVATED;
@@ -1381,6 +1394,12 @@ fill_output_access_point(gpointer data, gpointer user_data)
     g_ptr_array_add(info->output_data, arr);
 }
 
+static void
+fill_output_access_point_void(gpointer data, gpointer user_data)
+{
+    fill_output_access_point(data, user_data);
+}
+
 static char *
 bluetooth_caps_to_string(NMBluetoothCapabilities caps)
 {
@@ -1679,18 +1698,14 @@ show_device_info(NMDevice *device, NmCli *nmc)
 
         /* Wireless specific information */
         if ((NM_IS_DEVICE_WIFI(device))) {
-            NMAccessPoint *active_ap    = NULL;
-            const char    *active_bssid = NULL;
-
             /* section AP */
             if (!g_ascii_strcasecmp(nmc_fields_dev_show_sections[section_idx]->name,
                                     nmc_fields_dev_show_sections[4]->name)) {
+                NMAccessPoint *active_ap = NULL;
                 NMC_OUTPUT_DATA_DEFINE_SCOPED(out);
 
-                if (state == NM_DEVICE_STATE_ACTIVATED) {
-                    active_ap    = nm_device_wifi_get_active_access_point(NM_DEVICE_WIFI(device));
-                    active_bssid = active_ap ? nm_access_point_get_bssid(active_ap) : NULL;
-                }
+                if (state == NM_DEVICE_STATE_ACTIVATED)
+                    active_ap = nm_device_wifi_get_active_access_point(NM_DEVICE_WIFI(device));
 
                 tmpl = (const NMMetaAbstractInfo *const *) nmc_fields_dev_wifi_list;
                 out_indices =
@@ -1703,19 +1718,20 @@ show_device_info(NMDevice *device, NmCli *nmc)
                 g_ptr_array_add(out.output_data, arr);
 
                 {
-                    gs_unref_ptrarray GPtrArray *aps  = NULL;
-                    APInfo                       info = {
+                    gs_unref_ptrarray GPtrArray *aps        = NULL;
+                    int                          info_index = 0;
+                    const APInfo                 info       = {
                                               .nmc          = nmc,
-                                              .index        = 1,
+                                              .p_index      = &info_index,
                                               .output_flags = NMC_OF_FLAG_SECTION_PREFIX,
-                                              .active_bssid = active_bssid,
+                                              .active_ap    = active_ap,
                                               .device       = nm_device_get_iface(device),
                                               .output_data  = out.output_data,
                     };
 
                     aps = sort_access_points(
                         nm_device_wifi_get_access_points(NM_DEVICE_WIFI(device)));
-                    g_ptr_array_foreach(aps, fill_output_access_point, &info);
+                    g_ptr_array_foreach(aps, fill_output_access_point_void, (gpointer) &info);
                 }
 
                 print_data_prepare_width(out.output_data);
@@ -2991,32 +3007,30 @@ find_ap_on_device(NMDevice *device, const char *bssid, const char *ssid, gboolea
 static void
 show_access_point_info(NMDeviceWifi *wifi, NmCli *nmc, NmcOutputData *out)
 {
-    NMAccessPoint  *active_ap    = NULL;
-    const char     *active_bssid = NULL;
+    NMAccessPoint  *active_ap = NULL;
     NmcOutputField *arr;
 
-    if (nm_device_get_state(NM_DEVICE(wifi)) == NM_DEVICE_STATE_ACTIVATED) {
-        active_ap    = nm_device_wifi_get_active_access_point(wifi);
-        active_bssid = active_ap ? nm_access_point_get_bssid(active_ap) : NULL;
-    }
+    if (nm_device_get_state(NM_DEVICE(wifi)) == NM_DEVICE_STATE_ACTIVATED)
+        active_ap = nm_device_wifi_get_active_access_point(wifi);
 
     arr = nmc_dup_fields_array((const NMMetaAbstractInfo *const *) nmc_fields_dev_wifi_list,
                                NMC_OF_FLAG_MAIN_HEADER_ADD | NMC_OF_FLAG_FIELD_NAMES);
     g_ptr_array_add(out->output_data, arr);
 
     {
-        gs_unref_ptrarray GPtrArray *aps  = NULL;
-        APInfo                       info = {
+        gs_unref_ptrarray GPtrArray *aps        = NULL;
+        int                          info_index = 0;
+        const APInfo                 info       = {
                                   .nmc          = nmc,
-                                  .index        = 1,
+                                  .p_index      = &info_index,
                                   .output_flags = 0,
-                                  .active_bssid = active_bssid,
+                                  .active_ap    = active_ap,
                                   .device       = nm_device_get_iface(NM_DEVICE(wifi)),
                                   .output_data  = out->output_data,
         };
 
         aps = sort_access_points(nm_device_wifi_get_access_points(wifi));
-        g_ptr_array_foreach(aps, fill_output_access_point, &info);
+        g_ptr_array_foreach(aps, fill_output_access_point_void, (gpointer) &info);
     }
 
     print_data_prepare_width(out->output_data);
@@ -3032,7 +3046,6 @@ wifi_print_aps(NMDeviceWifi                    *wifi,
 {
     NMAccessPoint   *ap = NULL;
     const GPtrArray *aps;
-    APInfo          *info;
     guint            i;
     NmcOutputField  *arr;
     const char      *base_hdr = _("Wi-Fi scan list");
@@ -3061,23 +3074,23 @@ wifi_print_aps(NMDeviceWifi                    *wifi,
                 ap = candidate_ap;
         }
         if (ap) {
+            int          info_index = 0;
+            const APInfo info       = {
+                      .nmc          = nmc,
+                      .p_index      = &info_index,
+                      .output_flags = 0,
+                      .device       = nm_device_get_iface(NM_DEVICE(wifi)),
+                      .output_data  = out.output_data,
+            };
+
             /* Add headers (field names) */
             arr = nmc_dup_fields_array(tmpl, NMC_OF_FLAG_MAIN_HEADER_ADD | NMC_OF_FLAG_FIELD_NAMES);
             g_ptr_array_add(out.output_data, arr);
 
-            info               = g_malloc0(sizeof(APInfo));
-            info->nmc          = nmc;
-            info->index        = 1;
-            info->output_flags = 0;
-            info->active_bssid = NULL;
-            info->device       = nm_device_get_iface(NM_DEVICE(wifi));
-            info->output_data  = out.output_data;
-
-            fill_output_access_point(ap, info);
+            fill_output_access_point(ap, &info);
 
             print_data_prepare_width(out.output_data);
             print_data(&nmc->nmc_config, &nmc->pager_data, out_indices, header_name, 0, &out);
-            g_free(info);
 
             *bssid_found = TRUE;
             empty_line   = TRUE;
diff --git a/src/nmcli/general.c b/src/nmcli/general.c
index 65437e79..d5a6788f 100644
--- a/src/nmcli/general.c
+++ b/src/nmcli/general.c
@@ -145,12 +145,22 @@ _metagen_general_status_get_fcn(NMC_META_GENERIC_INFO_GET_FCN_ARGS)
         v_bool = nm_client_networking_get_enabled(nmc->client);
         goto enabled_out;
     case NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WIFI_HW:
+        if (!(nm_client_get_radio_flags(nmc->client) & NM_RADIO_FLAG_WLAN_AVAILABLE)) {
+            NMC_HANDLE_COLOR(NM_META_COLOR_NONE);
+            value = N_("missing");
+            goto translate_and_out;
+        }
         v_bool = nm_client_wireless_hardware_get_enabled(nmc->client);
         goto enabled_out;
     case NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WIFI:
         v_bool = nm_client_wireless_get_enabled(nmc->client);
         goto enabled_out;
     case NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WWAN_HW:
+        if (!(nm_client_get_radio_flags(nmc->client) & NM_RADIO_FLAG_WWAN_AVAILABLE)) {
+            NMC_HANDLE_COLOR(NM_META_COLOR_NONE);
+            value = N_("missing");
+            goto translate_and_out;
+        }
         v_bool = nm_client_wwan_hardware_get_enabled(nmc->client);
         goto enabled_out;
     case NMC_GENERIC_INFO_TYPE_GENERAL_STATUS_WWAN:
diff --git a/src/nmcli/generate-docs-nm-settings-nmcli.xml b/src/nmcli/generate-docs-nm-settings-nmcli.xml
index 1d329305..525b36b6 100644
--- a/src/nmcli/generate-docs-nm-settings-nmcli.xml
+++ b/src/nmcli/generate-docs-nm-settings-nmcli.xml
@@ -371,7 +371,7 @@
         <property name="uuid"
                   description="A universally unique identifier for the connection, for example generated with libuuid.  It should be assigned when the connection is created, and never changed as long as the connection still applies to the same network.  For example, it should not be changed when the &quot;id&quot; property or NMSettingIP4Config changes, but might need to be re-created when the Wi-Fi SSID, mobile broadband network provider, or &quot;type&quot; property changes. The UUID must be in the format &quot;2815492f-7e56-435e-b2e9-246bd7cdc664&quot; (ie, contains only hexadecimal characters and &quot;-&quot;)." />
         <property name="stable-id"
-                  description="This represents the identity of the connection used for various purposes. It allows to configure multiple profiles to share the identity. Also, the stable-id can contain placeholders that are substituted dynamically and deterministically depending on the context. The stable-id is used for generating IPv6 stable private addresses with ipv6.addr-gen-mode=stable-privacy. It is also used to seed the generated cloned MAC address for ethernet.cloned-mac-address=stable and wifi.cloned-mac-address=stable. It is also used as DHCP client identifier with ipv4.dhcp-client-id=stable and to derive the DHCP DUID with ipv6.dhcp-duid=stable-[llt,ll,uuid]. Note that depending on the context where it is used, other parameters are also seeded into the generation algorithm. For example, a per-host key is commonly also included, so that different systems end up generating different IDs. Or with ipv6.addr-gen-mode=stable-privacy, also the device&apos;s name is included, so that different interfaces yield different addresses. The per-host key is the identity of your machine and stored in /var/lib/NetworkManager/secret-key. The &apos;$&apos; character is treated special to perform dynamic substitutions at runtime. Currently, supported are &quot;${CONNECTION}&quot;, &quot;${DEVICE}&quot;, &quot;${MAC}&quot;, &quot;${BOOT}&quot;, &quot;${RANDOM}&quot;. These effectively create unique IDs per-connection, per-device, per-boot, or every time. Note that &quot;${DEVICE}&quot; corresponds to the interface name of the device and &quot;${MAC}&quot; is the permanent MAC address of the device. Any unrecognized patterns following &apos;$&apos; are treated verbatim, however are reserved for future use. You are thus advised to avoid &apos;$&apos; or escape it as &quot;$$&quot;. For example, set it to &quot;${CONNECTION}-${BOOT}-${DEVICE}&quot; to create a unique id for this connection that changes with every reboot and differs depending on the interface where the profile activates. If the value is unset, a global connection default is consulted. If the value is still unset, the default is similar to &quot;${CONNECTION}&quot; and uses a unique, fixed ID for the connection." />
+                  description="This represents the identity of the connection used for various purposes. It allows to configure multiple profiles to share the identity. Also, the stable-id can contain placeholders that are substituted dynamically and deterministically depending on the context. The stable-id is used for generating IPv6 stable private addresses with ipv6.addr-gen-mode=stable-privacy. It is also used to seed the generated cloned MAC address for ethernet.cloned-mac-address=stable and wifi.cloned-mac-address=stable. It is also used as DHCP client identifier with ipv4.dhcp-client-id=stable and to derive the DHCP DUID with ipv6.dhcp-duid=stable-[llt,ll,uuid]. Note that depending on the context where it is used, other parameters are also seeded into the generation algorithm. For example, a per-host key is commonly also included, so that different systems end up generating different IDs. Or with ipv6.addr-gen-mode=stable-privacy, also the device&apos;s name is included, so that different interfaces yield different addresses. The per-host key is the identity of your machine and stored in /var/lib/NetworkManager/secret_key. See NetworkManager(8) manual about the secret-key and the host identity. The &apos;$&apos; character is treated special to perform dynamic substitutions at runtime. Currently, supported are &quot;${CONNECTION}&quot;, &quot;${DEVICE}&quot;, &quot;${MAC}&quot;, &quot;${BOOT}&quot;, &quot;${RANDOM}&quot;. These effectively create unique IDs per-connection, per-device, per-boot, or every time. Note that &quot;${DEVICE}&quot; corresponds to the interface name of the device and &quot;${MAC}&quot; is the permanent MAC address of the device. Any unrecognized patterns following &apos;$&apos; are treated verbatim, however are reserved for future use. You are thus advised to avoid &apos;$&apos; or escape it as &quot;$$&quot;. For example, set it to &quot;${CONNECTION}-${BOOT}-${DEVICE}&quot; to create a unique id for this connection that changes with every reboot and differs depending on the interface where the profile activates. If the value is unset, a global connection default is consulted. If the value is still unset, the default is similar to &quot;${CONNECTION}&quot; and uses a unique, fixed ID for the connection." />
         <property name="type"
                   alias="type"
                   description="Base type of the connection. For hardware-dependent connections, should contain the setting name of the hardware-type specific setting (ie, &quot;802-3-ethernet&quot; or &quot;802-11-wireless&quot; or &quot;bluetooth&quot;, etc), and for non-hardware dependent connections like VPN or otherwise, should contain the setting name of that setting type (ie, &quot;vpn&quot; or &quot;bridge&quot;, etc)." />
@@ -380,7 +380,7 @@
                   description="The name of the network interface this connection is bound to. If not set, then the connection can be attached to any interface of the appropriate type (subject to restrictions imposed by other settings). For software devices this specifies the name of the created device. For connection types where interface names cannot easily be made persistent (e.g. mobile broadband or USB Ethernet), this property should not be used. Setting this property restricts the interfaces a connection can be used with, and if interface names change or are reordered the connection may be applied to the wrong interface." />
         <property name="autoconnect"
                   alias="autoconnect"
-                  description="Whether or not the connection should be automatically connected by NetworkManager when the resources for the connection are available. TRUE to automatically activate the connection, FALSE to require manual intervention to activate the connection. Autoconnect happens when the circumstances are suitable. That means for example that the device is currently managed and not active. Autoconnect thus never replaces or competes with an already active profile. Note that autoconnect is not implemented for VPN profiles. See &quot;secondaries&quot; as an alternative to automatically connect VPN profiles." />
+                  description="Whether or not the connection should be automatically connected by NetworkManager when the resources for the connection are available. TRUE to automatically activate the connection, FALSE to require manual intervention to activate the connection. Autoconnect happens when the circumstances are suitable. That means for example that the device is currently managed and not active. Autoconnect thus never replaces or competes with an already active profile. Note that autoconnect is not implemented for VPN profiles. See &quot;secondaries&quot; as an alternative to automatically connect VPN profiles. If multiple profiles are ready to autoconnect on the same device, the one with the better &quot;connection.autoconnect-priority&quot; is chosen. If the priorities are equal, then the most recently connected profile is activated. If the profiles were not connected earlier or their &quot;connection.timestamp&quot; is identical, the choice is undefined. Depending on &quot;connection.multi-connect&quot;, a profile can (auto)connect only once at a time or multiple times." />
         <property name="autoconnect-priority"
                   description="The autoconnect priority in range -999 to 999. If the connection is set to autoconnect, connections with higher priority will be preferred. The higher number means higher priority. Defaults to 0. Note that this property only matters if there are more than one candidate profile to select for autoconnect. In case of equal priority, the profile used most recently is chosen." />
         <property name="autoconnect-retries"
@@ -712,7 +712,7 @@
                   description="DNS servers priority. The relative priority for DNS servers specified by this setting.  A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections.  It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the &quot;rotate&quot; option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain.  Queries for domains not present in any search list are routed through connections having the &apos;~.&apos; special wildcard domain, which is added automatically to connections with the default route (or can be added manually).  When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins.  If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured." />
         <property name="addresses"
                   alias="ip6"
-                  description="A list of IPv6 addresses and their prefix length. Multiple addresses can be separated by comma. For example &quot;2001:db8:85a3::8a2e:370:7334/64, 2001:db8:85a3::5/64&quot;. The addresses are listed in increasing priority, meaning the last address will be the primary address." />
+                  description="A list of IPv6 addresses and their prefix length. Multiple addresses can be separated by comma. For example &quot;2001:db8:85a3::8a2e:370:7334/64, 2001:db8:85a3::5/64&quot;. The addresses are listed in decreasing priority, meaning the first address will be the primary address. This can make a difference with IPv6 source address selection (RFC 6724, section 5)." />
         <property name="gateway"
                   alias="gw6"
                   description="The gateway associated with this configuration. This is only meaningful if &quot;addresses&quot; is also set. The gateway&apos;s main purpose is to control the next hop of the standard default route on the device. Hence, the gateway property conflicts with &quot;never-default&quot; and will be automatically dropped if the IP configuration is set to never-default. As an alternative to set the gateway, configure a static default route with /0 as prefix length." />
@@ -911,7 +911,7 @@
                   description="PAC URL for obtaining PAC file." />
         <property name="pac-script"
                   alias="pac-script"
-                  description="PAC script for the connection." />
+                  description="The PAC script. In the profile this must be an UTF-8 encoded javascript code that defines a FindProxyForURL() function. When setting the property in nmcli, a filename is accepted too. In that case, nmcli will read the content of the file and set the script. The prefixes &quot;file://&quot; and &quot;js://&quot; are supported to explicitly differentiate between the two." />
     </setting>
     <setting name="serial" >
         <property name="baud"
diff --git a/src/nmcli/generate-docs-nm-settings-nmcli.xml.in b/src/nmcli/generate-docs-nm-settings-nmcli.xml.in
index 1d329305..525b36b6 100644
--- a/src/nmcli/generate-docs-nm-settings-nmcli.xml.in
+++ b/src/nmcli/generate-docs-nm-settings-nmcli.xml.in
@@ -371,7 +371,7 @@
         <property name="uuid"
                   description="A universally unique identifier for the connection, for example generated with libuuid.  It should be assigned when the connection is created, and never changed as long as the connection still applies to the same network.  For example, it should not be changed when the &quot;id&quot; property or NMSettingIP4Config changes, but might need to be re-created when the Wi-Fi SSID, mobile broadband network provider, or &quot;type&quot; property changes. The UUID must be in the format &quot;2815492f-7e56-435e-b2e9-246bd7cdc664&quot; (ie, contains only hexadecimal characters and &quot;-&quot;)." />
         <property name="stable-id"
-                  description="This represents the identity of the connection used for various purposes. It allows to configure multiple profiles to share the identity. Also, the stable-id can contain placeholders that are substituted dynamically and deterministically depending on the context. The stable-id is used for generating IPv6 stable private addresses with ipv6.addr-gen-mode=stable-privacy. It is also used to seed the generated cloned MAC address for ethernet.cloned-mac-address=stable and wifi.cloned-mac-address=stable. It is also used as DHCP client identifier with ipv4.dhcp-client-id=stable and to derive the DHCP DUID with ipv6.dhcp-duid=stable-[llt,ll,uuid]. Note that depending on the context where it is used, other parameters are also seeded into the generation algorithm. For example, a per-host key is commonly also included, so that different systems end up generating different IDs. Or with ipv6.addr-gen-mode=stable-privacy, also the device&apos;s name is included, so that different interfaces yield different addresses. The per-host key is the identity of your machine and stored in /var/lib/NetworkManager/secret-key. The &apos;$&apos; character is treated special to perform dynamic substitutions at runtime. Currently, supported are &quot;${CONNECTION}&quot;, &quot;${DEVICE}&quot;, &quot;${MAC}&quot;, &quot;${BOOT}&quot;, &quot;${RANDOM}&quot;. These effectively create unique IDs per-connection, per-device, per-boot, or every time. Note that &quot;${DEVICE}&quot; corresponds to the interface name of the device and &quot;${MAC}&quot; is the permanent MAC address of the device. Any unrecognized patterns following &apos;$&apos; are treated verbatim, however are reserved for future use. You are thus advised to avoid &apos;$&apos; or escape it as &quot;$$&quot;. For example, set it to &quot;${CONNECTION}-${BOOT}-${DEVICE}&quot; to create a unique id for this connection that changes with every reboot and differs depending on the interface where the profile activates. If the value is unset, a global connection default is consulted. If the value is still unset, the default is similar to &quot;${CONNECTION}&quot; and uses a unique, fixed ID for the connection." />
+                  description="This represents the identity of the connection used for various purposes. It allows to configure multiple profiles to share the identity. Also, the stable-id can contain placeholders that are substituted dynamically and deterministically depending on the context. The stable-id is used for generating IPv6 stable private addresses with ipv6.addr-gen-mode=stable-privacy. It is also used to seed the generated cloned MAC address for ethernet.cloned-mac-address=stable and wifi.cloned-mac-address=stable. It is also used as DHCP client identifier with ipv4.dhcp-client-id=stable and to derive the DHCP DUID with ipv6.dhcp-duid=stable-[llt,ll,uuid]. Note that depending on the context where it is used, other parameters are also seeded into the generation algorithm. For example, a per-host key is commonly also included, so that different systems end up generating different IDs. Or with ipv6.addr-gen-mode=stable-privacy, also the device&apos;s name is included, so that different interfaces yield different addresses. The per-host key is the identity of your machine and stored in /var/lib/NetworkManager/secret_key. See NetworkManager(8) manual about the secret-key and the host identity. The &apos;$&apos; character is treated special to perform dynamic substitutions at runtime. Currently, supported are &quot;${CONNECTION}&quot;, &quot;${DEVICE}&quot;, &quot;${MAC}&quot;, &quot;${BOOT}&quot;, &quot;${RANDOM}&quot;. These effectively create unique IDs per-connection, per-device, per-boot, or every time. Note that &quot;${DEVICE}&quot; corresponds to the interface name of the device and &quot;${MAC}&quot; is the permanent MAC address of the device. Any unrecognized patterns following &apos;$&apos; are treated verbatim, however are reserved for future use. You are thus advised to avoid &apos;$&apos; or escape it as &quot;$$&quot;. For example, set it to &quot;${CONNECTION}-${BOOT}-${DEVICE}&quot; to create a unique id for this connection that changes with every reboot and differs depending on the interface where the profile activates. If the value is unset, a global connection default is consulted. If the value is still unset, the default is similar to &quot;${CONNECTION}&quot; and uses a unique, fixed ID for the connection." />
         <property name="type"
                   alias="type"
                   description="Base type of the connection. For hardware-dependent connections, should contain the setting name of the hardware-type specific setting (ie, &quot;802-3-ethernet&quot; or &quot;802-11-wireless&quot; or &quot;bluetooth&quot;, etc), and for non-hardware dependent connections like VPN or otherwise, should contain the setting name of that setting type (ie, &quot;vpn&quot; or &quot;bridge&quot;, etc)." />
@@ -380,7 +380,7 @@
                   description="The name of the network interface this connection is bound to. If not set, then the connection can be attached to any interface of the appropriate type (subject to restrictions imposed by other settings). For software devices this specifies the name of the created device. For connection types where interface names cannot easily be made persistent (e.g. mobile broadband or USB Ethernet), this property should not be used. Setting this property restricts the interfaces a connection can be used with, and if interface names change or are reordered the connection may be applied to the wrong interface." />
         <property name="autoconnect"
                   alias="autoconnect"
-                  description="Whether or not the connection should be automatically connected by NetworkManager when the resources for the connection are available. TRUE to automatically activate the connection, FALSE to require manual intervention to activate the connection. Autoconnect happens when the circumstances are suitable. That means for example that the device is currently managed and not active. Autoconnect thus never replaces or competes with an already active profile. Note that autoconnect is not implemented for VPN profiles. See &quot;secondaries&quot; as an alternative to automatically connect VPN profiles." />
+                  description="Whether or not the connection should be automatically connected by NetworkManager when the resources for the connection are available. TRUE to automatically activate the connection, FALSE to require manual intervention to activate the connection. Autoconnect happens when the circumstances are suitable. That means for example that the device is currently managed and not active. Autoconnect thus never replaces or competes with an already active profile. Note that autoconnect is not implemented for VPN profiles. See &quot;secondaries&quot; as an alternative to automatically connect VPN profiles. If multiple profiles are ready to autoconnect on the same device, the one with the better &quot;connection.autoconnect-priority&quot; is chosen. If the priorities are equal, then the most recently connected profile is activated. If the profiles were not connected earlier or their &quot;connection.timestamp&quot; is identical, the choice is undefined. Depending on &quot;connection.multi-connect&quot;, a profile can (auto)connect only once at a time or multiple times." />
         <property name="autoconnect-priority"
                   description="The autoconnect priority in range -999 to 999. If the connection is set to autoconnect, connections with higher priority will be preferred. The higher number means higher priority. Defaults to 0. Note that this property only matters if there are more than one candidate profile to select for autoconnect. In case of equal priority, the profile used most recently is chosen." />
         <property name="autoconnect-retries"
@@ -712,7 +712,7 @@
                   description="DNS servers priority. The relative priority for DNS servers specified by this setting.  A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections.  It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the &quot;rotate&quot; option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain.  Queries for domains not present in any search list are routed through connections having the &apos;~.&apos; special wildcard domain, which is added automatically to connections with the default route (or can be added manually).  When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins.  If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured." />
         <property name="addresses"
                   alias="ip6"
-                  description="A list of IPv6 addresses and their prefix length. Multiple addresses can be separated by comma. For example &quot;2001:db8:85a3::8a2e:370:7334/64, 2001:db8:85a3::5/64&quot;. The addresses are listed in increasing priority, meaning the last address will be the primary address." />
+                  description="A list of IPv6 addresses and their prefix length. Multiple addresses can be separated by comma. For example &quot;2001:db8:85a3::8a2e:370:7334/64, 2001:db8:85a3::5/64&quot;. The addresses are listed in decreasing priority, meaning the first address will be the primary address. This can make a difference with IPv6 source address selection (RFC 6724, section 5)." />
         <property name="gateway"
                   alias="gw6"
                   description="The gateway associated with this configuration. This is only meaningful if &quot;addresses&quot; is also set. The gateway&apos;s main purpose is to control the next hop of the standard default route on the device. Hence, the gateway property conflicts with &quot;never-default&quot; and will be automatically dropped if the IP configuration is set to never-default. As an alternative to set the gateway, configure a static default route with /0 as prefix length." />
@@ -911,7 +911,7 @@
                   description="PAC URL for obtaining PAC file." />
         <property name="pac-script"
                   alias="pac-script"
-                  description="PAC script for the connection." />
+                  description="The PAC script. In the profile this must be an UTF-8 encoded javascript code that defines a FindProxyForURL() function. When setting the property in nmcli, a filename is accepted too. In that case, nmcli will read the content of the file and set the script. The prefixes &quot;file://&quot; and &quot;js://&quot; are supported to explicitly differentiate between the two." />
     </setting>
     <setting name="serial" >
         <property name="baud"
diff --git a/src/nmcli/nmcli.c b/src/nmcli/nmcli.c
index fae40584..96b8ec4a 100644
--- a/src/nmcli/nmcli.c
+++ b/src/nmcli/nmcli.c
@@ -11,7 +11,6 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <signal.h>
-#include <termios.h>
 #include <unistd.h>
 #include <locale.h>
 #if HAVE_EDITLINE_READLINE
@@ -44,6 +43,7 @@
             [NM_META_COLOR_CONNECTION_DISCONNECTING] = "31",   \
             [NM_META_COLOR_CONNECTION_INVISIBLE]     = "2",    \
             [NM_META_COLOR_CONNECTION_EXTERNAL]      = "32;2", \
+            [NM_META_COLOR_CONNECTION_DEPRECATED]    = "2",    \
             [NM_META_COLOR_CONNECTIVITY_FULL]        = "32",   \
             [NM_META_COLOR_CONNECTIVITY_LIMITED]     = "33",   \
             [NM_META_COLOR_CONNECTIVITY_NONE]        = "31",   \
@@ -74,6 +74,7 @@
             [NM_META_COLOR_WIFI_SIGNAL_GOOD]         = "33",   \
             [NM_META_COLOR_WIFI_SIGNAL_POOR]         = "36",   \
             [NM_META_COLOR_WIFI_SIGNAL_UNKNOWN]      = "2",    \
+            [NM_META_COLOR_WIFI_DEPRECATED]          = "2",    \
             [NM_META_COLOR_ENABLED]                  = "32",   \
             [NM_META_COLOR_DISABLED]                 = "31",   \
         },                                                     \
@@ -118,8 +119,7 @@ typedef struct {
 } ArgsInfo;
 
 /* --- Global variables --- */
-GMainLoop     *loop = NULL;
-struct termios termios_orig;
+GMainLoop *loop = NULL;
 
 NM_CACHED_QUARK_FCN("nmcli-error-quark", nmcli_error_quark);
 
@@ -551,6 +551,7 @@ static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
     {"connection-external", NM_META_COLOR_CONNECTION_EXTERNAL},
     {"connection-invisible", NM_META_COLOR_CONNECTION_INVISIBLE},
     {"connection-unknown", NM_META_COLOR_CONNECTION_UNKNOWN},
+    {"connection-deprecated", NM_META_COLOR_CONNECTION_DEPRECATED},
     {"connectivity-full", NM_META_COLOR_CONNECTIVITY_FULL},
     {"connectivity-limited", NM_META_COLOR_CONNECTIVITY_LIMITED},
     {"connectivity-none", NM_META_COLOR_CONNECTIVITY_NONE},
@@ -587,7 +588,8 @@ static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
     {"wifi-signal-fair", NM_META_COLOR_WIFI_SIGNAL_FAIR},
     {"wifi-signal-good", NM_META_COLOR_WIFI_SIGNAL_GOOD},
     {"wifi-signal-poor", NM_META_COLOR_WIFI_SIGNAL_POOR},
-    {"wifi-signal-unknown", NM_META_COLOR_WIFI_SIGNAL_UNKNOWN}, );
+    {"wifi-signal-unknown", NM_META_COLOR_WIFI_SIGNAL_UNKNOWN},
+    {"wifi-deprecated", NM_META_COLOR_WIFI_DEPRECATED}, );
 
 static gboolean
 parse_color_scheme(char *palette_buffer, NmcColorPalette *out_palette, GError **error)
@@ -935,7 +937,6 @@ nmc_clear_sigint(void)
 void
 nmc_exit(void)
 {
-    tcsetattr(STDIN_FILENO, TCSADRAIN, &termios_orig);
     nmc_cleanup_readline();
     exit(1);
 }
@@ -1026,9 +1027,6 @@ main(int argc, char *argv[])
     textdomain(GETTEXT_PACKAGE);
 #endif
 
-    /* Save terminal settings */
-    tcgetattr(STDIN_FILENO, &termios_orig);
-
     nm_cli.return_text = g_string_new(_("Success"));
     loop               = g_main_loop_new(NULL, FALSE);
 
diff --git a/src/nmcli/utils.c b/src/nmcli/utils.c
index 7645a08c..209a5b69 100644
--- a/src/nmcli/utils.c
+++ b/src/nmcli/utils.c
@@ -156,7 +156,7 @@ next_arg(NmCli *nmc, int *argc, const char *const **argv, ...)
     va_list     args;
     const char *cmd_option;
 
-    g_assert(*argc >= 0);
+    g_return_val_if_fail(*argc >= 0, -1);
 
     do {
         int cmd_option_pos = 1;
@@ -1229,7 +1229,7 @@ _print_do(const NmcConfig           *nmc_config,
     guint                         i_row, i_col;
     nm_auto_free_gstring GString *str = NULL;
 
-    g_assert(col_len);
+    g_return_if_fail(col_len);
 
     /* Main header */
     if (nmc_config->print_output == NMC_PRINT_PRETTY && header_name_no_l10n) {
@@ -1628,7 +1628,7 @@ print_required_fields(const NmcConfig      *nmc_config,
             gboolean is_array = field_values[idx].value_is_array;
 
             /* section prefix can't be an array */
-            g_assert(!is_array || !section_prefix || idx != 0);
+            g_return_if_fail(!is_array || !section_prefix || idx != 0);
 
             if (section_prefix && idx == 0) /* The first field is section prefix */
                 continue;