summary refs log tree commit diff
path: root/src/nm-config-data.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-config-data.c')
-rw-r--r--src/nm-config-data.c797
1 files changed, 775 insertions, 22 deletions
diff --git a/src/nm-config-data.c b/src/nm-config-data.c
index ffe00ec2..e2b6c3a6 100644
--- a/src/nm-config-data.c
+++ b/src/nm-config-data.c
@@ -19,17 +19,18 @@
  * Copyright (C) 2013 Thomas Bechtold <thomasbechtold@jpberlin.de>
  */
 
+#include "config.h"
+
 #include "nm-config-data.h"
 
 #include <string.h>
 
 #include "nm-config.h"
 #include "nm-device.h"
-#include "gsystem-local-alloc.h"
 #include "nm-core-internal.h"
 #include "nm-keyfile-internal.h"
 #include "nm-macros-internal.h"
-#include "nm-logging.h"
+#include "nm-default.h"
 
 typedef struct {
 	char *group_name;
@@ -48,6 +49,8 @@ typedef struct {
 	char *config_description;
 
 	GKeyFile *keyfile;
+	GKeyFile *keyfile_user;
+	GKeyFile *keyfile_intern;
 
 	/* A zero-terminated list of pre-processed information from the
 	 * [connection] sections. This is to speed up lookup. */
@@ -69,14 +72,34 @@ typedef struct {
 	GSList *assume_ipv6ll_only;
 
 	char *dns_mode;
+	char *rc_manager;
+
+	NMGlobalDnsConfig *global_dns;
+
+	/* mutable field */
+	char *value_cached;
 } NMConfigDataPrivate;
 
+struct _NMGlobalDnsDomain {
+	char *name;
+	char **servers;
+	char **options;
+};
+
+struct _NMGlobalDnsConfig {
+	char **searches;
+	char **options;
+	GHashTable *domains;
+	char **domain_list;
+	gboolean internal;
+};
 
 enum {
 	PROP_0,
 	PROP_CONFIG_MAIN_FILE,
 	PROP_CONFIG_DESCRIPTION,
-	PROP_KEYFILE,
+	PROP_KEYFILE_USER,
+	PROP_KEYFILE_INTERN,
 	PROP_CONNECTIVITY_URI,
 	PROP_CONNECTIVITY_INTERVAL,
 	PROP_CONNECTIVITY_RESPONSE,
@@ -85,12 +108,21 @@ enum {
 	LAST_PROP
 };
 
+
 G_DEFINE_TYPE (NMConfigData, nm_config_data, G_TYPE_OBJECT)
 
 #define NM_CONFIG_DATA_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_CONFIG_DATA, NMConfigDataPrivate))
 
 /************************************************************************/
 
+#define _HAS_PREFIX(str, prefix) \
+	({ \
+		const char *_str = (str); \
+		g_str_has_prefix ( _str, ""prefix"") && _str[STRLEN(prefix)] != '\0'; \
+	})
+
+/************************************************************************/
+
 const char *
 nm_config_data_get_config_main_file (const NMConfigData *self)
 {
@@ -126,6 +158,22 @@ nm_config_data_get_value (const NMConfigData *self, const char *group, const cha
 	return nm_config_keyfile_get_value (NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile, group, key, flags);
 }
 
+const char *nm_config_data_get_value_cached (const NMConfigData *self, const char *group, const char *key, NMConfigGetValueFlags flags)
+{
+	NMConfigDataPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_CONFIG_DATA (self), NULL);
+	g_return_val_if_fail (group && *group, NULL);
+	g_return_val_if_fail (key && *key, NULL);
+
+	priv = NM_CONFIG_DATA_GET_PRIVATE (self);
+
+	/* we modify @value_cached. In C++ jargon, the field is mutable. */
+	g_free (priv->value_cached);
+	priv->value_cached = nm_config_keyfile_get_value (priv->keyfile, group, key, flags);
+	return priv->value_cached;
+}
+
 gboolean
 nm_config_data_has_value (const NMConfigData *self, const char *group, const char *key, NMConfigGetValueFlags flags)
 {
@@ -211,6 +259,14 @@ nm_config_data_get_dns_mode (const NMConfigData *self)
 	return NM_CONFIG_DATA_GET_PRIVATE (self)->dns_mode;
 }
 
+const char *
+nm_config_data_get_rc_manager (const NMConfigData *self)
+{
+	g_return_val_if_fail (self, NULL);
+
+	return NM_CONFIG_DATA_GET_PRIVATE (self)->rc_manager;
+}
+
 gboolean
 nm_config_data_get_ignore_carrier (const NMConfigData *self, NMDevice *device)
 {
@@ -229,6 +285,40 @@ nm_config_data_get_assume_ipv6ll_only (const NMConfigData *self, NMDevice *devic
 	return nm_device_spec_match_list (device, NM_CONFIG_DATA_GET_PRIVATE (self)->assume_ipv6ll_only);
 }
 
+GKeyFile *
+nm_config_data_clone_keyfile_intern (const NMConfigData *self)
+{
+	NMConfigDataPrivate *priv;
+	GKeyFile *keyfile;
+
+	g_return_val_if_fail (NM_IS_CONFIG_DATA (self), FALSE);
+
+	priv = NM_CONFIG_DATA_GET_PRIVATE (self);
+
+	keyfile = nm_config_create_keyfile ();
+	if (priv->keyfile_intern)
+		_nm_keyfile_copy (keyfile, priv->keyfile_intern);
+	return keyfile;
+}
+
+GKeyFile *
+_nm_config_data_get_keyfile (const NMConfigData *self)
+{
+	return NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile;
+}
+
+GKeyFile *
+_nm_config_data_get_keyfile_intern (const NMConfigData *self)
+{
+	return NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile_intern;
+}
+
+GKeyFile *
+_nm_config_data_get_keyfile_user (const NMConfigData *self)
+{
+	return NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile_user;
+}
+
 /************************************************************************/
 
 /**
@@ -256,15 +346,128 @@ nm_config_data_get_keys (const NMConfigData *self, const char *group)
 	return g_key_file_get_keys (NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile, group, NULL, NULL);
 }
 
+/**
+ * nm_config_data_is_intern_atomic_group:
+ * @self:
+ * @group: name of the group to check.
+ *
+ * whether a configuration group @group exists and is entirely overwritten
+ * by internal configuration, i.e. whether it is an atomic group that is
+ * overwritten.
+ *
+ * It doesn't say, that there actually is a user setting that was overwritten. That
+ * means there could be no corresponding section defined in user configuration
+ * that required overwriting.
+ *
+ * Returns: %TRUE if @group exists and is an atomic group set via internal configuration.
+ */
+gboolean
+nm_config_data_is_intern_atomic_group (const NMConfigData *self, const char *group)
+{
+	NMConfigDataPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_CONFIG_DATA (self), FALSE);
+	g_return_val_if_fail (group && *group, FALSE);
+
+	priv = NM_CONFIG_DATA_GET_PRIVATE (self);
+
+	if (   !priv->keyfile_intern
+	    || !g_key_file_has_key (priv->keyfile_intern, group, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, NULL))
+		return FALSE;
+
+	/* we have a .was entry for the section. That means that the section would be overwritten
+	 * from user configuration. But it doesn't mean that the merged configuration contains this
+	 * groups, because the internal setting could hide the user section.
+	 * Only return TRUE, if we actually have such a group in the merged configuration.*/
+	return g_key_file_has_group (priv->keyfile, group);
+}
+
+/************************************************************************/
+
+static GKeyFile *
+_merge_keyfiles (GKeyFile *keyfile_user, GKeyFile *keyfile_intern)
+{
+	gs_strfreev char **groups = NULL;
+	guint g, k;
+	GKeyFile *keyfile;
+	gsize ngroups;
+
+	keyfile = nm_config_create_keyfile ();
+	if (keyfile_user)
+		_nm_keyfile_copy (keyfile, keyfile_user);
+	if (!keyfile_intern)
+		return keyfile;
+
+	groups = g_key_file_get_groups (keyfile_intern, &ngroups);
+	if (!groups)
+		return keyfile;
+
+	/* we must reverse the order of the connection settings so that we
+	 * have lowest priority last. */
+	_nm_config_sort_groups (groups, ngroups);
+	for (g = 0; groups[g]; g++) {
+		const char *group = groups[g];
+		gs_strfreev char **keys = NULL;
+		gboolean is_intern, is_atomic = FALSE;
+
+		keys = g_key_file_get_keys (keyfile_intern, group, NULL, NULL);
+		if (!keys)
+			continue;
+
+		is_intern = g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
+		if (   !is_intern
+		    && g_key_file_has_key (keyfile_intern, group, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, NULL)) {
+			/* the entire section is atomically overwritten by @keyfile_intern. */
+			g_key_file_remove_group (keyfile, group, NULL);
+			is_atomic = TRUE;
+		}
+
+		for (k = 0; keys[k]; k++) {
+			const char *key = keys[k];
+			gs_free char *value = NULL;
+
+			if (is_atomic && strcmp (key, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS) == 0)
+				continue;
+
+			if (   !is_intern && !is_atomic
+			    && _HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) {
+				const char *key_base = &key[STRLEN (NM_CONFIG_KEYFILE_KEYPREFIX_WAS)];
+
+				if (!g_key_file_has_key (keyfile_intern, group, key_base, NULL))
+					g_key_file_remove_key (keyfile, group, key_base, NULL);
+				continue;
+			}
+			if (!is_intern && !is_atomic && _HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_SET))
+				continue;
+
+			value = g_key_file_get_value (keyfile_intern, group, key, NULL);
+			g_key_file_set_value (keyfile, group, key, value);
+		}
+	}
+	return keyfile;
+}
+
 /************************************************************************/
 
 static int
 _nm_config_data_log_sort (const char **pa, const char **pb, gpointer dummy)
 {
 	gboolean a_is_connection, b_is_connection;
+	gboolean a_is_intern, b_is_intern;
 	const char *a = *pa;
 	const char *b = *pb;
 
+	/* we sort intern groups to the end. */
+	a_is_intern = g_str_has_prefix (a, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
+	b_is_intern = g_str_has_prefix (b, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
+
+	if (a_is_intern && b_is_intern)
+		return 0;
+	if (a_is_intern)
+		return 1;
+	if (b_is_intern)
+		return -1;
+
 	/* we sort connection groups before intern groups (to the end). */
 	a_is_connection = a && g_str_has_prefix (a, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
 	b_is_connection = b && g_str_has_prefix (b, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
@@ -340,9 +543,12 @@ nm_config_data_log (const NMConfigData *self,
 	for (g = 0; g < ngroups; g++) {
 		const char *group = groups[g];
 		gs_strfreev char **keys = NULL;
+		gboolean is_atomic;
+
+		is_atomic = nm_config_data_is_intern_atomic_group (self, group);
 
 		_LOG (stream, prefix, "");
-		_LOG (stream, prefix, "[%s]", group);
+		_LOG (stream, prefix, "[%s]%s", group, is_atomic && !stream ? " # atomic section" : "");
 
 		keys = g_key_file_get_keys (priv->keyfile, group, NULL, NULL);
 		for (k = 0; keys && keys[k]; k++) {
@@ -359,6 +565,492 @@ nm_config_data_log (const NMConfigData *self,
 
 /************************************************************************/
 
+const char *const *
+nm_global_dns_config_get_searches (const NMGlobalDnsConfig *dns)
+{
+	g_return_val_if_fail (dns, NULL);
+
+	return (const char *const *) dns->searches;
+}
+
+const char *const *
+nm_global_dns_config_get_options (const NMGlobalDnsConfig *dns)
+{
+	g_return_val_if_fail (dns, NULL);
+
+	return (const char *const *) dns->options;
+}
+
+guint
+nm_global_dns_config_get_num_domains (const NMGlobalDnsConfig *dns)
+{
+	g_return_val_if_fail (dns, 0);
+	g_return_val_if_fail (dns->domains, 0);
+
+	return g_hash_table_size (dns->domains);
+}
+
+NMGlobalDnsDomain *
+nm_global_dns_config_get_domain (const NMGlobalDnsConfig *dns, guint i)
+{
+	NMGlobalDnsDomain *domain;
+
+	g_return_val_if_fail (dns, NULL);
+	g_return_val_if_fail (dns->domains, NULL);
+	g_return_val_if_fail (dns->domain_list, NULL);
+	g_return_val_if_fail (i < g_strv_length (dns->domain_list), NULL);
+
+	domain = g_hash_table_lookup (dns->domains, dns->domain_list[i]);
+	g_return_val_if_fail (domain, NULL);
+
+	return domain;
+}
+
+NMGlobalDnsDomain *nm_global_dns_config_lookup_domain (const NMGlobalDnsConfig *dns, const char *name)
+{
+	g_return_val_if_fail (dns, NULL);
+	g_return_val_if_fail (dns->domains, NULL);
+	g_return_val_if_fail (name, NULL);
+
+	return g_hash_table_lookup (dns->domains, name);
+}
+
+const char *
+nm_global_dns_domain_get_name (const NMGlobalDnsDomain *domain)
+{
+	g_return_val_if_fail (domain, NULL);
+
+	return (const char *) domain->name;
+}
+
+const char *const *
+nm_global_dns_domain_get_servers (const NMGlobalDnsDomain *domain)
+{
+	g_return_val_if_fail (domain, NULL);
+
+	return (const char *const *) domain->servers;
+}
+
+const char *const *
+nm_global_dns_domain_get_options (const NMGlobalDnsDomain *domain)
+{
+	g_return_val_if_fail (domain, NULL);
+	return (const char *const *) domain->options;
+}
+
+gboolean
+nm_global_dns_config_is_internal (const NMGlobalDnsConfig *dns)
+{
+	return dns->internal;
+}
+
+gboolean
+nm_global_dns_config_is_empty (const NMGlobalDnsConfig *dns)
+{
+	g_return_val_if_fail (dns, TRUE);
+	g_return_val_if_fail (dns->domains, TRUE);
+
+	return    (!dns->searches || g_strv_length (dns->searches) == 0)
+	       && (!dns->options || g_strv_length (dns->options) == 0)
+	       && g_hash_table_size (dns->domains) == 0;
+}
+
+void
+nm_global_dns_config_update_checksum (const NMGlobalDnsConfig *dns, GChecksum *sum)
+{
+	NMGlobalDnsDomain *domain;
+	GList *keys, *key;
+	guint i;
+
+	g_return_if_fail (dns);
+	g_return_if_fail (dns->domains);
+	g_return_if_fail (sum);
+
+	for (i = 0; dns->searches && dns->searches[i]; i++)
+		g_checksum_update (sum, (guchar *) dns->searches[i], strlen (dns->searches[i]));
+	for (i = 0; dns->options && dns->options[i]; i++)
+		g_checksum_update (sum, (guchar *) dns->options[i], strlen (dns->options[i]));
+
+	keys = g_list_sort (g_hash_table_get_keys (dns->domains), (GCompareFunc) strcmp);
+	for (key = keys; key; key = g_list_next (key)) {
+
+		domain = g_hash_table_lookup (dns->domains, key->data);
+		g_assert (domain != NULL);
+		g_checksum_update (sum, (guchar *) domain->name, strlen (domain->name));
+
+		for (i = 0; domain->servers && domain->servers[i]; i++)
+			g_checksum_update (sum, (guchar *) domain->servers[i], strlen (domain->servers[i]));
+		for (i = 0; domain->options && domain->options[i]; i++)
+			g_checksum_update (sum, (guchar *) domain->options[i], strlen (domain->options[i]));
+	}
+	g_list_free (keys);
+}
+
+static void
+global_dns_domain_free (NMGlobalDnsDomain  *domain)
+{
+	if (domain) {
+		g_free (domain->name);
+		g_strfreev (domain->servers);
+		g_strfreev (domain->options);
+		g_free (domain);
+	}
+}
+
+void
+nm_global_dns_config_free (NMGlobalDnsConfig *conf)
+{
+	if (conf) {
+		g_strfreev (conf->searches);
+		g_strfreev (conf->options);
+		g_free (conf->domain_list);
+		g_hash_table_unref (conf->domains);
+		g_free (conf);
+	}
+}
+
+NMGlobalDnsConfig *
+nm_config_data_get_global_dns_config (const NMConfigData *self)
+{
+	g_return_val_if_fail (NM_IS_CONFIG_DATA (self), NULL);
+
+	return NM_CONFIG_DATA_GET_PRIVATE (self)->global_dns;
+}
+
+static void
+global_dns_config_update_domain_list (NMGlobalDnsConfig *dns)
+{
+	guint length;
+
+	g_free (dns->domain_list);
+	dns->domain_list = (char **) g_hash_table_get_keys_as_array (dns->domains, &length);
+}
+
+static NMGlobalDnsConfig *
+load_global_dns (GKeyFile *keyfile, gboolean internal)
+{
+	NMGlobalDnsConfig *conf;
+	char *group, *domain_prefix;
+	gs_strfreev char **groups = NULL;
+	int g, i, j, domain_prefix_len;
+	gboolean default_found = FALSE;
+	char **strv;
+
+	group = internal
+	        ? NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS
+	        : NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS;
+	domain_prefix = internal
+	                ? NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN
+	                : NM_CONFIG_KEYFILE_GROUPPREFIX_GLOBAL_DNS_DOMAIN;
+	domain_prefix_len = strlen (domain_prefix);
+
+	if (!nm_config_keyfile_has_global_dns_config (keyfile, internal))
+		return NULL;
+
+	conf = g_malloc0 (sizeof (NMGlobalDnsConfig));
+	conf->domains = g_hash_table_new_full (g_str_hash, g_str_equal,
+	                                       g_free, (GDestroyNotify) global_dns_domain_free);
+
+	strv = g_key_file_get_string_list (keyfile, group, "searches", NULL, NULL);
+	if (strv)
+		conf->searches = _nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
+
+	strv = g_key_file_get_string_list (keyfile, group, "options", NULL, NULL);
+	if (strv) {
+		_nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
+		for (i = 0, j = 0; strv[i]; i++) {
+			if (_nm_utils_dns_option_validate (strv[i], NULL, NULL, TRUE, NULL))
+				strv[j++] = strv[i];
+			else
+				g_free (strv[i]);
+		}
+		strv[j] = NULL;
+		conf->options = strv;
+	}
+
+	groups = g_key_file_get_groups (keyfile, NULL);
+	for (g = 0; groups[g]; g++) {
+		char *name;
+		char **servers = NULL, **options = NULL;
+		NMGlobalDnsDomain *domain;
+
+		if (   !g_str_has_prefix (groups[g], domain_prefix)
+		    || !groups[g][domain_prefix_len])
+			continue;
+
+		strv = g_key_file_get_string_list (keyfile, groups[g], "servers", NULL, NULL);
+		if (strv) {
+			_nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
+			for (i = 0, j = 0; strv[i]; i++) {
+				if (   nm_utils_ipaddr_valid (AF_INET, strv[i])
+				    || nm_utils_ipaddr_valid (AF_INET6, strv[i]))
+					strv[j++] = strv[i];
+				else
+					g_free (strv[i]);
+			}
+			if (j) {
+				strv[j] = NULL;
+				servers = strv;
+			}
+			else
+				g_free (strv);
+		}
+
+		if (!servers)
+			continue;
+
+		strv = g_key_file_get_string_list (keyfile, groups[g], "options", NULL, NULL);
+		if (strv)
+			options = _nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
+
+		name = strdup (&groups[g][domain_prefix_len]);
+		domain = g_malloc0 (sizeof (NMGlobalDnsDomain));
+		domain->name = name;
+		domain->servers = servers;
+		domain->options = options;
+
+		g_hash_table_insert (conf->domains, strdup (name), domain);
+
+		if (!strcmp (name, "*"))
+			default_found = TRUE;
+	}
+
+	if (!default_found) {
+		nm_log_dbg (LOGD_CORE, "%s global DNS configuration is missing default domain, ignore it",
+		            internal ? "internal" : "user");
+		nm_global_dns_config_free (conf);
+		return NULL;
+	}
+
+	conf->internal = internal;
+	global_dns_config_update_domain_list (conf);
+	return conf;
+}
+
+
+void
+nm_global_dns_config_to_dbus (const NMGlobalDnsConfig *dns, GValue *value)
+{
+	GVariantBuilder conf_builder, domains_builder, domain_builder;
+	NMGlobalDnsDomain *domain;
+	GHashTableIter iter;
+
+	g_variant_builder_init (&conf_builder, G_VARIANT_TYPE ("a{sv}"));
+	if (!dns)
+		goto out;
+
+	if (dns->searches) {
+		g_variant_builder_add (&conf_builder, "{sv}", "searches",
+		                       g_variant_new_strv ((const char *const *) dns->searches, -1));
+	}
+
+	if (dns->options) {
+		g_variant_builder_add (&conf_builder, "{sv}", "options",
+		                       g_variant_new_strv ((const char *const *) dns->options, -1));
+	}
+
+	g_variant_builder_init (&domains_builder, G_VARIANT_TYPE ("a{sv}"));
+
+	g_hash_table_iter_init (&iter, dns->domains);
+	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &domain)) {
+
+		g_variant_builder_init (&domain_builder, G_VARIANT_TYPE ("a{sv}"));
+
+		if (domain->servers) {
+			g_variant_builder_add (&domain_builder, "{sv}", "servers",
+			                       g_variant_new_strv ((const char *const *) domain->servers, -1));
+		}
+		if (domain->options) {
+			g_variant_builder_add (&domain_builder, "{sv}", "options",
+			                       g_variant_new_strv ((const char *const *) domain->options, -1));
+		}
+
+		g_variant_builder_add (&domains_builder, "{sv}", domain->name,
+		                       g_variant_builder_end (&domain_builder));
+	}
+
+	g_variant_builder_add (&conf_builder, "{sv}", "domains",
+	                       g_variant_builder_end (&domains_builder));
+out:
+	g_value_take_variant (value, g_variant_builder_end (&conf_builder));
+}
+
+static NMGlobalDnsDomain *
+global_dns_domain_from_dbus (char *name, GVariant *variant)
+{
+	NMGlobalDnsDomain *domain;
+	GVariantIter iter;
+	char **strv, *key;
+	GVariant *val;
+	int i, j;
+
+	if (!g_variant_is_of_type (variant, G_VARIANT_TYPE ("a{sv}")))
+		return NULL;
+
+	domain = g_malloc0 (sizeof (NMGlobalDnsDomain));
+	domain->name = g_strdup (name);
+
+	g_variant_iter_init (&iter, variant);
+	while (g_variant_iter_next (&iter, "{&sv}", &key, &val)) {
+
+		if (   !g_strcmp0 (key, "servers")
+		    && g_variant_is_of_type (val, G_VARIANT_TYPE ("as"))) {
+			strv = g_variant_dup_strv (val, NULL);
+			_nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
+			for (i = 0, j = 0; strv && strv[i]; i++) {
+				if (   nm_utils_ipaddr_valid (AF_INET, strv[i])
+				    || nm_utils_ipaddr_valid (AF_INET6, strv[i]))
+					strv[j++] = strv[i];
+				else
+					g_free (strv[i]);
+			}
+			if (j) {
+				strv[j] = NULL;
+				domain->servers = strv;
+			} else
+				g_free (strv);
+		} else if (   !g_strcmp0 (key, "options")
+		           && g_variant_is_of_type (val, G_VARIANT_TYPE ("as"))) {
+			strv = g_variant_dup_strv (val, NULL);
+			domain->options = _nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
+		}
+
+		g_variant_unref (val);
+	}
+
+	/* At least one server is required */
+	if (!domain->servers) {
+		global_dns_domain_free (domain);
+		return NULL;
+	}
+
+	return domain;
+}
+
+NMGlobalDnsConfig *
+nm_global_dns_config_from_dbus (const GValue *value, GError **error)
+{
+	NMGlobalDnsConfig *dns_config;
+	GVariant *variant, *val;
+	GVariantIter iter;
+	char **strv, *key;
+	int i, j;
+
+	if (!G_VALUE_HOLDS_VARIANT (value)) {
+		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
+		             "invalid value type");
+		return NULL;
+	}
+
+	variant = g_value_get_variant (value);
+	if (!g_variant_is_of_type (variant, G_VARIANT_TYPE ("a{sv}"))) {
+		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
+		             "invalid variant type");
+		return NULL;
+	}
+
+	dns_config = g_malloc0 (sizeof (NMGlobalDnsConfig));
+	dns_config->domains = g_hash_table_new_full (g_str_hash, g_str_equal,
+	                                             g_free, (GDestroyNotify) global_dns_domain_free);
+
+	g_variant_iter_init (&iter, variant);
+	while (g_variant_iter_next (&iter, "{&sv}", &key, &val)) {
+
+		if (   !g_strcmp0 (key, "searches")
+		    && g_variant_is_of_type (val, G_VARIANT_TYPE ("as"))) {
+			strv = g_variant_dup_strv (val, NULL);
+			dns_config->searches = _nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
+		} else if (   !g_strcmp0 (key, "options")
+		           && g_variant_is_of_type (val, G_VARIANT_TYPE ("as"))) {
+			strv = g_variant_dup_strv (val, NULL);
+			_nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
+
+			for (i = 0, j = 0; strv && strv[i]; i++) {
+				if (_nm_utils_dns_option_validate (strv[i], NULL, NULL, TRUE, NULL))
+					strv[j++] = strv[i];
+				else
+					g_free (strv[i]);
+			}
+
+			if (strv)
+				strv[j] = NULL;
+
+			dns_config->options = strv;
+		} else if (   !g_strcmp0 (key, "domains")
+		           && g_variant_is_of_type (val, G_VARIANT_TYPE ("a{sv}"))) {
+			NMGlobalDnsDomain *domain;
+			GVariantIter domain_iter;
+			GVariant *v;
+			char *k;
+
+			g_variant_iter_init (&domain_iter, val);
+			while (g_variant_iter_next (&domain_iter, "{&sv}", &k, &v)) {
+				if (k) {
+					domain = global_dns_domain_from_dbus (k, v);
+					if (domain)
+						g_hash_table_insert (dns_config->domains, strdup (k), domain);
+				}
+				g_variant_unref (v);
+			}
+		}
+		g_variant_unref (val);
+	}
+
+	/* An empty value is valid and clears the internal configuration */
+	if (   !nm_global_dns_config_is_empty (dns_config)
+	    && !nm_global_dns_config_lookup_domain (dns_config, "*")) {
+		g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
+		                     "Global DNS configuration is missing the default domain");
+		nm_global_dns_config_free (dns_config);
+		return NULL;
+	}
+
+	global_dns_config_update_domain_list (dns_config);
+	return dns_config;
+}
+
+static gboolean
+global_dns_equal (NMGlobalDnsConfig *old, NMGlobalDnsConfig *new)
+{
+	NMGlobalDnsDomain *domain_old, *domain_new;
+	gpointer key, value_old, value_new;
+	GHashTableIter iter;
+
+	if (old == new)
+		return TRUE;
+
+	if (!old || !new)
+		return FALSE;
+
+	if (   !_nm_utils_strv_equal (old->options, new->options)
+	    || !_nm_utils_strv_equal (old->searches, new->searches))
+		return FALSE;
+
+	if ((!old->domains || !new->domains) && old->domains != new->domains)
+		return FALSE;
+
+	if (g_hash_table_size (old->domains) != g_hash_table_size (new->domains))
+		return FALSE;
+
+	g_hash_table_iter_init (&iter, old->domains);
+	while (g_hash_table_iter_next (&iter, &key, &value_old)) {
+		value_new = g_hash_table_lookup (new->domains, key);
+		if (!value_new)
+			return FALSE;
+
+		domain_old = value_old;
+		domain_new = value_new;
+
+		if (   !_nm_utils_strv_equal (domain_old->options, domain_new->options)
+		    || !_nm_utils_strv_equal (domain_old->servers, domain_new->servers))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+/************************************************************************/
+
 char *
 nm_config_data_get_connection_default (const NMConfigData *self,
                                        const char *property,
@@ -408,10 +1100,10 @@ _get_connection_info_init (ConnectionInfo *connection_info, GKeyFile *keyfile, c
 	/* pass ownership of @group on... */
 	connection_info->group_name = group;
 
-	connection_info->match_device.spec = nm_config_get_device_match_spec (keyfile,
-	                                                                      group,
-	                                                                      "match-device",
-	                                                                      &connection_info->match_device.has);
+	connection_info->match_device.spec = nm_config_get_match_spec (keyfile,
+	                                                               group,
+	                                                               "match-device",
+	                                                               &connection_info->match_device.has);
 	connection_info->stop_match = nm_config_keyfile_get_boolean (keyfile, group, "stop-match", FALSE);
 }
 
@@ -483,8 +1175,11 @@ nm_config_data_diff (NMConfigData *old_data, NMConfigData *new_data)
 	priv_old = NM_CONFIG_DATA_GET_PRIVATE (old_data);
 	priv_new = NM_CONFIG_DATA_GET_PRIVATE (new_data);
 
-	if (!_nm_keyfile_equals (priv_old->keyfile, priv_new->keyfile, TRUE))
-		changes |= NM_CONFIG_CHANGE_VALUES;
+	if (!_nm_keyfile_equals (priv_old->keyfile_user, priv_new->keyfile_user, TRUE))
+		changes |= NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_USER;
+
+	if (!_nm_keyfile_equals (priv_old->keyfile_intern, priv_new->keyfile_intern, TRUE))
+		changes |= NM_CONFIG_CHANGE_VALUES | NM_CONFIG_CHANGE_VALUES_INTERN;
 
 	if (   g_strcmp0 (nm_config_data_get_config_main_file (old_data), nm_config_data_get_config_main_file (new_data)) != 0
 	    || g_strcmp0 (nm_config_data_get_config_description (old_data), nm_config_data_get_config_description (new_data)) != 0)
@@ -502,6 +1197,12 @@ nm_config_data_diff (NMConfigData *old_data, NMConfigData *new_data)
 	if (g_strcmp0 (nm_config_data_get_dns_mode (old_data), nm_config_data_get_dns_mode (new_data)))
 		changes |= NM_CONFIG_CHANGE_DNS_MODE;
 
+	if (g_strcmp0 (nm_config_data_get_rc_manager (old_data), nm_config_data_get_rc_manager (new_data)))
+		changes |= NM_CONFIG_CHANGE_RC_MANAGER;
+
+	if (!global_dns_equal (priv_old->global_dns, priv_new->global_dns))
+		changes |= NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG;
+
 	return changes;
 }
 
@@ -554,10 +1255,21 @@ set_property (GObject *object,
 	case PROP_CONFIG_DESCRIPTION:
 		priv->config_description = g_value_dup_string (value);
 		break;
-	case PROP_KEYFILE:
-		priv->keyfile = g_value_dup_boxed (value);
-		if (!priv->keyfile)
-			priv->keyfile = nm_config_create_keyfile ();
+	case PROP_KEYFILE_USER:
+		priv->keyfile_user = g_value_dup_boxed (value);
+		if (   priv->keyfile_user
+		    && !_nm_keyfile_has_values (priv->keyfile_user)) {
+			g_key_file_unref (priv->keyfile_user);
+			priv->keyfile_user = NULL;
+		}
+		break;
+	case PROP_KEYFILE_INTERN:
+		priv->keyfile_intern = g_value_dup_boxed (value);
+		if (   priv->keyfile_intern
+		    && !_nm_keyfile_has_values (priv->keyfile_intern)) {
+			g_key_file_unref (priv->keyfile_intern);
+			priv->keyfile_intern = NULL;
+		}
 		break;
 	case PROP_NO_AUTO_DEFAULT:
 		{
@@ -607,10 +1319,13 @@ finalize (GObject *gobject)
 	g_strfreev (priv->no_auto_default.arr);
 
 	g_free (priv->dns_mode);
+	g_free (priv->rc_manager);
 
 	g_slist_free_full (priv->ignore_carrier, g_free);
 	g_slist_free_full (priv->assume_ipv6ll_only, g_free);
 
+	nm_global_dns_config_free (priv->global_dns);
+
 	if (priv->connection_infos) {
 		for (i = 0; priv->connection_infos[i].group_name; i++) {
 			g_free (priv->connection_infos[i].group_name);
@@ -620,8 +1335,14 @@ finalize (GObject *gobject)
 	}
 
 	g_key_file_unref (priv->keyfile);
+	if (priv->keyfile_user)
+		g_key_file_unref (priv->keyfile_user);
+	if (priv->keyfile_intern)
+		g_key_file_unref (priv->keyfile_intern);
 
 	G_OBJECT_CLASS (nm_config_data_parent_class)->finalize (gobject);
+
+	g_free (priv->value_cached);
 }
 
 static void
@@ -636,6 +1357,8 @@ constructed (GObject *object)
 	NMConfigDataPrivate *priv = NM_CONFIG_DATA_GET_PRIVATE (self);
 	char *interval;
 
+	priv->keyfile = _merge_keyfiles (priv->keyfile_user, priv->keyfile_intern);
+
 	priv->connection_infos = _get_connection_infos (priv->keyfile);
 
 	priv->connectivity.uri = nm_strstrip (g_key_file_get_string (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "uri", NULL));
@@ -650,11 +1373,16 @@ constructed (GObject *object)
 	g_free (interval);
 
 	priv->dns_mode = nm_strstrip (g_key_file_get_string (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "dns", NULL));
+	priv->rc_manager = nm_strstrip (g_key_file_get_string (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "rc-manager", NULL));
+
+	priv->ignore_carrier = nm_config_get_match_spec (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "ignore-carrier", NULL);
+	priv->assume_ipv6ll_only = nm_config_get_match_spec (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "assume-ipv6ll-only", NULL);
 
-	priv->ignore_carrier = nm_config_get_device_match_spec (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "ignore-carrier", NULL);
-	priv->assume_ipv6ll_only = nm_config_get_device_match_spec (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "assume-ipv6ll-only", NULL);
+	priv->no_auto_default.specs_config = nm_config_get_match_spec (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "no-auto-default", NULL);
 
-	priv->no_auto_default.specs_config = nm_config_get_device_match_spec (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "no-auto-default", NULL);
+	priv->global_dns = load_global_dns (priv->keyfile_user, FALSE);
+	if (!priv->global_dns)
+		priv->global_dns = load_global_dns (priv->keyfile_intern, TRUE);
 
 	G_OBJECT_CLASS (nm_config_data_parent_class)->constructed (object);
 }
@@ -663,17 +1391,33 @@ NMConfigData *
 nm_config_data_new (const char *config_main_file,
                     const char *config_description,
                     const char *const*no_auto_default,
-                    GKeyFile *keyfile)
+                    GKeyFile *keyfile_user,
+                    GKeyFile *keyfile_intern)
 {
 	return g_object_new (NM_TYPE_CONFIG_DATA,
 	                     NM_CONFIG_DATA_CONFIG_MAIN_FILE, config_main_file,
 	                     NM_CONFIG_DATA_CONFIG_DESCRIPTION, config_description,
-	                     NM_CONFIG_DATA_KEYFILE, keyfile,
+	                     NM_CONFIG_DATA_KEYFILE_USER, keyfile_user,
+	                     NM_CONFIG_DATA_KEYFILE_INTERN, keyfile_intern,
 	                     NM_CONFIG_DATA_NO_AUTO_DEFAULT, no_auto_default,
 	                     NULL);
 }
 
 NMConfigData *
+nm_config_data_new_update_keyfile_intern (const NMConfigData *base, GKeyFile *keyfile_intern)
+{
+	NMConfigDataPrivate *priv = NM_CONFIG_DATA_GET_PRIVATE (base);
+
+	return g_object_new (NM_TYPE_CONFIG_DATA,
+	                     NM_CONFIG_DATA_CONFIG_MAIN_FILE, priv->config_main_file,
+	                     NM_CONFIG_DATA_CONFIG_DESCRIPTION, priv->config_description,
+	                     NM_CONFIG_DATA_KEYFILE_USER, priv->keyfile_user, /* the keyfile is unchanged. It's safe to share it. */
+	                     NM_CONFIG_DATA_KEYFILE_INTERN, keyfile_intern,
+	                     NM_CONFIG_DATA_NO_AUTO_DEFAULT, priv->no_auto_default.arr,
+	                     NULL);
+}
+
+NMConfigData *
 nm_config_data_new_update_no_auto_default (const NMConfigData *base,
                                            const char *const*no_auto_default)
 {
@@ -682,7 +1426,8 @@ nm_config_data_new_update_no_auto_default (const NMConfigData *base,
 	return g_object_new (NM_TYPE_CONFIG_DATA,
 	                     NM_CONFIG_DATA_CONFIG_MAIN_FILE, priv->config_main_file,
 	                     NM_CONFIG_DATA_CONFIG_DESCRIPTION, priv->config_description,
-	                     NM_CONFIG_DATA_KEYFILE, priv->keyfile, /* the keyfile is unchanged. It's safe to share it. */
+	                     NM_CONFIG_DATA_KEYFILE_USER, priv->keyfile_user, /* the keyfile is unchanged. It's safe to share it. */
+	                     NM_CONFIG_DATA_KEYFILE_INTERN, priv->keyfile_intern,
 	                     NM_CONFIG_DATA_NO_AUTO_DEFAULT, no_auto_default,
 	                     NULL);
 }
@@ -717,8 +1462,16 @@ nm_config_data_class_init (NMConfigDataClass *config_class)
 	                          G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
-	      (object_class, PROP_KEYFILE,
-	       g_param_spec_boxed (NM_CONFIG_DATA_KEYFILE, "", "",
+	      (object_class, PROP_KEYFILE_USER,
+	       g_param_spec_boxed (NM_CONFIG_DATA_KEYFILE_USER, "", "",
+	                           G_TYPE_KEY_FILE,
+	                           G_PARAM_WRITABLE |
+	                           G_PARAM_CONSTRUCT_ONLY |
+	                           G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property
+	      (object_class, PROP_KEYFILE_INTERN,
+	       g_param_spec_boxed (NM_CONFIG_DATA_KEYFILE_INTERN, "", "",
 	                           G_TYPE_KEY_FILE,
 	                           G_PARAM_WRITABLE |
 	                           G_PARAM_CONSTRUCT_ONLY |