connections.c 361 KB
Newer Older
1
/* SPDX-License-Identifier: GPL-2.0-or-later */
2
/*
3
 * Copyright (C) 2010 - 2018 Red Hat, Inc.
4
5
 */

6
#include "libnm-client-aux-extern/nm-default-client.h"
7

8
9
#include "connections.h"

10
11
#include <stdio.h>
#include <stdlib.h>
12
#include <unistd.h>
13
#include <signal.h>
14
15
16
#if HAVE_EDITLINE_READLINE
#include <editline/readline.h>
#else
17
18
#include <readline/readline.h>
#include <readline/history.h>
19
#endif
20
#include <fcntl.h>
21

22
#include "libnm-glib-aux/nm-dbus-aux.h"
23
24
25
26
#include "libnmc-base/nm-client-utils.h"
#include "libnmc-base/nm-vpn-helpers.h"
#include "libnmc-setting/nm-meta-setting-access.h"
#include "libnmc-base/nm-secret-agent-simple.h"
27

28
#include "utils.h"
29
#include "common.h"
30
#include "settings.h"
31
#include "devices.h"
32
#include "polkit-agent.h"
33

34
35
/*****************************************************************************/

Thomas Haller's avatar
Thomas Haller committed
36
typedef enum {
37
38
39
40
41
    PROPERTY_INF_FLAG_NONE     = 0x0,
    PROPERTY_INF_FLAG_DISABLED = 0x1, /* Don't ask due to runtime decision. */
    PROPERTY_INF_FLAG_ENABLED =
        0x2, /* Override NM_META_PROPERTY_INF_FLAG_DONT_ASK due to runtime decision. */
    PROPERTY_INF_FLAG_ALL = 0x3,
Thomas Haller's avatar
Thomas Haller committed
42
43
} PropertyInfFlags;

44
typedef char *(*CompEntryFunc)(const char *, int);
45

46
typedef struct _OptionInfo {
47
48
49
50
51
52
53
54
55
    const NMMetaSettingInfoEditor *setting_info;
    const char *                   property;
    const char *                   option;
    gboolean (*check_and_set)(NmCli *                   nmc,
                              NMConnection *            connection,
                              const struct _OptionInfo *option,
                              const char *              value,
                              GError **                 error);
    CompEntryFunc generator_func;
56
} OptionInfo;
57

58
59
60
61
62
/* define some prompts for connection editor */
#define EDITOR_PROMPT_SETTING  _("Setting name? ")
#define EDITOR_PROMPT_PROPERTY _("Property name? ")
#define EDITOR_PROMPT_CON_TYPE _("Enter connection type: ")

63
/* define some other prompts */
64

65
66
67
#define PROMPT_CONNECTION         _("Connection (name, UUID, or path): ")
#define PROMPT_VPN_CONNECTION     _("VPN connection (name, UUID, or path): ")
#define PROMPT_CONNECTIONS        _("Connection(s) (name, UUID, or path): ")
68
#define PROMPT_ACTIVE_CONNECTIONS _("Connection(s) (name, UUID, path or apath): ")
69

70
71
#define BASE_PROMPT "nmcli> "

72
73
/*****************************************************************************/

74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
static NM_UTILS_LOOKUP_STR_DEFINE(
    active_connection_state_to_string,
    NMActiveConnectionState,
    NM_UTILS_LOOKUP_DEFAULT(N_("unknown")),
    NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_ACTIVATING, N_("activating")),
    NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_ACTIVATED, N_("activated")),
    NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, N_("deactivating")),
    NM_UTILS_LOOKUP_ITEM(NM_ACTIVE_CONNECTION_STATE_DEACTIVATED, N_("deactivated")),
    NM_UTILS_LOOKUP_ITEM_IGNORE(NM_ACTIVE_CONNECTION_STATE_UNKNOWN), );

static NM_UTILS_LOOKUP_STR_DEFINE(
    vpn_connection_state_to_string,
    NMVpnConnectionState,
    NM_UTILS_LOOKUP_DEFAULT(N_("unknown")),
    NM_UTILS_LOOKUP_ITEM(NM_VPN_CONNECTION_STATE_PREPARE, N_("VPN connecting (prepare)")),
    NM_UTILS_LOOKUP_ITEM(NM_VPN_CONNECTION_STATE_NEED_AUTH,
                         N_("VPN connecting (need authentication)")),
    NM_UTILS_LOOKUP_ITEM(NM_VPN_CONNECTION_STATE_CONNECT, N_("VPN connecting")),
    NM_UTILS_LOOKUP_ITEM(NM_VPN_CONNECTION_STATE_IP_CONFIG_GET,
                         N_("VPN connecting (getting IP configuration)")),
    NM_UTILS_LOOKUP_ITEM(NM_VPN_CONNECTION_STATE_ACTIVATED, N_("VPN connected")),
    NM_UTILS_LOOKUP_ITEM(NM_VPN_CONNECTION_STATE_FAILED, N_("VPN connection failed")),
    NM_UTILS_LOOKUP_ITEM(NM_VPN_CONNECTION_STATE_DISCONNECTED, N_("VPN disconnected")),
    NM_UTILS_LOOKUP_ITEM_IGNORE(NM_VPN_CONNECTION_STATE_UNKNOWN), );
Thomas Haller's avatar
Thomas Haller committed
98

99
100
101
/*****************************************************************************/

typedef struct {
102
103
104
105
    NmCli *nmc;
    char * orig_id;
    char * orig_uuid;
    char * new_id;
106
107
108
} AddConnectionInfo;

static AddConnectionInfo *
109
_add_connection_info_new(NmCli *nmc, NMConnection *orig_connection, NMConnection *new_connection)
110
{
111
    AddConnectionInfo *info;
112

113
114
115
116
117
118
119
120
    info  = g_slice_new(AddConnectionInfo);
    *info = (AddConnectionInfo){
        .nmc       = nmc,
        .orig_id   = orig_connection ? g_strdup(nm_connection_get_id(orig_connection)) : NULL,
        .orig_uuid = orig_connection ? g_strdup(nm_connection_get_uuid(orig_connection)) : NULL,
        .new_id    = g_strdup(nm_connection_get_id(new_connection)),
    };
    return info;
121
122
123
}

static void
124
_add_connection_info_free(AddConnectionInfo *info)
125
{
126
127
128
129
    g_free(info->orig_id);
    g_free(info->orig_uuid);
    g_free(info->new_id);
    nm_g_slice_free(info);
130
131
}

132
133
134
NM_AUTO_DEFINE_FCN(AddConnectionInfo *,
                   _nm_auto_free_add_connection_info,
                   _add_connection_info_free);
135

136
#define nm_auto_free_add_connection_info nm_auto(_nm_auto_free_add_connection_info)
137
138
139

/*****************************************************************************/

Thomas Haller's avatar
Thomas Haller committed
140
141
142
143
/* Essentially a version of nm_setting_connection_get_connection_type() that
 * prefers an alias instead of the settings name when in pretty print mode.
 * That is so that we print "wifi" instead of "802-11-wireless" in "nmcli c". */
static const char *
144
connection_type_to_display(const char *type, NMMetaAccessorGetType get_type)
Thomas Haller's avatar
Thomas Haller committed
145
{
146
147
    const NMMetaSettingInfoEditor *editor;
    int                            i;
Thomas Haller's avatar
Thomas Haller committed
148

149
150
    nm_assert(
        NM_IN_SET(get_type, NM_META_ACCESSOR_GET_TYPE_PRETTY, NM_META_ACCESSOR_GET_TYPE_PARSABLE));
151

152
153
    if (!type)
        return NULL;
154

155
156
    if (get_type != NM_META_ACCESSOR_GET_TYPE_PRETTY)
        return type;
Thomas Haller's avatar
Thomas Haller committed
157

158
159
160
161
162
163
    for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) {
        editor = &nm_meta_setting_infos_editor[i];
        if (nm_streq(type, editor->general->setting_name))
            return editor->alias ?: type;
    }
    return type;
