From 6c3d71c431ef63005f9005e68ff49b21b153ee9f Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 23 Mar 2015 09:15:47 +0100 Subject: [PATCH] core: move handling of hostname from plugins to core How to write and read the machine hostname is something that has been handled until now by plugins; this is questionable since the method using for storing the hostname should depend only on the distro used and not on which plugins are enabled. This commit moves all hostname-related functions from plugins to the core and allows to specify the method used to load and store the hostname at build time with the --with-hostname-persist=default|suse|gentoo configure option. 'default' method stores the hostname to /etc/hostname and monitors it to detect runtime changes. When the selected method is 'suse', the hostname gets read from and written to /etc/HOSTNAME; the file /etc/sysconfig/network/dhcp is also read to detect if the hostname is dynamic and thus invalid. Both files are monitored for changes. 'gentoo' method relies on /etc/conf.d/hostname for storing the hostname. --- configure.ac | 26 ++++ src/settings/nm-settings.c | 278 +++++++++++++++++++++++++++++++------ 2 files changed, 258 insertions(+), 46 deletions(-) diff --git a/configure.ac b/configure.ac index 442f25abd3..7b1f9eb5b4 100644 --- a/configure.ac +++ b/configure.ac @@ -119,6 +119,13 @@ if test -z "$config_plugins_default" -o "$config_plugins_default" = no; then test "$enable_config_plugin_ibft" = "yes" && config_plugins_default="$config_plugins_default,ibft" config_plugins_default="${config_plugins_default#,}" fi + +test "$enable_ifcfg_rh" = "yes" && distro_plugins="$distro_plugins,ifcfg-rh" +test "$enable_ifcfg_suse" = "yes" && distro_plugins="$distro_plugins,ifcfg-suse" +test "$enable_ifupdown" = "yes" && distro_plugins="$distro_plugins,ifupdown" +test "$enable_ifnet" = "yes" && distro_plugins="$distro_plugins,ifnet" +distro_plugins="${distro_plugins#,}" + AC_DEFINE_UNQUOTED(CONFIG_PLUGINS_DEFAULT, "$config_plugins_default", [Default configuration option for main.plugins setting]) if test "$enable_ifcfg_rh" = "yes"; then @@ -347,6 +354,24 @@ fi PKG_CHECK_MODULES(SYSTEMD_200, [systemd >= 200], [have_systemd_200=yes],[have_systemd_200=no]) AM_CONDITIONAL(HAVE_SYSTEMD_200, test "${have_systemd_200}" = "yes") +# Hostname persist mode +AC_ARG_WITH(hostname-persist, AS_HELP_STRING([--with-hostname-persist=default|suse|gentoo], + [Hostname persist method])) + +AS_IF([test "$with_hostname_persist" = "suse"], hostname_persist=suse) +AS_IF([test "$with_hostname_persist" = "gentoo"], hostname_persist=gentoo) +AS_IF([test "$with_hostname_persist" = "default"], hostname_persist=default) +# if the method was not explicitly set, try to guess it from the enabled plugins +AS_IF([test -z "$hostname_persist" -a "$distro_plugins" = "ifcfg-suse"], hostname_persist=suse) +AS_IF([test -z "$hostname_persist" -a "$distro_plugins" = "ifnet"], hostname_persist=gentoo) +AS_IF([test -z "$hostname_persist"], hostname_persist=default) + +if test "$hostname_persist" = suse; then + AC_DEFINE(HOSTNAME_PERSIST_SUSE, 1, [Enable SuSE hostname persist method]) +elif test "$hostname_persist" = gentoo; then + AC_DEFINE(HOSTNAME_PERSIST_GENTOO, 1, [Enable Gentoo hostname persist method]) +fi + # Session tracking support AC_ARG_WITH(systemd-logind, AS_HELP_STRING([--with-systemd-logind=yes|no], [Support systemd session tracking])) @@ -1063,6 +1088,7 @@ else fi echo " polkit agent: ${enable_polkit_agent}" echo " selinux: $have_selinux" +echo " hostname persist: ${hostname_persist}" echo echo "Features:" diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c index a1e1d9b7de..e921755d11 100644 --- a/src/settings/nm-settings.c +++ b/src/settings/nm-settings.c @@ -34,6 +34,10 @@ #include #include +#if HAVE_SELINUX +#include +#endif + #include "gsystem-local-alloc.h" #include #include @@ -94,6 +98,20 @@ EXPORT(nm_settings_connection_replace_settings) EXPORT(nm_settings_connection_replace_and_commit) /* END LINKER CRACKROCK */ +#define HOSTNAME_FILE_DEFAULT "/etc/hostname" +#define HOSTNAME_FILE_SUSE "/etc/HOSTNAME" +#define HOSTNAME_FILE_GENTOO "/etc/conf.d/hostname" +#define IFCFG_DIR SYSCONFDIR "/sysconfig/network" +#define CONF_DHCP IFCFG_DIR "/dhcp" + +#if defined(HOSTNAME_PERSIST_SUSE) +#define HOSTNAME_FILE HOSTNAME_FILE_SUSE +#elif defined(HOSTNAME_PERSIST_GENTOO) +#define HOSTNAME_FILE HOSTNAME_FILE_GENTOO +#else +#define HOSTNAME_FILE HOSTNAME_FILE_DEFAULT +#endif + static void claim_connection (NMSettings *self, NMSettingsConnection *connection); @@ -152,6 +170,15 @@ typedef struct { GSList *get_connections_cache; gboolean startup_complete; + + struct { + char *value; + char *file; + GFileMonitor *monitor; + GFileMonitor *dhcp_monitor; + guint monitor_id; + guint dhcp_monitor_id; + } hostname; } NMSettingsPrivate; #define NM_SETTINGS_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_SETTINGS, NMSettingsPrivate)) @@ -497,7 +524,7 @@ get_plugin (NMSettings *self, guint32 capability) g_return_val_if_fail (self != NULL, NULL); - /* Do any of the plugins support setting the hostname? */ + /* Do any of the plugins support the given capability? */ for (iter = priv->plugins; iter; iter = iter->next) { NMSystemConfigInterfaceCapabilities caps = NM_SYSTEM_CONFIG_INTERFACE_CAP_NONE; @@ -509,30 +536,88 @@ get_plugin (NMSettings *self, guint32 capability) return NULL; } +#if defined(HOSTNAME_PERSIST_GENTOO) +static gchar * +read_hostname_gentoo (const char *path) +{ + gchar *contents = NULL, *result = NULL, *tmp; + gchar **all_lines = NULL; + guint line_num, i; + + if (!g_file_get_contents (path, &contents, NULL, NULL)) + return NULL; + all_lines = g_strsplit (contents, "\n", 0); + line_num = g_strv_length (all_lines); + for (i = 0; i < line_num; 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][STRLEN ("hostname=")]; + result = g_shell_unquote (tmp, NULL); + break; + } + } + g_strfreev (all_lines); + g_free (contents); + return result; +} +#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[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_settings_get_hostname (NMSettings *self) { NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self); - GSList *iter; char *hostname = NULL; - /* Hostname returned is the hostname returned from the first plugin - * that provides one. - */ - for (iter = priv->plugins; iter; iter = iter->next) { - NMSystemConfigInterfaceCapabilities caps = NM_SYSTEM_CONFIG_INTERFACE_CAP_NONE; +#if defined(HOSTNAME_PERSIST_GENTOO) + hostname = read_hostname_gentoo (priv->hostname.file); +#else - g_object_get (G_OBJECT (iter->data), NM_SYSTEM_CONFIG_INTERFACE_CAPABILITIES, &caps, NULL); - if (caps & NM_SYSTEM_CONFIG_INTERFACE_CAP_MODIFY_HOSTNAME) { - g_object_get (G_OBJECT (iter->data), NM_SYSTEM_CONFIG_INTERFACE_HOSTNAME, &hostname, NULL); - if (hostname && strlen (hostname)) - return hostname; - g_free (hostname); - } +#if defined(HOSTNAME_PERSIST_SUSE) + if (priv->hostname.dhcp_monitor_id && hostname_is_dynamic ()) + return NULL; +#endif + if (g_file_get_contents (priv->hostname.file, &hostname, NULL, NULL)) + g_strchomp (hostname); + +#endif /* HOSTNAME_PERSIST_GENTOO */ + + if (hostname && !hostname[0]) { + g_free (hostname); + hostname = NULL; } - return NULL; + return hostname; } static gboolean @@ -595,14 +680,6 @@ unrecognized_specs_changed (NMSystemConfigInterface *config, nm_system_config_interface_get_unrecognized_specs); } -static void -hostname_changed (NMSystemConfigInterface *config, - GParamSpec *pspec, - gpointer user_data) -{ - g_object_notify (G_OBJECT (user_data), NM_SETTINGS_HOSTNAME); -} - static void add_plugin (NMSettings *self, NMSystemConfigInterface *plugin) { @@ -616,9 +693,6 @@ add_plugin (NMSettings *self, NMSystemConfigInterface *plugin) priv = NM_SETTINGS_GET_PRIVATE (self); priv->plugins = g_slist_append (priv->plugins, g_object_ref (plugin)); - - g_signal_connect (plugin, "notify::"NM_SYSTEM_CONFIG_INTERFACE_HOSTNAME, G_CALLBACK (hostname_changed), self); - nm_system_config_interface_init (plugin, NULL); g_object_get (G_OBJECT (plugin), @@ -1442,6 +1516,56 @@ impl_settings_reload_connections (NMSettings *self, dbus_g_method_return (context, TRUE); } +static gboolean +write_hostname (NMSettingsPrivate *priv, const char *hostname) +{ + char *hostname_eol; + gboolean ret; + gs_free_error GError *error = NULL; + const char *file = priv->hostname.file; +#if HAVE_SELINUX + security_context_t se_ctx_prev = NULL, se_ctx = NULL; + struct stat file_stat = { .st_mode = 0 }; + mode_t st_mode = 0; + + /* Get default context for hostname file and set it for fscreate */ + if (stat (file, &file_stat) == 0) + st_mode = file_stat.st_mode; + matchpathcon (file, st_mode, &se_ctx); + matchpathcon_fini (); + getfscreatecon (&se_ctx_prev); + setfscreatecon (se_ctx); +#endif + +#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 + + /* FIXME: g_file_set_contents() writes first to a temporary file + * and renames it atomically. We should hack g_file_set_contents() + * to set the SELINUX labels before renaming the file. */ + ret = g_file_set_contents (file, hostname_eol, -1, &error); + +#if HAVE_SELINUX + /* Restore previous context and cleanup */ + setfscreatecon (se_ctx_prev); + freecon (se_ctx); + freecon (se_ctx_prev); +#endif + + g_free (hostname_eol); + + if (!ret) { + nm_log_warn (LOGD_SETTINGS, "Could not save hostname to %s: %s", file, error->message); + return FALSE; + } + + return TRUE; +} + static void pk_hostname_cb (NMAuthChain *chain, GError *chain_error, @@ -1452,7 +1576,6 @@ pk_hostname_cb (NMAuthChain *chain, NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self); NMAuthCallResult result; GError *error = NULL; - GSList *iter; const char *hostname; g_assert (context); @@ -1472,21 +1595,12 @@ pk_hostname_cb (NMAuthChain *chain, NM_SETTINGS_ERROR_PERMISSION_DENIED, "Insufficient privileges."); } else { - /* Set the hostname in all plugins */ hostname = nm_auth_chain_get_data (chain, "hostname"); - for (iter = priv->plugins; iter; iter = iter->next) { - NMSystemConfigInterfaceCapabilities caps = NM_SYSTEM_CONFIG_INTERFACE_CAP_NONE; - /* error will be cleared if any plugin supports saving the hostname */ + if (!write_hostname (priv, hostname)) { error = g_error_new_literal (NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, "Saving the hostname failed."); - - g_object_get (G_OBJECT (iter->data), NM_SYSTEM_CONFIG_INTERFACE_CAPABILITIES, &caps, NULL); - if (caps & NM_SYSTEM_CONFIG_INTERFACE_CAP_MODIFY_HOSTNAME) { - g_object_set (G_OBJECT (iter->data), NM_SYSTEM_CONFIG_INTERFACE_HOSTNAME, hostname, NULL); - g_clear_error (&error); - } } } @@ -1543,14 +1657,6 @@ impl_settings_save_hostname (NMSettings *self, goto done; } - /* Do any of the plugins support setting the hostname? */ - if (!get_plugin (self, NM_SYSTEM_CONFIG_INTERFACE_CAP_MODIFY_HOSTNAME)) { - error = g_error_new_literal (NM_SETTINGS_ERROR, - NM_SETTINGS_ERROR_NOT_SUPPORTED, - "None of the registered plugins support setting the hostname."); - goto done; - } - chain = nm_auth_chain_new_context (context, pk_hostname_cb, self); if (!chain) { error = g_error_new_literal (NM_SETTINGS_ERROR, @@ -1569,6 +1675,37 @@ done: g_clear_error (&error); } +static void +hostname_maybe_changed (NMSettings *settings) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (settings); + char *new_hostname; + + new_hostname = nm_settings_get_hostname (settings); + + if ( (new_hostname && !priv->hostname.value) + || (!new_hostname && priv->hostname.value) + || (priv->hostname.value && new_hostname && strcmp (priv->hostname.value, new_hostname))) { + + nm_log_info (LOGD_SETTINGS, "hostname changed from '%s' to '%s'", + priv->hostname.value, new_hostname); + g_free (priv->hostname.value); + priv->hostname.value = new_hostname; + g_object_notify (G_OBJECT (settings), NM_SETTINGS_HOSTNAME); + } else + g_free (new_hostname); +} + +static void +hostname_file_changed_cb (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + hostname_maybe_changed (user_data); +} + static gboolean have_connection_for_device (NMSettings *self, NMDevice *device) { @@ -1872,6 +2009,8 @@ nm_settings_new (GError **error) { NMSettings *self; NMSettingsPrivate *priv; + GFile *file; + GFileMonitor *monitor; self = g_object_new (NM_TYPE_SETTINGS, NULL); @@ -1889,6 +2028,34 @@ nm_settings_new (GError **error) load_connections (self); check_startup_complete (self); + priv->hostname.file = HOSTNAME_FILE; + priv->hostname.value = nm_settings_get_hostname (self); + + /* monitor changes to hostname file */ + file = g_file_new_for_path (priv->hostname.file); + monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref (file); + if (monitor) { + priv->hostname.monitor_id = g_signal_connect (monitor, "changed", + G_CALLBACK (hostname_file_changed_cb), + self); + priv->hostname.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->hostname.dhcp_monitor_id = g_signal_connect (monitor, "changed", + G_CALLBACK (hostname_file_changed_cb), + self); + priv->hostname.dhcp_monitor = monitor; + } +#endif + + hostname_maybe_changed (self); nm_dbus_manager_register_object (priv->dbus_mgr, NM_DBUS_PATH_SETTINGS, self); return self; } @@ -1932,6 +2099,25 @@ dispose (GObject *object) g_object_unref (priv->agent_mgr); + if (priv->hostname.monitor) { + if (priv->hostname.monitor_id) + g_signal_handler_disconnect (priv->hostname.monitor, priv->hostname.monitor_id); + + g_file_monitor_cancel (priv->hostname.monitor); + g_clear_object (&priv->hostname.monitor); + } + + if (priv->hostname.dhcp_monitor) { + if (priv->hostname.dhcp_monitor_id) + g_signal_handler_disconnect (priv->hostname.dhcp_monitor, + priv->hostname.dhcp_monitor_id); + + g_file_monitor_cancel (priv->hostname.dhcp_monitor); + g_clear_object (&priv->hostname.dhcp_monitor); + } + + g_clear_pointer (&priv->hostname.value, g_free); + G_OBJECT_CLASS (nm_settings_parent_class)->dispose (object); } @@ -2001,7 +2187,7 @@ static void nm_settings_class_init (NMSettingsClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); - + g_type_class_add_private (class, sizeof (NMSettingsPrivate)); /* virtual methods */