nm-hostname-manager.c 18.4 KB
Newer Older
1
/* SPDX-License-Identifier: GPL-2.0-or-later */
2
/*
3
 * Copyright (C) 2017 Red Hat, Inc.
4
5
 */

6
#include "src/core/nm-default-daemon.h"
7
8
9
10
11
12

#include "nm-hostname-manager.h"

#include <sys/stat.h>

#if HAVE_SELINUX
13
14
#include <selinux/selinux.h>
#include <selinux/label.h>
15
16
#endif

17
#include "libnm-core-aux-intern/nm-common-macros.h"
18
19
20
#include "nm-dbus-interface.h"
#include "nm-connection.h"
#include "nm-utils.h"
21
#include "libnm-core-intern/nm-core-internal.h"
22
23
24
25
26
27
28
29
30
31
32
33
34

#include "NetworkManagerUtils.h"

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

#define HOSTNAMED_SERVICE_NAME      "org.freedesktop.hostname1"
#define HOSTNAMED_SERVICE_PATH      "/org/freedesktop/hostname1"
#define HOSTNAMED_SERVICE_INTERFACE "org.freedesktop.hostname1"

#define HOSTNAME_FILE_DEFAULT        "/etc/hostname"
#define HOSTNAME_FILE_UCASE_HOSTNAME "/etc/HOSTNAME"
#define HOSTNAME_FILE_GENTOO         "/etc/conf.d/hostname"

35
#define CONF_DHCP SYSCONFDIR "/sysconfig/network/dhcp"
36

37
38
39
#if (defined(HOSTNAME_PERSIST_SUSE) + defined(HOSTNAME_PERSIST_SLACKWARE) \
     + defined(HOSTNAME_PERSIST_GENTOO))                                  \
    > 1
40
#error "Can only define one of HOSTNAME_PERSIST_*"
41
42
43
#endif

#if defined(HOSTNAME_PERSIST_SUSE)
44
#define HOSTNAME_FILE HOSTNAME_FILE_UCASE_HOSTNAME
45
#elif defined(HOSTNAME_PERSIST_SLACKWARE)
46
#define HOSTNAME_FILE HOSTNAME_FILE_UCASE_HOSTNAME
47
#elif defined(HOSTNAME_PERSIST_GENTOO)
48
#define HOSTNAME_FILE HOSTNAME_FILE_GENTOO
49
#else
50
#define HOSTNAME_FILE HOSTNAME_FILE_DEFAULT
51
52
53
54
#endif

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

55
NM_GOBJECT_PROPERTIES_DEFINE(NMHostnameManager, PROP_HOSTNAME, );
56
57

typedef struct {
58
59
60
61
62
63
    char *        current_hostname;
    GFileMonitor *monitor;
    GFileMonitor *dhcp_monitor;
    gulong        monitor_id;
    gulong        dhcp_monitor_id;
    GDBusProxy *  hostnamed_proxy;
64
65
66
} NMHostnameManagerPrivate;

struct _NMHostnameManager {
67
68
    GObject                  parent;
    NMHostnameManagerPrivate _priv;
69
70
71
};

struct _NMHostnameManagerClass {
72
    GObjectClass parent;
73
74
};

75
G_DEFINE_TYPE(NMHostnameManager, nm_hostname_manager, G_TYPE_OBJECT);
76

77
78
#define NM_HOSTNAME_MANAGER_GET_PRIVATE(self) \
    _NM_GET_PRIVATE(self, NMHostnameManager, NM_IS_HOSTNAME_MANAGER)
79

80
NM_DEFINE_SINGLETON_GETTER(NMHostnameManager, nm_hostname_manager_get, NM_TYPE_HOSTNAME_MANAGER);
81
82
83
84

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

#define _NMLOG_DOMAIN      LOGD_CORE
85
#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "hostname", __VA_ARGS__)
86
87
88

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

89
90
91
92
93
94
95
96
97
98
99
100
101
static inline GFileMonitor *
_file_monitor_new(const char *path)
{
    gs_unref_object GFile *file = NULL;

    nm_assert(path);

    file = g_file_new_for_path(path);
    return g_file_monitor_file(file, G_FILE_MONITOR_NONE, NULL, NULL);
}

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