Thomas Haller's avatar
Thomas Haller committed
164
165
}

166
static int
167
active_connection_get_state_ord(NMActiveConnection *active)
168
{
169
    /* returns an integer related to @active's state, that can be used for sorting
170
     * active connections based on their activation state. */
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
    if (!active)
        return -2;

    switch (nm_active_connection_get_state(active)) {
    case NM_ACTIVE_CONNECTION_STATE_UNKNOWN:
        return 0;
    case NM_ACTIVE_CONNECTION_STATE_DEACTIVATED:
        return 1;
    case NM_ACTIVE_CONNECTION_STATE_DEACTIVATING:
        return 2;
    case NM_ACTIVE_CONNECTION_STATE_ACTIVATING:
        return 3;
    case NM_ACTIVE_CONNECTION_STATE_ACTIVATED:
        return 4;
    }
    return -1;
187
188
}

189
int
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
nmc_active_connection_cmp(NMActiveConnection *ac_a, NMActiveConnection *ac_b)
{
    NMSettingIPConfig * s_ip;
    NMRemoteConnection *conn;
    NMIPConfig *        da_ip;
    NMIPConfig *        db_ip;
    int                 da_num_addrs;
    int                 db_num_addrs;
    int                 cmp = 0;

    /* Non-active sort last. */
    NM_CMP_SELF(ac_a, ac_b);
    NM_CMP_DIRECT(active_connection_get_state_ord(ac_b), active_connection_get_state_ord(ac_a));

    /* Shared connections (likely hotspots) go on the top if possible */
    conn = nm_active_connection_get_connection(ac_a);
    s_ip = conn ? nm_connection_get_setting_ip6_config(NM_CONNECTION(conn)) : NULL;
    if (s_ip
208
        && nm_streq(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP6_CONFIG_METHOD_SHARED))
209
210
211
212
        cmp++;
    conn = nm_active_connection_get_connection(ac_b);
    s_ip = conn ? nm_connection_get_setting_ip6_config(NM_CONNECTION(conn)) : NULL;
    if (s_ip
213
        && nm_streq(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP6_CONFIG_METHOD_SHARED))
214
215
216
217
218
219
        cmp--;
    NM_CMP_RETURN(cmp);

    conn = nm_active_connection_get_connection(ac_a);
    s_ip = conn ? nm_connection_get_setting_ip4_config(NM_CONNECTION(conn)) : NULL;
    if (s_ip
220
        && nm_streq(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP4_CONFIG_METHOD_SHARED))
221
222
223
224
        cmp++;
    conn = nm_active_connection_get_connection(ac_b);
    s_ip = conn ? nm_connection_get_setting_ip4_config(NM_CONNECTION(conn)) : NULL;
    if (s_ip
225
        && nm_streq(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP4_CONFIG_METHOD_SHARED))
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
        cmp--;
    NM_CMP_RETURN(cmp);

    /* VPNs go next */
    NM_CMP_DIRECT(!!nm_active_connection_get_vpn(ac_a), !!nm_active_connection_get_vpn(ac_b));

    /* Default devices are prioritized */
    NM_CMP_DIRECT(nm_active_connection_get_default(ac_a), nm_active_connection_get_default(ac_b));

    /* Default IPv6 devices are prioritized */
    NM_CMP_DIRECT(nm_active_connection_get_default6(ac_a), nm_active_connection_get_default6(ac_b));

    /* Sort by number of addresses. */
    da_ip        = nm_active_connection_get_ip4_config(ac_a);
    da_num_addrs = da_ip ? nm_ip_config_get_addresses(da_ip)->len : 0;
    db_ip        = nm_active_connection_get_ip4_config(ac_b);
    db_num_addrs = db_ip ? nm_ip_config_get_addresses(db_ip)->len : 0;

    da_ip = nm_active_connection_get_ip6_config(ac_a);
    da_num_addrs += da_ip ? nm_ip_config_get_addresses(da_ip)->len : 0;
    db_ip = nm_active_connection_get_ip6_config(ac_b);
    db_num_addrs += db_ip ? nm_ip_config_get_addresses(db_ip)->len : 0;

    NM_CMP_DIRECT(da_num_addrs, db_num_addrs);

    return 0;
252
253
}

Thomas Haller's avatar
Thomas Haller committed
254
static char *
255
get_ac_device_string(NMActiveConnection *active)
Thomas Haller's avatar
Thomas Haller committed
256
{
257
258
259
    GString *        dev_str;
    const GPtrArray *devices;
    guint            i;
Thomas Haller's avatar
Thomas Haller committed
260

261
262
    if (!active)
        return NULL;
Thomas Haller's avatar
Thomas Haller committed
263

264
265
266
267
268
269
    /* Get devices of the active connection */
    dev_str = g_string_new(NULL);
    devices = nm_active_connection_get_devices(active);
    for (i = 0; i < devices->len; i++) {
        NMDevice *  device    = g_ptr_array_index(devices, i);
        const char *dev_iface = nm_device_get_iface(device);
Thomas Haller's avatar
Thomas Haller committed
270

271
272
273
274
275
276
277
        if (dev_iface) {
            g_string_append(dev_str, dev_iface);
            g_string_append_c(dev_str, ',');
        }
    }
    if (dev_str->len > 0)
        g_string_truncate(dev_str, dev_str->len - 1); /* Cut off last ',' */
Thomas Haller's avatar
Thomas Haller committed
278

279
    return g_string_free(dev_str, FALSE);
Thomas Haller's avatar
Thomas Haller committed
280
281
282
283
}

/*****************************************************************************/

Thomas Haller's avatar
Thomas Haller committed
284
285
286
/* FIXME: The same or similar code for VPN info appears also in nm-applet (applet-dialogs.c),
 * and in gnome-control-center as well. It could probably be shared somehow. */

287
static const char *
288
get_vpn_connection_type(NMConnection *connection)
Thomas Haller's avatar
Thomas Haller committed
289
{
290
291
    NMSettingVpn *s_vpn;
    const char *  type, *p;
Thomas Haller's avatar
Thomas Haller committed
292

293
294
295
    s_vpn = nm_connection_get_setting_vpn(connection);
    if (!s_vpn)
        return NULL;
296

297
    /* The service type is in form of "org.freedesktop.NetworkManager.vpnc".
298
299
     * Extract end part after last dot, e.g. "vpnc"
     */
300
301
302
303
304
    type = nm_setting_vpn_get_service_type(nm_connection_get_setting_vpn(connection));
    if (!type)
        return NULL;
    p = strrchr(type, '.');
    return p ? p + 1 : type;
Thomas Haller's avatar
Thomas Haller committed
305
306
307
308
309
310
311
312
313
314
}

/* VPN parameters can be found at:
 * http://git.gnome.org/browse/network-manager-openvpn/tree/src/nm-openvpn-service.h
 * http://git.gnome.org/browse/network-manager-vpnc/tree/src/nm-vpnc-service.h
 * http://git.gnome.org/browse/network-manager-pptp/tree/src/nm-pptp-service.h
 * http://git.gnome.org/browse/network-manager-openconnect/tree/src/nm-openconnect-service.h
 * http://git.gnome.org/browse/network-manager-openswan/tree/src/nm-openswan-service.h
 * See also 'properties' directory in these plugins.
 */
315
static const char *
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
find_vpn_gateway_key(const char *vpn_type)
{
    if (vpn_type) {
        if (nm_streq(vpn_type, "openvpn"))
            return "remote";
        if (nm_streq(vpn_type, "vpnc"))
            return "IPSec gateway";
        if (nm_streq(vpn_type, "pptp"))
            return "gateway";
        if (nm_streq(vpn_type, "openconnect"))
            return "gateway";
        if (nm_streq(vpn_type, "openswan"))
            return "right";
        if (nm_streq(vpn_type, "libreswan"))
            return "right";
        if (nm_streq(vpn_type, "ssh"))
            return "remote";
        if (nm_streq(vpn_type, "l2tp"))
            return "gateway";
    }
    return NULL;
Thomas Haller's avatar
Thomas Haller committed
337
338
}

