NetworkManager/src/core/nm-hostname-manager.c
Thomas Haller fdf9614ba7
build: move "libnm-core/" to "src/" and split it
"libnm-core/" is rather complicated. It provides a static library that
is linked into libnm.so and NetworkManager. It also contains public
headers (like "nm-setting.h") which are part of public libnm API.

Then we have helper libraries ("libnm-core/nm-libnm-core-*/") which
only rely on public API of libnm-core, but are themself static
libraries that can be used by anybody who uses libnm-core. And
"libnm-core/nm-libnm-core-intern" is used by libnm-core itself.

Move "libnm-core/" to "src/". But also split it in different
directories so that they have a clearer purpose.

The goal is to have a flat directory hierarchy. The "src/libnm-core*/"
directories correspond to the different modules (static libraries and set
of headers that we have). We have different kinds of such modules because
of how we combine various code together. The directory layout now reflects
this.
2021-02-18 19:46:51 +01:00

649 lines
20 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2017 Red Hat, Inc.
*/
#include "src/core/nm-default-daemon.h"
#include "nm-hostname-manager.h"
#include <sys/stat.h>
#if HAVE_SELINUX
#include <selinux/selinux.h>
#include <selinux/label.h>
#endif
#include "libnm-core-aux-intern/nm-common-macros.h"
#include "nm-dbus-interface.h"
#include "nm-connection.h"
#include "nm-utils.h"
#include "libnm-core-intern/nm-core-internal.h"
#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"
#define CONF_DHCP SYSCONFDIR "/sysconfig/network/dhcp"
#if (defined(HOSTNAME_PERSIST_SUSE) + defined(HOSTNAME_PERSIST_SLACKWARE) \
+ defined(HOSTNAME_PERSIST_GENTOO)) \
> 1
#error "Can only define one of HOSTNAME_PERSIST_*"
#endif
#if defined(HOSTNAME_PERSIST_SUSE)
#define HOSTNAME_FILE HOSTNAME_FILE_UCASE_HOSTNAME
#elif defined(HOSTNAME_PERSIST_SLACKWARE)
#define HOSTNAME_FILE HOSTNAME_FILE_UCASE_HOSTNAME
#elif defined(HOSTNAME_PERSIST_GENTOO)
#define HOSTNAME_FILE HOSTNAME_FILE_GENTOO
#else
#define HOSTNAME_FILE HOSTNAME_FILE_DEFAULT
#endif
/*****************************************************************************/
NM_GOBJECT_PROPERTIES_DEFINE(NMHostnameManager, PROP_HOSTNAME, );
typedef struct {
char * current_hostname;
GFileMonitor *monitor;
GFileMonitor *dhcp_monitor;
gulong monitor_id;
gulong dhcp_monitor_id;
GDBusProxy * hostnamed_proxy;
} NMHostnameManagerPrivate;
struct _NMHostnameManager {
GObject parent;
NMHostnameManagerPrivate _priv;
};
struct _NMHostnameManagerClass {
GObjectClass parent;
};
G_DEFINE_TYPE(NMHostnameManager, nm_hostname_manager, G_TYPE_OBJECT);
#define NM_HOSTNAME_MANAGER_GET_PRIVATE(self) \
_NM_GET_PRIVATE(self, NMHostnameManager, NM_IS_HOSTNAME_MANAGER)
NM_DEFINE_SINGLETON_GETTER(NMHostnameManager, nm_hostname_manager_get, NM_TYPE_HOSTNAME_MANAGER);
/*****************************************************************************/
#define _NMLOG_DOMAIN LOGD_CORE
#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "hostname", __VA_ARGS__)
/*****************************************************************************/
#if defined(HOSTNAME_PERSIST_GENTOO)
static char *
read_hostname_gentoo(const char *path)
{
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;
}
#endif
#if defined(HOSTNAME_PERSIST_SLACKWARE)
static char *
read_hostname_slackware(const char *path)
{
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;
}
#endif
#if defined(HOSTNAME_PERSIST_SUSE)
static gboolean
hostname_is_dynamic(void)
{
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;
}
#endif
/* Returns an allocated string which the caller owns and must eventually free */
char *
nm_hostname_manager_read_hostname(NMHostnameManager *self)
{
NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
char * hostname = NULL;
if (priv->hostnamed_proxy) {
hostname = g_strdup(priv->current_hostname);
goto out;
}
#if defined(HOSTNAME_PERSIST_SUSE)
if (priv->dhcp_monitor_id && hostname_is_dynamic())
return NULL;
#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
out:
if (hostname && !hostname[0]) {
g_free(hostname);
return NULL;
}
return hostname;
}
/*****************************************************************************/
const char *
nm_hostname_manager_get_hostname(NMHostnameManager *self)
{
g_return_val_if_fail(NM_IS_HOSTNAME_MANAGER(self), NULL);
return NM_HOSTNAME_MANAGER_GET_PRIVATE(self)->current_hostname;
}
static void
_set_hostname_take(NMHostnameManager *self, char *hostname)
{
NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
_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)"));
g_free(priv->current_hostname);
priv->current_hostname = hostname;
_notify(self, PROP_HOSTNAME);
}
static void
_set_hostname(NMHostnameManager *self, const char *hostname)
{
NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
hostname = nm_str_not_empty(hostname);
if (!nm_streq0(hostname, priv->current_hostname))
_set_hostname_take(self, g_strdup(hostname));
}
static void
_set_hostname_read(NMHostnameManager *self)
{
NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
char * hostname;
if (priv->hostnamed_proxy) {
/* read-hostname returns the current hostname with hostnamed. */
return;
}
hostname = nm_hostname_manager_read_hostname(self);
if (nm_streq0(hostname, priv->current_hostname)) {
g_free(hostname);
return;
}
_set_hostname_take(self, hostname);
}
/*****************************************************************************/
typedef struct {
char * hostname;
NMHostnameManagerSetHostnameCb cb;
gpointer user_data;
} SetHostnameInfo;
static void
set_transient_hostname_done(GObject *object, GAsyncResult *res, gpointer user_data)
{
GDBusProxy *proxy = G_DBUS_PROXY(object);
gs_free SetHostnameInfo *info = user_data;
gs_unref_variant GVariant *result = NULL;
gs_free_error GError *error = NULL;
result = g_dbus_proxy_call_finish(proxy, res, &error);
if (error) {
_LOGW("couldn't set the system hostname to '%s' using hostnamed: %s",
info->hostname,
error->message);
}
info->cb(info->hostname, !error, info->user_data);
g_free(info->hostname);
}
void
nm_hostname_manager_set_transient_hostname(NMHostnameManager * self,
const char * hostname,
NMHostnameManagerSetHostnameCb cb,
gpointer user_data)
{
NMHostnameManagerPrivate *priv;
SetHostnameInfo * info;
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;
}
info = g_new0(SetHostnameInfo, 1);
info->hostname = g_strdup(hostname);
info->cb = cb;
info->user_data = user_data;
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,
info);
}
gboolean
nm_hostname_manager_get_transient_hostname(NMHostnameManager *self, char **hostname)
{
NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
GVariant * v_hostname;
if (!priv->hostnamed_proxy)
return FALSE;
v_hostname = g_dbus_proxy_get_cached_property(priv->hostnamed_proxy, "Hostname");
if (!v_hostname) {
_LOGT("transient hostname retrieval failed");
return FALSE;
}
*hostname = g_variant_dup_string(v_hostname, NULL);
g_variant_unref(v_hostname);
return TRUE;
}
gboolean
nm_hostname_manager_write_hostname(NMHostnameManager *self, const char *hostname)
{
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;
#if HAVE_SELINUX
gboolean fcon_was_set = FALSE;
char * fcon_prev = NULL;
#endif
g_return_val_if_fail(NM_IS_HOSTNAME_MANAGER(self), FALSE);
priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
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);
return !error;
}
/* If the hostname file is a symbolic link, follow it to find where the
* real file is located, otherwise g_file_set_contents will attempt to
* replace the link with a plain file.
*/
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);
#else
hostname_eol = g_strdup_printf("%s\n", hostname);
#endif
#if HAVE_SELINUX
/* 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);
}
}
#endif
ret = g_file_set_contents(file, hostname_eol, -1, &error);
#if HAVE_SELINUX
/* Restore previous context and cleanup */
if (fcon_was_set)
setfscreatecon(fcon_prev);
if (fcon_prev)
freecon(fcon_prev);
#endif
g_free(hostname_eol);
if (!ret) {
_LOGW("could not save hostname to %s: %s", file, error->message);
return FALSE;
}
return TRUE;
}
gboolean
nm_hostname_manager_validate_hostname(const char *hostname)
{
const char *p;
gboolean dot = TRUE;
if (!hostname || !hostname[0])
return FALSE;
for (p = hostname; *p; p++) {
if (*p == '.') {
if (dot)
return FALSE;
dot = TRUE;
} else {
if (!g_ascii_isalnum(*p) && (*p != '-') && (*p != '_'))
return FALSE;
dot = FALSE;
}
}
if (dot)
return FALSE;
return (p - hostname <= HOST_NAME_MAX);
}
static void
hostname_file_changed_cb(GFileMonitor * monitor,
GFile * file,
GFile * other_file,
GFileMonitorEvent event_type,
gpointer user_data)
{
_set_hostname_read(user_data);
}
/*****************************************************************************/
static void
hostnamed_properties_changed(GDBusProxy *proxy,
GVariant * changed_properties,
char ** invalidated_properties,
gpointer user_data)
{
NMHostnameManager * self = user_data;
NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
GVariant * v_hostname;
v_hostname = g_dbus_proxy_get_cached_property(priv->hostnamed_proxy, "StaticHostname");
if (v_hostname) {
_set_hostname(self, g_variant_get_string(v_hostname, NULL));
g_variant_unref(v_hostname);
}
}
static void
setup_hostname_file_monitors(NMHostnameManager *self)
{
NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
GFileMonitor * monitor;
const char * path = HOSTNAME_FILE;
char * link_path = NULL;
struct stat file_stat;
GFile * file;
/* 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 */
file = g_file_new_for_path(path);
monitor = g_file_monitor_file(file, G_FILE_MONITOR_NONE, NULL, NULL);
g_object_unref(file);
g_free(link_path);
if (monitor) {
priv->monitor_id =
g_signal_connect(monitor, "changed", G_CALLBACK(hostname_file_changed_cb), self);
priv->monitor = monitor;
}
#if defined(HOSTNAME_PERSIST_SUSE)
/* monitor changes to dhcp file to know whether the hostname is valid */
file = g_file_new_for_path(CONF_DHCP);
monitor = g_file_monitor_file(file, G_FILE_MONITOR_NONE, NULL, NULL);
g_object_unref(file);
if (monitor) {
priv->dhcp_monitor_id =
g_signal_connect(monitor, "changed", G_CALLBACK(hostname_file_changed_cb), self);
priv->dhcp_monitor = monitor;
}
#endif
_set_hostname_read(self);
}
/*****************************************************************************/
static void
get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
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;
}
}
/*****************************************************************************/
static void
nm_hostname_manager_init(NMHostnameManager *self)
{}
static void
constructed(GObject *object)
{
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)
setup_hostname_file_monitors(self);
G_OBJECT_CLASS(nm_hostname_manager_parent_class)->constructed(object);
}
static void
dispose(GObject *object)
{
NMHostnameManager * self = NM_HOSTNAME_MANAGER(object);
NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
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);
}
if (priv->monitor) {
if (priv->monitor_id)
g_signal_handler_disconnect(priv->monitor, priv->monitor_id);
g_file_monitor_cancel(priv->monitor);
g_clear_object(&priv->monitor);
}
if (priv->dhcp_monitor) {
if (priv->dhcp_monitor_id)
g_signal_handler_disconnect(priv->dhcp_monitor, priv->dhcp_monitor_id);
g_file_monitor_cancel(priv->dhcp_monitor);
g_clear_object(&priv->dhcp_monitor);
}
nm_clear_g_free(&priv->current_hostname);
G_OBJECT_CLASS(nm_hostname_manager_parent_class)->dispose(object);
}
static void
nm_hostname_manager_class_init(NMHostnameManagerClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS(class);
object_class->constructed = constructed;
object_class->get_property = get_property;
object_class->dispose = dispose;
obj_properties[PROP_HOSTNAME] = g_param_spec_string(NM_HOSTNAME_MANAGER_HOSTNAME,
"",
"",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
}