102
#if defined(HOSTNAME_PERSIST_GENTOO)
103
static char *
104
read_hostname_gentoo(const char *path)
105
{
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
    gs_free char *     contents  = NULL;
    gs_strfreev char **all_lines = NULL;
    const char *       tmp;
    guint              i;

    if (!g_file_get_contents(path, &contents, NULL, NULL))
        return NULL;

    all_lines = g_strsplit(contents, "\n", 0);
    for (i = 0; all_lines[i]; i++) {
        g_strstrip(all_lines[i]);
        if (all_lines[i][0] == '#' || all_lines[i][0] == '\0')
            continue;
        if (g_str_has_prefix(all_lines[i], "hostname=")) {
            tmp = &all_lines[i][NM_STRLEN("hostname=")];
            return g_shell_unquote(tmp, NULL);
        }
    }
    return NULL;
125
126
127
128
}
#endif

#if defined(HOSTNAME_PERSIST_SLACKWARE)
129
static char *
130
read_hostname_slackware(const char *path)
131
{
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
    gs_free char *     contents  = NULL;
    gs_strfreev char **all_lines = NULL;
    guint              i         = 0;

    if (!g_file_get_contents(path, &contents, NULL, NULL))
        return NULL;

    all_lines = g_strsplit(contents, "\n", 0);
    for (i = 0; all_lines[i]; i++) {
        g_strstrip(all_lines[i]);
        if (all_lines[i][0] == '#' || all_lines[i][0] == '\0')
            continue;
        return g_shell_unquote(&all_lines[i][0], NULL);
    }
    return NULL;
147
148
149
150
151
}
#endif

#if defined(HOSTNAME_PERSIST_SUSE)
static gboolean
152
hostname_is_dynamic(void)
153
{
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
    GIOChannel *channel;
    char *      str     = NULL;
    gboolean    dynamic = FALSE;

    channel = g_io_channel_new_file(CONF_DHCP, "r", NULL);
    if (!channel)
        return dynamic;

    while (g_io_channel_read_line(channel, &str, NULL, NULL, NULL) != G_IO_STATUS_EOF) {
        if (str) {
            g_strstrip(str);
            if (g_str_has_prefix(str, "DHCLIENT_SET_HOSTNAME="))
                dynamic = strcmp(&str[NM_STRLEN("DHCLIENT_SET_HOSTNAME=")], "\"yes\"") == 0;
            g_free(str);
        }
    }

    g_io_channel_shutdown(channel, FALSE, NULL);
    g_io_channel_unref(channel);

    return dynamic;
175
176
177
178
179
}
#endif

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

180
const char *
181
nm_hostname_manager_get_hostname(NMHostnameManager *self)
182
{
183
184
    g_return_val_if_fail(NM_IS_HOSTNAME_MANAGER(self), NULL);
    return NM_HOSTNAME_MANAGER_GET_PRIVATE(self)->current_hostname;
185
186
187
}

static void
188
_set_hostname(NMHostnameManager *self, const char *hostname)
189
{
190
    NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
191
192
193
194
195
196
    char *                    old_hostname;

    hostname = nm_str_not_empty(hostname);

    if (nm_streq0(hostname, priv->current_hostname))
        return;
197

198
199
200
    _LOGI("hostname changed from %s%s%s to %s%s%s",
          NM_PRINT_FMT_QUOTED(priv->current_hostname, "\"", priv->current_hostname, "\"", "(none)"),
          NM_PRINT_FMT_QUOTED(hostname, "\"", hostname, "\"", "(none)"));
201

202
203
204
    old_hostname           = priv->current_hostname;
    priv->current_hostname = g_strdup(hostname);
    g_free(old_hostname);
205

206
    _notify(self, PROP_HOSTNAME);
207
208
209
}

static void
210
_set_hostname_read_file(NMHostnameManager *self)
211
{
212
213
    NMHostnameManagerPrivate *priv     = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
    gs_free char *            hostname = NULL;
214

215
216
217
218
    if (priv->hostnamed_proxy) {
        /* read-hostname returns the current hostname with hostnamed. */
        return;
    }
219

220
221
#if defined(HOSTNAME_PERSIST_SUSE)
    if (priv->dhcp_monitor_id && hostname_is_dynamic())
222
        return;
223
224
225
226
227
228
229
230
231
232
#endif

#if defined(HOSTNAME_PERSIST_GENTOO)
    hostname = read_hostname_gentoo(HOSTNAME_FILE);
#elif defined(HOSTNAME_PERSIST_SLACKWARE)
    hostname     = read_hostname_slackware(HOSTNAME_FILE);
#else
    if (g_file_get_contents(HOSTNAME_FILE, &hostname, NULL, NULL))
        g_strchomp(hostname);
#endif
233

234
    _set_hostname(self, hostname);
235
236
237
238
}

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