339
static const char *
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
find_vpn_username_key(const char *vpn_type)
{
    if (vpn_type) {
        if (nm_streq(vpn_type, "openvpn"))
            return "username";
        if (nm_streq(vpn_type, "vpnc"))
            return "Xauth username";
        if (nm_streq(vpn_type, "pptp"))
            return "user";
        if (nm_streq(vpn_type, "openconnect"))
            return "username";
        if (nm_streq(vpn_type, "openswan"))
            return "leftxauthusername";
        if (nm_streq(vpn_type, "libreswan"))
            return "leftxauthusername";
        if (nm_streq(vpn_type, "l2tp"))
            return "user";
    }
    return NULL;
}

enum VpnDataItem { VPN_DATA_ITEM_GATEWAY, VPN_DATA_ITEM_USERNAME };
Thomas Haller's avatar
Thomas Haller committed
362

363
static const char *
364
get_vpn_data_item(NMConnection *connection, enum VpnDataItem vpn_data_item)
Thomas Haller's avatar
Thomas Haller committed
365
{
366
367
    const char *type;
    const char *key = NULL;
Thomas Haller's avatar
Thomas Haller committed
368

369
    type = get_vpn_connection_type(connection);
Thomas Haller's avatar
Thomas Haller committed
370

371
372
373
374
375
376
377
378
379
380
    switch (vpn_data_item) {
    case VPN_DATA_ITEM_GATEWAY:
        key = find_vpn_gateway_key(type);
        break;
    case VPN_DATA_ITEM_USERNAME:
        key = find_vpn_username_key(type);
        break;
    default:
        break;
    }
Thomas Haller's avatar
Thomas Haller committed
381

382
383
384
    if (!key)
        return NULL;
    return nm_setting_vpn_get_data_item(nm_connection_get_setting_vpn(connection), key);
Thomas Haller's avatar
Thomas Haller committed
385
386
387
388
}

/*****************************************************************************/

389
typedef struct {
390
391
392
393
    NMConnection *      connection;
    NMActiveConnection *primary_active;
    GPtrArray *         all_active;
    bool                show_active_fields;
394
395
396
} MetagenConShowRowData;

static MetagenConShowRowData *
397
398
_metagen_con_show_row_data_new_for_connection(NMRemoteConnection *connection,
                                              gboolean            show_active_fields)
399
{
400
    MetagenConShowRowData *row_data;
401

402
403
404
405
    row_data                     = g_slice_new0(MetagenConShowRowData);
    row_data->connection         = g_object_ref(NM_CONNECTION(connection));
    row_data->show_active_fields = show_active_fields;
    return row_data;
406
407
408
}

static MetagenConShowRowData *
409
410
411
_metagen_con_show_row_data_new_for_active_connection(NMRemoteConnection *connection,
                                                     NMActiveConnection *active,
                                                     gboolean            show_active_fields)
412
{
413
    MetagenConShowRowData *row_data;
414

415
416
417
418
419
420
    row_data = g_slice_new0(MetagenConShowRowData);
    if (connection)
        row_data->connection = g_object_ref(NM_CONNECTION(connection));
    row_data->primary_active     = g_object_ref(active);
    row_data->show_active_fields = show_active_fields;
    return row_data;
421
422
423
}

static void
424
425
_metagen_con_show_row_data_add_active_connection(MetagenConShowRowData *row_data,
                                                 NMActiveConnection *   active)
426
{
427
428
429
430
431
432
433
434
435
    if (!row_data->primary_active) {
        row_data->primary_active = g_object_ref(active);
        return;
    }
    if (!row_data->all_active) {
        row_data->all_active = g_ptr_array_new_with_free_func(g_object_unref);
        g_ptr_array_add(row_data->all_active, g_object_ref(row_data->primary_active));
    }
    g_ptr_array_add(row_data->all_active, g_object_ref(active));
436
437
438
}

static void
439
_metagen_con_show_row_data_init_primary_active(MetagenConShowRowData *row_data)
440
{
441
442
    NMActiveConnection *ac, *best_ac;
    guint               i;
443

444
445
    if (!row_data->all_active)
        return;
446

447
448
449
    best_ac = row_data->all_active->pdata[0];
    for (i = 1; i < row_data->all_active->len; i++) {
        ac = row_data->all_active->pdata[i];
450

451
452
453
        if (active_connection_get_state_ord(ac) > active_connection_get_state_ord(best_ac))
            best_ac = ac;
    }
454

455
456
457
458
459
    if (row_data->primary_active != best_ac) {
        g_object_unref(row_data->primary_active);
        row_data->primary_active = g_object_ref(best_ac);
    }
    nm_clear_pointer(&row_data->all_active, g_ptr_array_unref);
460
461
462
}

static void
463
_metagen_con_show_row_data_destroy(gpointer data)
464
{
465
    MetagenConShowRowData *row_data = data;
466

467
468
    if (!row_data)
        return;
469

470
471
472
473
    g_clear_object(&row_data->connection);
    g_clear_object(&row_data->primary_active);
    nm_clear_pointer(&row_data->all_active, g_ptr_array_unref);
    g_slice_free(MetagenConShowRowData, row_data);
474
475
476
}

static const char *
477
_con_show_fcn_get_id(NMConnection *c, NMActiveConnection *ac)
478
{
479
480
    NMSettingConnection *s_con = NULL;
    const char *         s;
481

482
483
    if (c)
        s_con = nm_connection_get_setting_connection(c);
484

485
486
487
    s = s_con ? nm_setting_connection_get_id(s_con) : NULL;
    if (!s && ac) {
        /* note that if we have no s_con, that usually means that the user has no permissions
488
489
490
491
492
         * to see the connection. We still fall to get the ID from the active-connection,
         * which exposes it despite the user having no permissions.
         *
         * That might be unexpected, because the user is shown an ID, which he later
         * is unable to resolve in other operations. */
493
494
495
        s = nm_active_connection_get_id(ac);
    }
    return s;
496
497
498
}

static const char *
499
_con_show_fcn_get_type(NMConnection *c, NMActiveConnection *ac, NMMetaAccessorGetType get_type)
500
{
501
502
    NMSettingConnection *s_con = NULL;
    const char *         s;
503

504
505
    if (c)
        s_con = nm_connection_get_setting_connection(c);
506

507
508
509
    s = s_con ? nm_setting_connection_get_connection_type(s_con) : NULL;
    if (!s && ac) {
        /* see _con_show_fcn_get_id() for why we fallback to get the value
510
         * from @ac. */
511
512
513
        s = nm_active_connection_get_connection_type(ac);
    }
    return connection_type_to_display(s, get_type);
514
515
}

516
static gconstpointer _metagen_con_show_get_fcn(NMC_META_GENERIC_INFO_GET_FCN_ARGS)
517
{
518
519
520
521
522
523
    const MetagenConShowRowData *row_data = target;
    NMConnection *               c        = row_data->connection;
    NMActiveConnection *         ac       = row_data->primary_active;
    NMSettingConnection *        s_con    = NULL;
    const char *                 s;
    char *                       s_mut;
524

525
    NMC_HANDLE_COLOR(nmc_active_connection_state_to_color(ac));
526

527
528
    if (c)
        s_con = nm_connection_get_setting_connection(c);
529

530
531
    if (!row_data->show_active_fields) {
        /* we are not supposed to show any fields of the active connection.
532
533
         * We only tracked the primary_active to get the coloring right.
         * From now on, there is no active connection. */
534
        ac = NULL;
535

536
        /* in this mode, we expect that we are called only with connections that
537
538
539
540
541
         * have a [connection] setting and a UUID. Otherwise, the connection is
         * effectively invisible to the user, and should be hidden.
         *
         * But in that case, we expect that the caller pre-filtered this row out.
         * So assert(). */
542
543
544
545
546
547
548
549
550
551
552
553
554
555
        nm_assert(s_con);
        nm_assert(nm_setting_connection_get_uuid(s_con));
    }

    nm_assert(
        NM_IN_SET(get_type, NM_META_ACCESSOR_GET_TYPE_PRETTY, NM_META_ACCESSOR_GET_TYPE_PARSABLE));

    switch (info->info_type) {
    case NMC_GENERIC_INFO_TYPE_CON_SHOW_NAME:
        return _con_show_fcn_get_id(c, ac);
    case NMC_GENERIC_INFO_TYPE_CON_SHOW_UUID:
        s = s_con ? nm_setting_connection_get_uuid(s_con) : NULL;
        if (!s && ac) {
            /* see _con_show_fcn_get_id() for why we fallback to get the value
556
             * from @ac. */
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
            s = nm_active_connection_get_uuid(ac);
        }
        return s;
    case NMC_GENERIC_INFO_TYPE_CON_SHOW_TYPE:
        return _con_show_fcn_get_type(c, ac, get_type);
    case NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP:
    case NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP_REAL:
        if (!s_con)
            return NULL;
        {
            guint64 timestamp;
            time_t  timestamp_real;

            timestamp = nm_setting_connection_get_timestamp(s_con);

            if (info->info_type == NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP)
                return (*out_to_free = g_strdup_printf("%" G_GUINT64_FORMAT, timestamp));
            else {
                struct tm localtime_result;

                if (!timestamp) {
                    if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY)
                        return _("never");
                    return "never";
                }
                timestamp_real = timestamp;
                s_mut          = g_malloc0(128);
                strftime(s_mut, 127, "%c", localtime_r(&timestamp_real, &localtime_result));
                return (*out_to_free = s_mut);
            }
        }
    case NMC_GENERIC_INFO_TYPE_CON_SHOW_AUTOCONNECT:
        if (!s_con)
            return NULL;
        return nmc_meta_generic_get_bool(nm_setting_connection_get_autoconnect(s_con), get_type);
    case NMC_GENERIC_INFO_TYPE_CON_SHOW_AUTOCONNECT_PRIORITY:
        if (!s_con)
            return NULL;
        return (*out_to_free =
                    g_strdup_printf("%d", nm_setting_connection_get_autoconnect_priority(s_con)));
    case NMC_GENERIC_INFO_TYPE_CON_SHOW_READONLY:
        if (!s_con)
            return NULL;
        return nmc_meta_generic_get_bool(nm_setting_connection_get_read_only(s_con), get_type);
    case NMC_GENERIC_INFO_TYPE_CON_SHOW_DBUS_PATH:
        if (!c)
            return NULL;
        return nm_connection_get_path(c);
    case NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE:
        return nmc_meta_generic_get_bool(!!ac, get_type);
    case NMC_GENERIC_INFO_TYPE_CON_SHOW_DEVICE:
        if (ac)
            return (*out_to_free = get_ac_device_string(ac));
        return NULL;
    case NMC_GENERIC_INFO_TYPE_CON_SHOW_STATE:
        return nmc_meta_generic_get_str_i18n(
            ac ? active_connection_state_to_string(nm_active_connection_get_state(ac)) : NULL,
            get_type);
    case NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE_PATH:
        if (ac)
            return nm_object_get_path(NM_OBJECT(ac));
        return NULL;
    case NMC_GENERIC_INFO_TYPE_CON_SHOW_SLAVE:
        if (!s_con)
            return NULL;
        return nm_setting_connection_get_slave_type(s_con);
    case NMC_GENERIC_INFO_TYPE_CON_SHOW_FILENAME:
        if (!NM_IS_REMOTE_CONNECTION(c))
            return NULL;
        return nm_remote_connection_get_filename(NM_REMOTE_CONNECTION(c));
    default:
        break;
    }

    g_return_val_if_reached(NULL);
632
633
634
635
}

const NmcMetaGenericInfo *const metagen_con_show[_NMC_GENERIC_INFO_TYPE_CON_SHOW_NUM + 1] = {
#define _METAGEN_CON_SHOW(type, name) \
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
    [type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_con_show_get_fcn)
    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_NAME, "NAME"),
    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_UUID, "UUID"),
    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_TYPE, "TYPE"),
    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP, "TIMESTAMP"),
    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_TIMESTAMP_REAL, "TIMESTAMP-REAL"),
    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_AUTOCONNECT, "AUTOCONNECT"),
    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_AUTOCONNECT_PRIORITY, "AUTOCONNECT-PRIORITY"),
    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_READONLY, "READONLY"),
    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_DBUS_PATH, "DBUS-PATH"),
    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE, "ACTIVE"),
    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_DEVICE, "DEVICE"),
    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_STATE, "STATE"),
    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE_PATH, "ACTIVE-PATH"),
    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_SLAVE, "SLAVE"),
    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_FILENAME, "FILENAME"),
652
};
653
#define NMC_FIELDS_CON_SHOW_COMMON "NAME,UUID,TYPE,DEVICE"
654

655
656
/*****************************************************************************/

657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
static gconstpointer _metagen_con_active_general_get_fcn(NMC_META_GENERIC_INFO_GET_FCN_ARGS)
{
    NMActiveConnection * ac = target;
    NMConnection *       c;
    NMSettingConnection *s_con = NULL;
    NMDevice *           dev;
    guint                i;
    const char *         s;

    NMC_HANDLE_COLOR(NM_META_COLOR_NONE);

    nm_assert(
        NM_IN_SET(get_type, NM_META_ACCESSOR_GET_TYPE_PRETTY, NM_META_ACCESSOR_GET_TYPE_PARSABLE));

    c = NM_CONNECTION(nm_active_connection_get_connection(ac));
    if (c)
        s_con = nm_connection_get_setting_connection(c);

    switch (info->info_type) {
    case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_NAME:
        return nm_active_connection_get_id(ac);
    case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_UUID:
        return nm_active_connection_get_uuid(ac);
    case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEVICES:
    case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_IP_IFACE:
    {
        GString *        str = NULL;
        const GPtrArray *devices;

        s       = NULL;
        devices = nm_active_connection_get_devices(ac);
        if (devices) {
            for (i = 0; i < devices->len; i++) {
                NMDevice *  device = g_ptr_array_index(devices, i);
                const char *iface;

                if (info->info_type == NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEVICES) {
                    iface = nm_device_get_iface(device);
                } else {
                    iface = nm_device_get_ip_iface(device);
                }

                if (!iface)
                    continue;
                if (!s) {
                    s = iface;
                    continue;
                }
                if (!str)
                    str = g_string_new(s);
                g_string_append_c(str, ',');
                g_string_append(str, iface);
            }
        }
        if (str)
            return (*out_to_free = g_string_free(str, FALSE));
        return s;
    }
    case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_STATE:
        return nmc_meta_generic_get_str_i18n(
            active_connection_state_to_string(nm_active_connection_get_state(ac)),
            get_type);
    case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEFAULT:
        return nmc_meta_generic_get_bool(nm_active_connection_get_default(ac), get_type);
    case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEFAULT6:
        return nmc_meta_generic_get_bool(nm_active_connection_get_default6(ac), get_type);
    case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_SPEC_OBJECT:
        return nm_active_connection_get_specific_object_path(ac);
    case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_VPN:
        return nmc_meta_generic_get_bool(NM_IS_VPN_CONNECTION(ac), get_type);
    case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DBUS_PATH:
        return nm_object_get_path(NM_OBJECT(ac));
    case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_CON_PATH:
        return c ? nm_connection_get_path(c) : NULL;
    case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_ZONE:
        /* this is really ugly, because the zone is not a property of the active-connection,
733
         * but the settings-connection profile. There is no guarantee, that they agree. */
734
735
736
737
738
739
740
        return s_con ? nm_setting_connection_get_zone(s_con) : NULL;
    case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_MASTER_PATH:
        dev = nm_active_connection_get_master(ac);
        return dev ? nm_object_get_path(NM_OBJECT(dev)) : NULL;
    default:
        break;
    }
741

742
    g_return_val_if_reached(NULL);
743
744
}