239
static void
240
set_transient_hostname_done(GObject *object, GAsyncResult *res, gpointer user_data)
241
{
242
243
244
245
246
247
248
249
    GDBusProxy *     proxy                  = G_DBUS_PROXY(object);
    gs_unref_variant GVariant *result       = NULL;
    gs_free_error GError *         error    = NULL;
    gs_free char *                 hostname = NULL;
    NMHostnameManagerSetHostnameCb cb;
    gpointer                       cb_user_data;

    nm_utils_user_data_unpack(user_data, &hostname, &cb, &cb_user_data);
250

251
    result = g_dbus_proxy_call_finish(proxy, res, &error);
252

253
254
    if (error) {
        _LOGW("couldn't set the system hostname to '%s' using hostnamed: %s",
255
              hostname,
256
257
              error->message);
    }
258

259
    cb(hostname, !error, cb_user_data);
260
261
262
}

void
263
264
265
266
nm_hostname_manager_set_transient_hostname(NMHostnameManager *            self,
                                           const char *                   hostname,
                                           NMHostnameManagerSetHostnameCb cb,
                                           gpointer                       user_data)
267
{
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
    NMHostnameManagerPrivate *priv;

    g_return_if_fail(NM_IS_HOSTNAME_MANAGER(self));

    priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);

    if (!priv->hostnamed_proxy) {
        cb(hostname, FALSE, user_data);
        return;
    }

    g_dbus_proxy_call(priv->hostnamed_proxy,
                      "SetHostname",
                      g_variant_new("(sb)", hostname, FALSE),
                      G_DBUS_CALL_FLAGS_NONE,
                      -1,
                      NULL,
                      set_transient_hostname_done,
286
                      nm_utils_user_data_pack(g_strdup(hostname), cb, user_data));
287
288
289
}

gboolean
290
nm_hostname_manager_get_transient_hostname(NMHostnameManager *self, char **hostname)
291
{
292
293
    NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
    GVariant *                v_hostname;
294

295
296
    if (!priv->hostnamed_proxy)
        return FALSE;
297

298
299
300
301
302
    v_hostname = g_dbus_proxy_get_cached_property(priv->hostnamed_proxy, "Hostname");
    if (!v_hostname) {
        _LOGT("transient hostname retrieval failed");
        return FALSE;
    }
303

304
305
    *hostname = g_variant_dup_string(v_hostname, NULL);
    g_variant_unref(v_hostname);
306

307
    return TRUE;
308
309
310
}