745
746
const NmcMetaGenericInfo
    *const metagen_con_active_general[_NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_NUM + 1] = {
747
#define _METAGEN_CON_ACTIVE_GENERAL(type, name) \
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
    [type] =                                    \
        NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_con_active_general_get_fcn)
        _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_NAME, "NAME"),
        _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_UUID, "UUID"),
        _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEVICES, "DEVICES"),
        _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_IP_IFACE, "IP-IFACE"),
        _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_STATE, "STATE"),
        _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEFAULT, "DEFAULT"),
        _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DEFAULT6, "DEFAULT6"),
        _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_SPEC_OBJECT,
                                    "SPEC-OBJECT"),
        _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_VPN, "VPN"),
        _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DBUS_PATH,
                                    "DBUS-PATH"),
        _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_CON_PATH, "CON-PATH"),
        _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_ZONE, "ZONE"),
        _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_MASTER_PATH,
                                    "MASTER-PATH"),
766
};
767
768
769

/*****************************************************************************/

770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
static gconstpointer _metagen_con_active_vpn_get_fcn(NMC_META_GENERIC_INFO_GET_FCN_ARGS)
{
    NMActiveConnection * ac = target;
    NMConnection *       c;
    NMSettingVpn *       s_vpn = NULL;
    NMVpnConnectionState vpn_state;
    guint                i;
    const char *         s;
    char **              arr = NULL;

    nm_assert(NM_IS_VPN_CONNECTION(ac));

    NMC_HANDLE_COLOR(NM_META_COLOR_NONE);

    nm_assert(
        NM_IN_SET(get_type, NM_META_ACCESSOR_GET_TYPE_PRETTY, NM_META_ACCESSOR_GET_TYPE_PARSABLE));

    c = NM_CONNECTION(nm_active_connection_get_connection(ac));
    if (c)
        s_vpn = nm_connection_get_setting_vpn(c);

    switch (info->info_type) {
    case NMC_GENERIC_INFO_TYPE_CON_VPN_TYPE:
        return c ? get_vpn_connection_type(c) : NULL;
    case NMC_GENERIC_INFO_TYPE_CON_VPN_USERNAME:
        if (s_vpn && (s = nm_setting_vpn_get_user_name(s_vpn)))
            return s;
        return c ? get_vpn_data_item(c, VPN_DATA_ITEM_USERNAME) : NULL;
    case NMC_GENERIC_INFO_TYPE_CON_VPN_GATEWAY:
        return c ? get_vpn_data_item(c, VPN_DATA_ITEM_GATEWAY) : NULL;
    case NMC_GENERIC_INFO_TYPE_CON_VPN_BANNER:
        s = nm_vpn_connection_get_banner(NM_VPN_CONNECTION(ac));
        if (s)
            return (*out_to_free = g_strescape(s, ""));
        return NULL;
    case NMC_GENERIC_INFO_TYPE_CON_VPN_VPN_STATE:
        vpn_state = nm_vpn_connection_get_vpn_state(NM_VPN_CONNECTION(ac));
        return (*out_to_free =
                    nmc_meta_generic_get_enum_with_detail(NMC_META_GENERIC_GET_ENUM_TYPE_DASH,
                                                          vpn_state,
                                                          vpn_connection_state_to_string(vpn_state),
                                                          get_type));
    case NMC_GENERIC_INFO_TYPE_CON_VPN_CFG:
        if (!NM_FLAGS_HAS(get_flags, NM_META_ACCESSOR_GET_FLAGS_ACCEPT_STRV))
            return NULL;
        if (s_vpn) {
            gs_free char **arr2 = NULL;
            guint          n;

            arr2 = (char **) nm_setting_vpn_get_data_keys(s_vpn, &n);
            if (!n)
                goto arr_out;

            nm_assert(arr2 && !arr2[n]);
            for (i = 0; i < n; i++) {
                const char *k = arr2[i];
                const char *v;

                nm_assert(k);
                v = nm_setting_vpn_get_data_item(s_vpn, k);
                /* update the arr array in-place. Previously it contained
831
                 * the constant keys, now it contains the strdup'ed output text. */
832
833
                arr2[i] = g_strdup_printf("%s = %s", k, v);
            }
834

835
836
837
838
839
840
            arr = g_steal_pointer(&arr2);
        }
        goto arr_out;
    default:
        break;
    }
841

842
    g_return_val_if_reached(NULL);
843
844

arr_out:
845
846
847
848
    NM_SET_OUT(out_is_default, !arr || !arr[0]);
    *out_flags |= NM_META_ACCESSOR_GET_OUT_FLAGS_STRV;
    *out_to_free = arr;
    return arr;
849
850
}

851
852
const NmcMetaGenericInfo
    *const metagen_con_active_vpn[_NMC_GENERIC_INFO_TYPE_CON_ACTIVE_VPN_NUM + 1] = {
853
#define _METAGEN_CON_ACTIVE_VPN(type, name) \
854
855
856
857
858
859
860
    [type] = NMC_META_GENERIC(name, .info_type = type, .get_fcn = _metagen_con_active_vpn_get_fcn)
        _METAGEN_CON_ACTIVE_VPN(NMC_GENERIC_INFO_TYPE_CON_VPN_TYPE, "TYPE"),
        _METAGEN_CON_ACTIVE_VPN(NMC_GENERIC_INFO_TYPE_CON_VPN_USERNAME, "USERNAME"),
        _METAGEN_CON_ACTIVE_VPN(NMC_GENERIC_INFO_TYPE_CON_VPN_GATEWAY, "GATEWAY"),
        _METAGEN_CON_ACTIVE_VPN(NMC_GENERIC_INFO_TYPE_CON_VPN_BANNER, "BANNER"),
        _METAGEN_CON_ACTIVE_VPN(NMC_GENERIC_INFO_TYPE_CON_VPN_VPN_STATE, "VPN-STATE"),
        _METAGEN_CON_ACTIVE_VPN(NMC_GENERIC_INFO_TYPE_CON_VPN_CFG, "CFG"),
861
862
863
864
};

/*****************************************************************************/

865
866
867
868
869
870
871
872
873
874
875
876
#define NMC_FIELDS_SETTINGS_NAMES_ALL                                                  \
    NM_SETTING_CONNECTION_SETTING_NAME                                                 \
    "," NM_SETTING_MATCH_SETTING_NAME "," NM_SETTING_WIRED_SETTING_NAME                \
    "," NM_SETTING_VETH_SETTING_NAME "," NM_SETTING_802_1X_SETTING_NAME                \
    "," NM_SETTING_WIRELESS_SETTING_NAME "," NM_SETTING_WIRELESS_SECURITY_SETTING_NAME \
    "," NM_SETTING_IP4_CONFIG_SETTING_NAME "," NM_SETTING_IP6_CONFIG_SETTING_NAME      \
    "," NM_SETTING_SERIAL_SETTING_NAME "," NM_SETTING_WIFI_P2P_SETTING_NAME            \
    "," NM_SETTING_PPP_SETTING_NAME "," NM_SETTING_PPPOE_SETTING_NAME                  \
    "," NM_SETTING_ADSL_SETTING_NAME "," NM_SETTING_GSM_SETTING_NAME                   \
    "," NM_SETTING_CDMA_SETTING_NAME "," NM_SETTING_BLUETOOTH_SETTING_NAME             \
    "," NM_SETTING_OLPC_MESH_SETTING_NAME "," NM_SETTING_VPN_SETTING_NAME              \
    "," NM_SETTING_INFINIBAND_SETTING_NAME "," NM_SETTING_BOND_SETTING_NAME            \
877
878
879
880
881
882
883
884
885
886
887
888
889
    "," NM_SETTING_BOND_PORT_SETTING_NAME "," NM_SETTING_VLAN_SETTING_NAME             \
    "," NM_SETTING_BRIDGE_SETTING_NAME "," NM_SETTING_BRIDGE_PORT_SETTING_NAME         \
    "," NM_SETTING_TEAM_SETTING_NAME "," NM_SETTING_TEAM_PORT_SETTING_NAME             \
    "," NM_SETTING_OVS_BRIDGE_SETTING_NAME "," NM_SETTING_OVS_INTERFACE_SETTING_NAME   \
    "," NM_SETTING_OVS_PATCH_SETTING_NAME "," NM_SETTING_OVS_PORT_SETTING_NAME         \
    "," NM_SETTING_DCB_SETTING_NAME "," NM_SETTING_TUN_SETTING_NAME                    \
    "," NM_SETTING_IP_TUNNEL_SETTING_NAME "," NM_SETTING_MACSEC_SETTING_NAME           \
    "," NM_SETTING_MACVLAN_SETTING_NAME "," NM_SETTING_VXLAN_SETTING_NAME              \
    "," NM_SETTING_VRF_SETTING_NAME "," NM_SETTING_WPAN_SETTING_NAME                   \
    "," NM_SETTING_6LOWPAN_SETTING_NAME "," NM_SETTING_WIREGUARD_SETTING_NAME          \
    "," NM_SETTING_PROXY_SETTING_NAME "," NM_SETTING_TC_CONFIG_SETTING_NAME            \
    "," NM_SETTING_SRIOV_SETTING_NAME "," NM_SETTING_ETHTOOL_SETTING_NAME              \
    "," NM_SETTING_OVS_DPDK_SETTING_NAME                                               \
Beniamino Galvani's avatar
Beniamino Galvani committed
890
    "," NM_SETTING_HOSTNAME_SETTING_NAME /* NM_SETTING_DUMMY_SETTING_NAME NM_SETTING_WIMAX_SETTING_NAME */
891

892
const NmcMetaGenericInfo *const nmc_fields_con_active_details_groups[] = {
893
894
895
896
897
898
899
    NMC_META_GENERIC_WITH_NESTED("GENERAL", metagen_con_active_general), /* 0 */
    NMC_META_GENERIC_WITH_NESTED("IP4", metagen_ip4_config),             /* 1 */
    NMC_META_GENERIC_WITH_NESTED("DHCP4", metagen_dhcp_config),          /* 2 */
    NMC_META_GENERIC_WITH_NESTED("IP6", metagen_ip6_config),             /* 3 */
    NMC_META_GENERIC_WITH_NESTED("DHCP6", metagen_dhcp_config),          /* 4 */
    NMC_META_GENERIC_WITH_NESTED("VPN", metagen_con_active_vpn),         /* 5 */
    NULL,
900
901
};

902
903
904
905
906
/* Pseudo group names for 'connection show <con>' */
/* e.g.: nmcli -f profile con show my-eth0 */
/* e.g.: nmcli -f active con show my-eth0 */
#define CON_SHOW_DETAIL_GROUP_PROFILE "profile"
#define CON_SHOW_DETAIL_GROUP_ACTIVE  "active"
907

908
static guint progress_id = 0; /* ID of event source for displaying progress */
909

910
/* for readline TAB completion in editor */
911
typedef struct {
912
913
914
915
916
917
    NmCli *       nmc;
    char *        con_type;
    NMConnection *connection;
    NMSetting *   setting;
    const char *  property;
    char **       words;
918
} TabCompletionInfo;
919
920
921
922

static TabCompletionInfo nmc_tab_completion;

/*****************************************************************************/
923

924
static void
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
usage(void)
{
    g_printerr(
        _("Usage: nmcli connection { COMMAND | help }\n\n"
          "COMMAND := { show | up | down | add | modify | clone | edit | delete | monitor | reload "
          "| load | import | export }\n\n"
          "  show [--active] [--order <order spec>]\n"
          "  show [--active] [id | uuid | path | apath] <ID> ...\n\n"
          "  up [[id | uuid | path] <ID>] [ifname <ifname>] [ap <BSSID>] [passwd-file <file with "
          "passwords>]\n\n"
          "  down [id | uuid | path | apath] <ID> ...\n\n"
          "  add COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS SLAVE_OPTIONS IP_OPTIONS [-- "
          "([+|-]<setting>.<property> <value>)+]\n\n"
          "  modify [--temporary] [id | uuid | path] <ID> ([+|-]<setting>.<property> <value>)+\n\n"
          "  clone [--temporary] [id | uuid | path ] <ID> <new name>\n\n"
          "  edit [id | uuid | path] <ID>\n"
          "  edit [type <new_con_type>] [con-name <new_con_name>]\n\n"
          "  delete [id | uuid | path] <ID>\n\n"
          "  monitor [id | uuid | path] <ID> ...\n\n"
          "  reload\n\n"
          "  load <filename> [ <filename>... ]\n\n"
          "  import [--temporary] type <type> file <file to import>\n\n"
          "  export [id | uuid | path] <ID> [<output file>]\n\n"));
948
949
950
}

static void
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
usage_connection_show(void)
{
    g_printerr(
        _("Usage: nmcli connection show { ARGUMENTS | help }\n"
          "\n"
          "ARGUMENTS := [--active] [--order <order spec>]\n"
          "\n"
          "List in-memory and on-disk connection profiles, some of which may also be\n"
          "active if a device is using that connection profile. Without a parameter, all\n"
          "profiles are listed. When --active option is specified, only the active\n"
          "profiles are shown. --order allows custom connection ordering (see manual page).\n"
          "\n"
          "ARGUMENTS := [--active] [id | uuid | path | apath] <ID> ...\n"
          "\n"
          "Show details for specified connections. By default, both static configuration\n"
          "and active connection data are displayed. It is possible to filter the output\n"
          "using global '--fields' option. Refer to the manual page for more information.\n"
          "When --active option is specified, only the active profiles are taken into\n"
          "account. Use global --show-secrets option to reveal associated secrets as well.\n"));
970
971
972
}

static void
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
usage_connection_up(void)
{
    g_printerr(_("Usage: nmcli connection up { ARGUMENTS | help }\n"
                 "\n"
                 "ARGUMENTS := [id | uuid | path] <ID> [ifname <ifname>] [ap <BSSID>] [nsp <name>] "
                 "[passwd-file <file with passwords>]\n"
                 "\n"
                 "Activate a connection on a device. The profile to activate is identified by its\n"
                 "name, UUID or D-Bus path.\n"
                 "\n"
                 "ARGUMENTS := ifname <ifname> [ap <BSSID>] [nsp <name>] [passwd-file <file with "
                 "passwords>]\n"
                 "\n"
                 "Activate a device with a connection. The connection profile is selected\n"
                 "automatically by NetworkManager.\n"
                 "\n"
                 "ifname      - specifies the device to active the connection on\n"
                 "ap          - specifies AP to connect to (only valid for Wi-Fi)\n"
                 "nsp         - specifies NSP to connect to (only valid for WiMAX)\n"
                 "passwd-file - file with password(s) required to activate the connection\n\n"));
993
994
995
}

static void
996
usage_connection_down(void)
997
{
998
999
1000
1001
1002
1003
1004
    g_printerr(_("Usage: nmcli connection down { ARGUMENTS | help }\n"
                 "\n"
                 "ARGUMENTS := [id | uuid | path | apath] <ID> ...\n"
                 "\n"
                 "Deactivate a connection from a device (without preventing the device from\n"
                 "further auto-activation). The profile to deactivate is identified by its name,\n"
                 "UUID or D-Bus path.\n\n"));
1005
1006
1007
}