gboolean
311
nm_hostname_manager_write_hostname(NMHostnameManager *self, const char *hostname)
312
{
313
314
315
316
317
318
319
320
    NMHostnameManagerPrivate *priv;
    char *                    hostname_eol;
    gboolean                  ret;
    gs_free_error GError *error     = NULL;
    const char *          file      = HOSTNAME_FILE;
    gs_free char *        link_path = NULL;
    gs_unref_variant GVariant *var  = NULL;
    struct stat                file_stat;
321
#if HAVE_SELINUX
322
323
    gboolean fcon_was_set = FALSE;
    char *   fcon_prev    = NULL;
324
325
#endif

326
    g_return_val_if_fail(NM_IS_HOSTNAME_MANAGER(self), FALSE);
327

328
    priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
329

330
331
332
333
334
335
336
337
338
339
    if (priv->hostnamed_proxy) {
        var = g_dbus_proxy_call_sync(priv->hostnamed_proxy,
                                     "SetStaticHostname",
                                     g_variant_new("(sb)", hostname, FALSE),
                                     G_DBUS_CALL_FLAGS_NONE,
                                     -1,
                                     NULL,
                                     &error);
        if (error)
            _LOGW("could not set hostname: %s", error->message);
340

341
342
        return !error;
    }
343

344
    /* If the hostname file is a symbolic link, follow it to find where the
345
346
347
     * real file is located, otherwise g_file_set_contents will attempt to
     * replace the link with a plain file.
     */
348
349
350
351
352
353
354
355
    if (lstat(file, &file_stat) == 0 && S_ISLNK(file_stat.st_mode)
        && (link_path = nm_utils_read_link_absolute(file, NULL)))
        file = link_path;

#if defined(HOSTNAME_PERSIST_GENTOO)
    hostname_eol = g_strdup_printf("#Generated by NetworkManager\n"
                                   "hostname=\"%s\"\n",
                                   hostname);
356
#else
357
    hostname_eol = g_strdup_printf("%s\n", hostname);
358
359
#endif

360
#if HAVE_SELINUX
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
    /* Get default context for hostname file and set it for fscreate */
    {
        struct selabel_handle *handle;

        handle = selabel_open(SELABEL_CTX_FILE, NULL, 0);
        if (handle) {
            mode_t st_mode = 0;
            char * fcon    = NULL;

            if (stat(file, &file_stat) == 0)
                st_mode = file_stat.st_mode;

            if ((selabel_lookup(handle, &fcon, file, st_mode) == 0)
                && (getfscreatecon(&fcon_prev) == 0)) {
                setfscreatecon(fcon);
                fcon_was_set = TRUE;
            }

            selabel_close(handle);
            freecon(fcon);
        }
    }
383
384
#endif

385
    ret = g_file_set_contents(file, hostname_eol, -1, &error);
386
387

#if HAVE_SELINUX
388
389
390
391
392
    /* Restore previous context and cleanup */
    if (fcon_was_set)
        setfscreatecon(fcon_prev);
    if (fcon_prev)
        freecon(fcon_prev);
393
394
#endif

395
    g_free(hostname_eol);
396

397
398
399
400
    if (!ret) {
        _LOGW("could not save hostname to %s: %s", file, error->message);
        return FALSE;
    }
401

402
    return TRUE;
403
404
405
406
407
}

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

static void
408
409
410
411
hostnamed_properties_changed(GDBusProxy *proxy,
                             GVariant *  changed_properties,
                             char **     invalidated_properties,
                             gpointer    user_data)
412
{
413
414
415
    NMHostnameManager *       self     = user_data;
    NMHostnameManagerPrivate *priv     = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
    gs_unref_variant GVariant *variant = NULL;
416

417
418
419
    variant = g_dbus_proxy_get_cached_property(priv->hostnamed_proxy, "StaticHostname");
    if (variant && g_variant_is_of_type(variant, G_VARIANT_TYPE_STRING))
        _set_hostname(self, g_variant_get_string(variant, NULL));
420
421
}

422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
/*****************************************************************************/

static void
_file_monitors_file_changed_cb(GFileMonitor *    monitor,
                               GFile *           file,
                               GFile *           other_file,
                               GFileMonitorEvent event_type,
                               gpointer          user_data)
{
    _set_hostname_read_file(user_data);
}

static void
_file_monitors_clear(NMHostnameManager *self)
{
    NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);

    if (priv->monitor) {
        nm_clear_g_signal_handler(priv->monitor, &priv->monitor_id);
        g_file_monitor_cancel(priv->monitor);
        g_clear_object(&priv->monitor);
    }

    if (priv->dhcp_monitor) {
        nm_clear_g_signal_handler(priv->dhcp_monitor, &priv->dhcp_monitor_id);
        g_file_monitor_cancel(priv->dhcp_monitor);
        g_clear_object(&priv->dhcp_monitor);
    }
}