static void
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
usage_connection_add(void)
{
    g_printerr(_("Usage: nmcli connection add { ARGUMENTS | help }\n"
                 "\n"
                 "ARGUMENTS := COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS SLAVE_OPTIONS IP_OPTIONS [-- "
                 "([+|-]<setting>.<property> <value>)+]\n\n"
                 "  COMMON_OPTIONS:\n"
                 "                  type <type>\n"
                 "                  ifname <interface name> | \"*\"\n"
                 "                  [con-name <connection name>]\n"
                 "                  [autoconnect yes|no]\n"
                 "                  [save yes|no]\n"
                 "                  [master <master (ifname, or connection UUID or name)>]\n"
                 "                  [slave-type <master connection type>]\n\n"
                 "  TYPE_SPECIFIC_OPTIONS:\n"
                 "    ethernet:     [mac <MAC address>]\n"
                 "                  [cloned-mac <cloned MAC address>]\n"
                 "                  [mtu <MTU>]\n\n"
                 "    wifi:         ssid <SSID>\n"
                 "                  [mac <MAC address>]\n"
                 "                  [cloned-mac <cloned MAC address>]\n"
                 "                  [mtu <MTU>]\n"
                 "                  [mode infrastructure|ap|adhoc]\n\n"
                 "    wimax:        [mac <MAC address>]\n"
                 "                  [nsp <NSP>]\n\n"
                 "    pppoe:        username <PPPoE username>\n"
                 "                  [password <PPPoE password>]\n"
                 "                  [service <PPPoE service name>]\n"
                 "                  [mtu <MTU>]\n"
                 "                  [mac <MAC address>]\n\n"
                 "    gsm:          apn <APN>\n"
                 "                  [user <username>]\n"
                 "                  [password <password>]\n\n"
                 "    cdma:         [user <username>]\n"
                 "                  [password <password>]\n\n"
                 "    infiniband:   [mac <MAC address>]\n"
                 "                  [mtu <MTU>]\n"
                 "                  [transport-mode datagram | connected]\n"
                 "                  [parent <ifname>]\n"
                 "                  [p-key <IPoIB P_Key>]\n\n"
                 "    bluetooth:    [addr <bluetooth address>]\n"
                 "                  [bt-type panu|nap|dun-gsm|dun-cdma]\n\n"
                 "    vlan:         dev <parent device (connection UUID, ifname, or MAC)>\n"
                 "                  id <VLAN ID>\n"
                 "                  [flags <VLAN flags>]\n"
                 "                  [ingress <ingress priority mapping>]\n"
                 "                  [egress <egress priority mapping>]\n"
                 "                  [mtu <MTU>]\n\n"
                 "    bond:         [mode balance-rr (0) | active-backup (1) | balance-xor (2) | "
                 "broadcast (3) |\n"
                 "                        802.3ad    (4) | balance-tlb   (5) | balance-alb (6)]\n"
                 "                  [primary <ifname>]\n"
                 "                  [miimon <num>]\n"
                 "                  [downdelay <num>]\n"
                 "                  [updelay <num>]\n"
                 "                  [arp-interval <num>]\n"
                 "                  [arp-ip-target <num>]\n"
                 "                  [lacp-rate slow (0) | fast (1)]\n\n"
1066
1067
                 "    bond-slave:   master <master (ifname, or connection UUID or name)>\n"
                 "                  [queue-id <0-65535>]\n\n"
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
                 "    team:         [config <file>|<raw JSON data>]\n\n"
                 "    team-slave:   master <master (ifname, or connection UUID or name)>\n"
                 "                  [config <file>|<raw JSON data>]\n\n"
                 "    bridge:       [stp yes|no]\n"
                 "                  [priority <num>]\n"
                 "                  [forward-delay <2-30>]\n"
                 "                  [hello-time <1-10>]\n"
                 "                  [max-age <6-40>]\n"
                 "                  [ageing-time <0-1000000>]\n"
                 "                  [multicast-snooping yes|no]\n"
                 "                  [mac <MAC address>]\n\n"
                 "    bridge-slave: master <master (ifname, or connection UUID or name)>\n"
                 "                  [priority <0-63>]\n"
                 "                  [path-cost <1-65535>]\n"
                 "                  [hairpin yes|no]\n\n"
                 "    vpn:          vpn-type "
                 "vpnc|openvpn|pptp|openconnect|openswan|libreswan|ssh|l2tp|iodine|...\n"
                 "                  [user <username>]\n\n"
                 "    olpc-mesh:    ssid <SSID>\n"
                 "                  [channel <1-13>]\n"
                 "                  [dhcp-anycast <MAC address>]\n\n"
                 "    adsl:         username <username>\n"
                 "                  protocol pppoa|pppoe|ipoatm\n"
                 "                  [password <password>]\n"
                 "                  [encapsulation vcmux|llc]\n\n"
                 "    tun:          mode tun|tap\n"
                 "                  [owner <UID>]\n"
                 "                  [group <GID>]\n"
                 "                  [pi yes|no]\n"
                 "                  [vnet-hdr yes|no]\n"
                 "                  [multi-queue yes|no]\n\n"
                 "    ip-tunnel:    mode ipip|gre|sit|isatap|vti|ip6ip6|ipip6|ip6gre|vti6\n"
                 "                  remote <remote endpoint IP>\n"
                 "                  [local <local endpoint IP>]\n"
                 "                  [dev <parent device (ifname or connection UUID)>]\n\n"
                 "    macsec:       dev <parent device (connection UUID, ifname, or MAC)>\n"
                 "                  mode <psk|eap>\n"
                 "                  [cak <key> ckn <key>]\n"
                 "                  [encrypt yes|no]\n"
                 "                  [port 1-65534]\n\n\n"
                 "    macvlan:      dev <parent device (connection UUID, ifname, or MAC)>\n"
                 "                  mode vepa|bridge|private|passthru|source\n"
                 "                  [tap yes|no]\n\n"
                 "    vxlan:        id <VXLAN ID>\n"
1112
                 "                  [remote <IP of multicast group or remote address>]\n"
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
                 "                  [local <source IP>]\n"
                 "                  [dev <parent device (ifname or connection UUID)>]\n"
                 "                  [source-port-min <0-65535>]\n"
                 "                  [source-port-max <0-65535>]\n"
                 "                  [destination-port <0-65535>]\n\n"
                 "    wpan:         [short-addr <0x0000-0xffff>]\n"
                 "                  [pan-id <0x0000-0xffff>]\n"
                 "                  [page <default|0-31>]\n"
                 "                  [channel <default|0-26>]\n"
                 "                  [mac <MAC address>]\n\n"
                 "    6lowpan:      dev <parent device (connection UUID, ifname, or MAC)>\n"
                 "    dummy:\n\n"
                 "  SLAVE_OPTIONS:\n"
                 "    bridge:       [priority <0-63>]\n"
                 "                  [path-cost <1-65535>]\n"
                 "                  [hairpin yes|no]\n\n"
                 "    team:         [config <file>|<raw JSON data>]\n\n"
1130
                 "    bond:         [queue-id <0-65535>]\n\n"
1131
1132
1133
                 "  IP_OPTIONS:\n"
                 "                  [ip4 <IPv4 address>] [gw4 <IPv4 gateway>]\n"
                 "                  [ip6 <IPv6 address>] [gw6 <IPv6 gateway>]\n\n"));
1134
1135
1136
}

static void
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
usage_connection_modify(void)
{
    g_printerr(
        _("Usage: nmcli connection modify { ARGUMENTS | help }\n"
          "\n"
          "ARGUMENTS := [id | uuid | path] <ID> ([+|-]<setting>.<property> <value>)+\n"
          "\n"
          "Modify one or more properties of the connection profile.\n"
          "The profile is identified by its name, UUID or D-Bus path. For multi-valued\n"
          "properties you can use optional '+' or '-' prefix to the property name.\n"
          "The '+' sign allows appending items instead of overwriting the whole value.\n"
          "The '-' sign allows removing selected items instead of the whole value.\n"
          "\n"
          "ARGUMENTS := remove <setting>\n"
          "\n"
          "Remove a setting from the connection profile.\n"
          "\n"
          "Examples:\n"
          "nmcli con mod home-wifi wifi.ssid rakosnicek\n"
          "nmcli con mod em1-1 ipv4.method manual ipv4.addr \"192.168.1.2/24, 10.10.1.5/8\"\n"
          "nmcli con mod em1-1 +ipv4.dns 8.8.4.4\n"
          "nmcli con mod em1-1 -ipv4.dns 1\n"
          "nmcli con mod em1-1 -ipv6.addr \"abbe::cafe/56\"\n"
          "nmcli con mod bond0 +bond.options mii=500\n"
          "nmcli con mod bond0 -bond.options downdelay\n"
          "nmcli con mod em1-1 remove sriov\n\n"));
1163
1164
}