452
static void
453
_file_monitors_setup(NMHostnameManager *self)
454
{
455
456
457
    NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
    GFileMonitor *            monitor;
    const char *              path      = HOSTNAME_FILE;
458
    gs_free char *            link_path = NULL;
459
    struct stat               file_stat;
460
461

    _file_monitors_clear(self);
462
463
464
465
466
467
468
469
470
471
472
473

    /* resolve the path to the hostname file if it is a symbolic link */
    if (lstat(path, &file_stat) == 0 && S_ISLNK(file_stat.st_mode)
        && (link_path = nm_utils_read_link_absolute(path, NULL))) {
        path = link_path;
        if (lstat(link_path, &file_stat) == 0 && S_ISLNK(file_stat.st_mode)) {
            _LOGW("only one level of symbolic link indirection is allowed when "
                  "monitoring " HOSTNAME_FILE);
        }
    }

    /* monitor changes to hostname file */
474
    monitor = _file_monitor_new(path);
475
476
    if (monitor) {
        priv->monitor_id =
477
            g_signal_connect(monitor, "changed", G_CALLBACK(_file_monitors_file_changed_cb), self);
478
479
480
481
482
        priv->monitor = monitor;
    }

#if defined(HOSTNAME_PERSIST_SUSE)
    /* monitor changes to dhcp file to know whether the hostname is valid */
483
    monitor = _file_monitor_new(CONF_DHCP);
484
485
    if (monitor) {
        priv->dhcp_monitor_id =
486
            g_signal_connect(monitor, "changed", G_CALLBACK(_file_monitors_file_changed_cb), self);
487
488
        priv->dhcp_monitor = monitor;
    }
489
490
#endif

491
    _set_hostname_read_file(self);
492
493
494
495
496
}

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

static void
497
get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
498
{
499
500
501
502
503
504
505
506
507
508
    NMHostnameManager *self = NM_HOSTNAME_MANAGER(object);

    switch (prop_id) {
    case PROP_HOSTNAME:
        g_value_set_string(value, nm_hostname_manager_get_hostname(self));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        break;
    }
509
510
511
512
513
}

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

static void
514
515
nm_hostname_manager_init(NMHostnameManager *self)
{}
516
517

static void
518
constructed(GObject *object)
519
{
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
    NMHostnameManager *       self = NM_HOSTNAME_MANAGER(object);
    NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
    GDBusProxy *              proxy;
    GVariant *                variant;
    gs_free_error GError *error = NULL;

    proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM,
                                          0,
                                          NULL,
                                          HOSTNAMED_SERVICE_NAME,
                                          HOSTNAMED_SERVICE_PATH,
                                          HOSTNAMED_SERVICE_INTERFACE,
                                          NULL,
                                          &error);
    if (proxy) {
        variant = g_dbus_proxy_get_cached_property(proxy, "StaticHostname");
        if (variant) {
            _LOGI("hostname: using hostnamed");
            priv->hostnamed_proxy = proxy;
            g_signal_connect(proxy,
                             "g-properties-changed",
                             G_CALLBACK(hostnamed_properties_changed),
                             self);
            hostnamed_properties_changed(proxy, NULL, NULL, self);
            g_variant_unref(variant);
        } else {
            _LOGI("hostname: couldn't get property from hostnamed");
            g_object_unref(proxy);
        }
    } else {
        _LOGI("hostname: hostnamed not used as proxy creation failed with: %s", error->message);
        g_clear_error(&error);
    }

    if (!priv->hostnamed_proxy)
555
        _file_monitors_setup(self);
556
557

    G_OBJECT_CLASS(nm_hostname_manager_parent_class)->constructed(object);
558
559
560
}

static void
561
dispose(GObject *object)
562
{
563
564
    NMHostnameManager *       self = NM_HOSTNAME_MANAGER(object);
    NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
565

566
567
568
569
570
571
    if (priv->hostnamed_proxy) {
        g_signal_handlers_disconnect_by_func(priv->hostnamed_proxy,
                                             G_CALLBACK(hostnamed_properties_changed),
                                             self);
        g_clear_object(&priv->hostnamed_proxy);
    }
572

573
    _file_monitors_clear(self);
574

575
    nm_clear_g_free(&priv->current_hostname);
576

577
    G_OBJECT_CLASS(nm_hostname_manager_parent_class)->dispose(object);
578
579
580
}

static void
581
nm_hostname_manager_class_init(NMHostnameManagerClass *class)
582
{
583
    GObjectClass *object_class = G_OBJECT_CLASS(class);
584

585
586
587
    object_class->constructed  = constructed;
    object_class->get_property = get_property;
    object_class->dispose      = dispose;
588

589
590
591
592
593
    obj_properties[PROP_HOSTNAME] = g_param_spec_string(NM_HOSTNAME_MANAGER_HOSTNAME,
                                                        "",
                                                        "",
                                                        NULL,
                                                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
594

595
    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
596
}