1165
static void
1166
usage_connection_clone(void)
1167
{
1168
1169
1170
1171
1172
1173
1174
    g_printerr(_("Usage: nmcli connection clone { ARGUMENTS | help }\n"
                 "\n"
                 "ARGUMENTS := [--temporary] [id | uuid | path] <ID> <new name>\n"
                 "\n"
                 "Clone an existing connection profile. The newly created connection will be\n"
                 "the exact copy of the <ID>, except the uuid property (will be generated) and\n"
                 "id (provided as <new name> argument).\n\n"));
1175
1176
}

1177
static void
1178
usage_connection_edit(void)
1179
{
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
    g_printerr(_("Usage: nmcli connection edit { ARGUMENTS | help }\n"
                 "\n"
                 "ARGUMENTS := [id | uuid | path] <ID>\n"
                 "\n"
                 "Edit an existing connection profile in an interactive editor.\n"
                 "The profile is identified by its name, UUID or D-Bus path\n"
                 "\n"
                 "ARGUMENTS := [type <new connection type>] [con-name <new connection name>]\n"
                 "\n"
                 "Add a new connection profile in an interactive editor.\n\n"));
1190
1191
1192
}

static void
1193
usage_connection_delete(void)
1194
{
1195
1196
1197
1198
1199
1200
    g_printerr(_("Usage: nmcli connection delete { ARGUMENTS | help }\n"
                 "\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"));
1201
1202
}

1203
static void
1204
usage_connection_monitor(void)
1205
{
1206
1207
1208
1209
1210
1211
1212
    g_printerr(_("Usage: nmcli connection monitor { ARGUMENTS | help }\n"
                 "\n"
                 "ARGUMENTS := [id | uuid | path] <ID> ...\n"
                 "\n"
                 "Monitor connection profile activity.\n"
                 "This command prints a line whenever the specified connection changes.\n"
                 "Monitors all connection profiles in case none is specified.\n\n"));
1213
1214
}

1215
static void
1216
usage_connection_reload(void)
1217
{
1218
1219
1220
    g_printerr(_("Usage: nmcli connection reload { help }\n"
                 "\n"
                 "Reload all connection files from disk.\n\n"));
1221
1222
1223
}

static void
1224
usage_connection_load(void)
1225
{
1226
1227
1228
1229
1230
1231
1232
    g_printerr(_("Usage: nmcli connection load { ARGUMENTS | help }\n"
                 "\n"
                 "ARGUMENTS := <filename> [<filename>...]\n"
                 "\n"
                 "Load/reload one or more connection files from disk. Use this after manually\n"
                 "editing a connection file to ensure that NetworkManager is aware of its latest\n"
                 "state.\n\n"));
1233
1234
}

1235
static void
1236
usage_connection_import(void)
1237
{
1238
1239
1240
1241
1242
1243
1244
1245
1246
    g_printerr(
        _("Usage: nmcli connection import { ARGUMENTS | help }\n"
          "\n"
          "ARGUMENTS := [--temporary] type <type> file <file to import>\n"
          "\n"
          "Import an external/foreign configuration as a NetworkManager connection profile.\n"
          "The type of the input file is specified by type option.\n"
          "Only VPN configurations are supported at the moment. The configuration\n"
          "is imported by NetworkManager VPN plugins.\n\n"));
1247
1248
}

1249
static void
1250
usage_connection_export(void)
1251
{
1252
1253
1254
1255
1256
1257
    g_printerr(_("Usage: nmcli connection export { ARGUMENTS | help }\n"
                 "\n"
                 "ARGUMENTS := [id | uuid | path] <ID> [<output file>]\n"
                 "\n"
                 "Export a connection. Only VPN connections are supported at the moment.\n"
                 "The data are directed to standard output or to a file if a name is given.\n\n"));
1258
1259
}

1260
static void
1261
quit(void)
1262
{
1263
1264
1265
    if (nm_clear_g_source(&progress_id))
        nmc_terminal_erase_line();
    g_main_loop_quit(loop);
1266
1267
}

1268
static char *
1269
construct_header_name(const char *base, const char *spec)
1270
{
1271
1272
    if (spec == NULL)
        return g_strdup(base);
1273

1274
    return g_strdup_printf("%s (%s)", base, spec);
1275
1276
}

1277
static int
1278
get_ac_for_connection_cmp(gconstpointer pa, gconstpointer pb)
1279
{
1280
1281
    NMActiveConnection *ac_a = *((NMActiveConnection *const *) pa);
    NMActiveConnection *ac_b = *((NMActiveConnection *const *) pb);
1282

1283
1284
1285
1286
1287
    NM_CMP_RETURN(nmc_active_connection_cmp(ac_a, ac_b));
    NM_CMP_DIRECT_STRCMP0(nm_active_connection_get_id(ac_a), nm_active_connection_get_id(ac_b));
    NM_CMP_DIRECT_STRCMP0(nm_active_connection_get_connection_type(ac_a),
                          nm_active_connection_get_connection_type(ac_b));
    NM_CMP_DIRECT_STRCMP0(nm_object_get_path(NM_OBJECT(ac_a)), nm_object_get_path(NM_OBJECT(ac_b)));
1288

1289
    g_return_val_if_reached(0);
1290
1291
}

1292
static NMActiveConnection *
1293
1294
1295
get_ac_for_connection(const GPtrArray *active_cons,
                      NMConnection *   connection,
                      GPtrArray **     out_result)
1296
{
1297
1298
1299
    guint               i;
    NMActiveConnection *best_candidate = NULL;
    GPtrArray *         result         = out_result ? *out_result : NULL;
1300

1301
1302
1303
    for (i = 0; i < active_cons->len; i++) {
        NMActiveConnection *candidate = g_ptr_array_index(active_cons, i);
        NMRemoteConnection *con;
1304

1305
1306
1307
        con = nm_active_connection_get_connection(candidate);
        if (NM_CONNECTION(con) != connection)
            continue;
1308

1309
1310
1311
1312
1313
1314
        if (!out_result)
            return candidate;
        if (!result)
            result = g_ptr_array_new_with_free_func(g_object_unref);
        g_ptr_array_add(result, g_object_ref(candidate));
    }
1315

1316
1317
1318
1319
    if (result) {
        g_ptr_array_sort(result, get_ac_for_connection_cmp);
        best_candidate = result->pdata[0];
    }
1320

1321
1322
    NM_SET_OUT(out_result, result);
    return best_candidate;
1323
1324
}

1325
typedef struct {
1326
1327
1328
    GMainLoop *   loop;
    NMConnection *local;
    const char *  setting_name;
1329
1330
1331
} GetSecretsData;

static void
1332
got_secrets(GObject *source_object, GAsyncResult *res, gpointer user_data)
1333
{
1334
1335
1336
    NMRemoteConnection *remote         = NM_REMOTE_CONNECTION(source_object);
    GetSecretsData *    data           = user_data;
    gs_unref_variant GVariant *secrets = NULL;
1337

1338
1339
1340
    secrets = nm_remote_connection_get_secrets_finish(remote, res, NULL);
    if (secrets) {
        gs_free_error GError *error = NULL;
1341

1342
1343
1344
1345
1346
1347
        if (!nm_connection_update_secrets(data->local, NULL, secrets, &error) && error) {
            g_printerr(_("Error updating secrets for %s: %s\n"),
                       data->setting_name,
                       error->message);
        }
    }
1348

1349
    g_main_loop_quit(data->loop);
1350
1351
}

1352
/* Put secrets into local connection. */