diff options
author | Beniamino Galvani <bgalvani@redhat.com> | 2015-10-01 09:17:42 +0200 |
---|---|---|
committer | Beniamino Galvani <bgalvani@redhat.com> | 2015-10-01 09:21:32 +0200 |
commit | f04b27bd1f8d0b31775489f691e371b1c65a1efd (patch) | |
tree | e7aeaa3fbd70878f8fd1a1cf49449992f8eb3bcd | |
parent | 804ec6fbcd9bae95b8fb17e1eab2b3a25a601b15 (diff) | |
parent | c83ac1ed412e8f220c507b9b9177c3bcf6800658 (diff) | |
download | NetworkManager-f04b27bd1f8d0b31775489f691e371b1c65a1efd.tar.gz |
core: merge branch 'bg/global-dns-conf-bgo750458'
Add support for a global DNS configuration read from user
configuration file or set through D-Bus.
https://bugzilla.gnome.org/show_bug.cgi?id=750458
-rw-r--r-- | include/nm-glib.h | 39 | ||||
-rw-r--r-- | introspection/nm-manager.xml | 14 | ||||
-rw-r--r-- | libnm-core/tests/test-general.c | 36 | ||||
-rw-r--r-- | man/NetworkManager.conf.xml.in | 66 | ||||
-rw-r--r-- | policy/org.freedesktop.NetworkManager.policy.in.in | 10 | ||||
-rw-r--r-- | src/dns-manager/nm-dns-dnsmasq.c | 69 | ||||
-rw-r--r-- | src/dns-manager/nm-dns-manager.c | 110 | ||||
-rw-r--r-- | src/dns-manager/nm-dns-plugin.c | 2 | ||||
-rw-r--r-- | src/dns-manager/nm-dns-plugin.h | 7 | ||||
-rw-r--r-- | src/dns-manager/nm-dns-unbound.c | 1 | ||||
-rw-r--r-- | src/nm-auth-utils.h | 1 | ||||
-rw-r--r-- | src/nm-config-data.c | 511 | ||||
-rw-r--r-- | src/nm-config-data.h | 21 | ||||
-rw-r--r-- | src/nm-config.c | 87 | ||||
-rw-r--r-- | src/nm-config.h | 10 | ||||
-rw-r--r-- | src/nm-manager.c | 100 | ||||
-rw-r--r-- | src/nm-manager.h | 1 | ||||
-rw-r--r-- | src/tests/config/Makefile.am | 2 | ||||
-rw-r--r-- | src/tests/config/NetworkManager.conf | 16 | ||||
-rw-r--r-- | src/tests/config/global-dns-disabled.conf | 8 | ||||
-rw-r--r-- | src/tests/config/global-dns-invalid.conf | 10 | ||||
-rw-r--r-- | src/tests/config/test-config.c | 78 |
22 files changed, 1131 insertions, 68 deletions
diff --git a/include/nm-glib.h b/include/nm-glib.h index 7e42016b94..9d497c4de2 100644 --- a/include/nm-glib.h +++ b/include/nm-glib.h @@ -303,4 +303,43 @@ _g_key_file_save_to_file (GKeyFile *key_file, #endif +#if !GLIB_CHECK_VERSION(2, 40, 0) || defined (NM_GLIB_COMPAT_H_TEST) +static inline gpointer * +_nm_g_hash_table_get_keys_as_array (GHashTable *hash_table, + guint *length) +{ + GHashTableIter iter; + gpointer key, *ret; + guint i = 0; + + g_return_val_if_fail (hash_table, NULL); + + ret = g_new0 (gpointer, g_hash_table_size (hash_table) + 1); + g_hash_table_iter_init (&iter, hash_table); + + while (g_hash_table_iter_next (&iter, &key, NULL)) + ret[i++] = key; + + ret[i] = NULL; + + if (length) + *length = i; + + return ret; +} +#endif +#if !GLIB_CHECK_VERSION(2, 40, 0) +#define g_hash_table_get_keys_as_array(hash_table, length) \ + G_GNUC_EXTENSION ({ \ + _nm_g_hash_table_get_keys_as_array (hash_table, length); \ + }) +#else +#define g_hash_table_get_keys_as_array(hash_table, length) \ + G_GNUC_EXTENSION ({ \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + (g_hash_table_get_keys_as_array) ((hash_table), (length)); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + }) +#endif + #endif /* __NM_GLIB_H__ */ diff --git a/introspection/nm-manager.xml b/introspection/nm-manager.xml index 8548072cee..88f477d62f 100644 --- a/introspection/nm-manager.xml +++ b/introspection/nm-manager.xml @@ -368,6 +368,20 @@ </tp:docstring> </property> + <property name="GlobalDnsConfiguration" type="a{sv}" access="readwrite"> + <tp:docstring> + Dictionary of global DNS settings where the key is one of + "searches", "options" and "domains". The values for the + "searches" and "options" keys are string arrays describing the + list of search domains and resolver options, respectively. + The value of the "domains" key is a second-level dictionary, + where each key is a domain name, and each key's value is a + third-level dictionary with the keys "servers" and + "options". "servers" is a string array of DNS servers, + "options" is a string array of domain-specific options. + </tp:docstring> + </property> + <signal name="PropertiesChanged"> <tp:docstring> NetworkManager's properties changed. diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c index 2253f0c3ba..4eb739e994 100644 --- a/libnm-core/tests/test-general.c +++ b/libnm-core/tests/test-general.c @@ -4534,6 +4534,41 @@ test_g_ptr_array_insert (void) /******************************************************************************/ +static void +test_g_hash_table_get_keys_as_array (void) +{ + GHashTable *table = g_hash_table_new (g_str_hash, g_str_equal); + guint length; + char **keys; + + g_hash_table_insert (table, "one", "1"); + g_hash_table_insert (table, "two", "2"); + g_hash_table_insert (table, "three", "3"); + + keys = (char **) _nm_g_hash_table_get_keys_as_array (table, &length); + g_assert (keys); + g_assert_cmpuint (length, ==, 3); + + g_assert ( !strcmp (keys[0], "one") + || !strcmp (keys[1], "one") + || !strcmp (keys[2], "one")); + + g_assert ( !strcmp (keys[0], "two") + || !strcmp (keys[1], "two") + || !strcmp (keys[2], "two")); + + g_assert ( !strcmp (keys[0], "three") + || !strcmp (keys[1], "three") + || !strcmp (keys[2], "three")); + + g_assert (!keys[3]); + + g_free (keys); + g_hash_table_unref (table); +} + +/******************************************************************************/ + static int _test_find_binary_search_cmp (gconstpointer a, gconstpointer b, gpointer dummy) { @@ -4903,6 +4938,7 @@ int main (int argc, char **argv) g_test_add_func ("/core/general/_nm_utils_ascii_str_to_int64", test_nm_utils_ascii_str_to_int64); g_test_add_func ("/core/general/nm_utils_is_power_of_two", test_nm_utils_is_power_of_two); g_test_add_func ("/core/general/_glib_compat_g_ptr_array_insert", test_g_ptr_array_insert); + g_test_add_func ("/core/general/_glib_compat_g_hash_table_get_keys_as_array", test_g_hash_table_get_keys_as_array); g_test_add_func ("/core/general/_nm_utils_ptrarray_find_binary_search", test_nm_utils_ptrarray_find_binary_search); g_test_add_func ("/core/general/_nm_utils_strstrdictkey", test_nm_utils_strstrdictkey); diff --git a/man/NetworkManager.conf.xml.in b/man/NetworkManager.conf.xml.in index 3dcf97e082..a8b7f3a38d 100644 --- a/man/NetworkManager.conf.xml.in +++ b/man/NetworkManager.conf.xml.in @@ -637,6 +637,72 @@ ipv6.ip6-privacy=1 </refsect1> <refsect1> + <title><literal>global-dns</literal> section</title> + <para>This section specifies global DNS settings that override + connection-specific configuration.</para> + <para> + <variablelist> + <varlistentry> + <term><varname>enable</varname></term> + <listitem> + <para> + Whether the global DNS configuration should be used. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><varname>searches</varname></term> + <listitem> + <para> + A list of search domains to be used during hostname lookup. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><varname>options</varname></term> + <listitem> + <para> + A list of of options to be passed to the hostname resolver. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + </refsect1> + + <refsect1> + <title><literal>global-dns-domain</literal> sections</title> + <para>Sections with a name starting with the "global-dns-domain-" + prefix allow to define global DNS configuration for specific + domains. The part of section name after "global-dns-domain-" + specifies the domain name a section applies to. More specific + domains have the precedence over less specific ones and the + default domain is represented by the wildcard "*". A default + domain section is mandatory. + </para> + <para> + <variablelist> + <varlistentry> + <term><varname>servers</varname></term> + <listitem> + <para> + A list of addresses of DNS servers to be used for the given domain. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><varname>options</varname></term> + <listitem> + <para> + A list of domain-specific DNS options. Not used at the moment. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + </refsect1> + + <refsect1> <title>Plugins</title> <variablelist> diff --git a/policy/org.freedesktop.NetworkManager.policy.in.in b/policy/org.freedesktop.NetworkManager.policy.in.in index daefd80f20..4df6d7e133 100644 --- a/policy/org.freedesktop.NetworkManager.policy.in.in +++ b/policy/org.freedesktop.NetworkManager.policy.in.in @@ -112,5 +112,15 @@ </defaults> </action> + <action id="org.freedesktop.NetworkManager.settings.modify.global-dns"> + <_description>Modify persistent global DNS configuration</_description> + <_message>System policy prevents modification of the persistent global DNS configuration</_message> + <defaults> + <allow_any>auth_admin_keep</allow_any> + <allow_inactive>auth_admin_keep</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + </action> + </policyconfig> diff --git a/src/dns-manager/nm-dns-dnsmasq.c b/src/dns-manager/nm-dns-dnsmasq.c index 59a341aa9a..63e37bf314 100644 --- a/src/dns-manager/nm-dns-dnsmasq.c +++ b/src/dns-manager/nm-dns-dnsmasq.c @@ -135,6 +135,30 @@ ip6_addr_to_string (const struct in6_addr *addr, const char *iface) return buf; } +static void +add_global_config (GString *str, const NMGlobalDnsConfig *config) +{ + guint i, j; + + g_return_if_fail (config); + + for (i = 0; i < nm_global_dns_config_get_num_domains (config); i++) { + NMGlobalDnsDomain *domain = nm_global_dns_config_get_domain (config, i); + const char *const *servers = nm_global_dns_domain_get_servers (domain); + + for (j = 0; servers && servers[j]; j++) { + if (!strcmp (servers[j], "*")) + g_string_append_printf (str, "server=%s\n", servers[j]); + else { + g_string_append_printf (str, "server=/%s/%s\n", + nm_global_dns_domain_get_name (domain), + servers[j]); + } + } + + } +} + static gboolean add_ip6_config (GString *str, NMIP6Config *ip6, gboolean split) { @@ -201,6 +225,7 @@ update (NMDnsPlugin *plugin, const GSList *vpn_configs, const GSList *dev_configs, const GSList *other_configs, + const NMGlobalDnsConfig *global_config, const char *hostname) { NMDnsDnsmasq *self = NM_DNS_DNSMASQ (plugin); @@ -229,28 +254,32 @@ update (NMDnsPlugin *plugin, /* Build up the new dnsmasq config file */ conf = g_string_sized_new (150); - /* Use split DNS for VPN configs */ - for (iter = (GSList *) vpn_configs; iter; iter = g_slist_next (iter)) { - if (NM_IS_IP4_CONFIG (iter->data)) - add_ip4_config (conf, NM_IP4_CONFIG (iter->data), TRUE); - else if (NM_IS_IP6_CONFIG (iter->data)) - add_ip6_config (conf, NM_IP6_CONFIG (iter->data), TRUE); - } + if (global_config) + add_global_config (conf, global_config); + else { + /* Use split DNS for VPN configs */ + for (iter = (GSList *) vpn_configs; iter; iter = g_slist_next (iter)) { + if (NM_IS_IP4_CONFIG (iter->data)) + add_ip4_config (conf, NM_IP4_CONFIG (iter->data), TRUE); + else if (NM_IS_IP6_CONFIG (iter->data)) + add_ip6_config (conf, NM_IP6_CONFIG (iter->data), TRUE); + } - /* Now add interface configs without split DNS */ - for (iter = (GSList *) dev_configs; iter; iter = g_slist_next (iter)) { - if (NM_IS_IP4_CONFIG (iter->data)) - add_ip4_config (conf, NM_IP4_CONFIG (iter->data), FALSE); - else if (NM_IS_IP6_CONFIG (iter->data)) - add_ip6_config (conf, NM_IP6_CONFIG (iter->data), FALSE); - } + /* Now add interface configs without split DNS */ + for (iter = (GSList *) dev_configs; iter; iter = g_slist_next (iter)) { + if (NM_IS_IP4_CONFIG (iter->data)) + add_ip4_config (conf, NM_IP4_CONFIG (iter->data), FALSE); + else if (NM_IS_IP6_CONFIG (iter->data)) + add_ip6_config (conf, NM_IP6_CONFIG (iter->data), FALSE); + } - /* And any other random configs */ - for (iter = (GSList *) other_configs; iter; iter = g_slist_next (iter)) { - if (NM_IS_IP4_CONFIG (iter->data)) - add_ip4_config (conf, NM_IP4_CONFIG (iter->data), FALSE); - else if (NM_IS_IP6_CONFIG (iter->data)) - add_ip6_config (conf, NM_IP6_CONFIG (iter->data), FALSE); + /* And any other random configs */ + for (iter = (GSList *) other_configs; iter; iter = g_slist_next (iter)) { + if (NM_IS_IP4_CONFIG (iter->data)) + add_ip4_config (conf, NM_IP4_CONFIG (iter->data), FALSE); + else if (NM_IS_IP6_CONFIG (iter->data)) + add_ip6_config (conf, NM_IP6_CONFIG (iter->data), FALSE); + } } /* Write out the config file */ diff --git a/src/dns-manager/nm-dns-manager.c b/src/dns-manager/nm-dns-manager.c index 58260a045c..fbf5da1201 100644 --- a/src/dns-manager/nm-dns-manager.c +++ b/src/dns-manager/nm-dns-manager.c @@ -667,7 +667,7 @@ update_resolv_conf (NMDnsManager *self, } static void -compute_hash (NMDnsManager *self, guint8 buffer[HASH_LEN]) +compute_hash (NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer[HASH_LEN]) { NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self); GChecksum *sum; @@ -677,6 +677,9 @@ compute_hash (NMDnsManager *self, guint8 buffer[HASH_LEN]) sum = g_checksum_new (G_CHECKSUM_SHA1); g_assert (len == g_checksum_type_get_length (G_CHECKSUM_SHA1)); + if (global) + nm_global_dns_config_update_checksum (global, sum); + if (priv->ip4_vpn_config) nm_ip4_config_hash (priv->ip4_vpn_config, sum, TRUE); if (priv->ip4_device_config) @@ -742,6 +745,38 @@ build_plugin_config_lists (NMDnsManager *self, } static gboolean +merge_global_dns_config (NMResolvConfData *rc, NMGlobalDnsConfig *global_conf) +{ + NMGlobalDnsDomain *default_domain; + const char *const *searches; + const char *const *options; + const char *const *servers; + gint i; + + if (!global_conf) + return FALSE; + + searches = nm_global_dns_config_get_searches (global_conf); + options = nm_global_dns_config_get_options (global_conf); + + for (i = 0; searches && searches[i]; i++) { + if (DOMAIN_IS_VALID (searches[i])) + add_string_item (rc->searches, searches[i]); + } + + for (i = 0; options && options[i]; i++) + add_string_item (rc->options, options[i]); + + default_domain = nm_global_dns_config_lookup_domain (global_conf, "*"); + g_assert (default_domain); + servers = nm_global_dns_domain_get_servers (default_domain); + for (i = 0; servers && servers[i]; i++) + add_string_item (rc->nameservers, servers[i]); + + return TRUE; +} + +static gboolean update_dns (NMDnsManager *self, gboolean no_caching, GError **error) @@ -758,6 +793,8 @@ update_dns (NMDnsManager *self, gboolean caching = FALSE, update = TRUE; gboolean resolv_conf_updated = FALSE; SpawnResult result = SR_ERROR; + NMConfigData *data; + NMGlobalDnsConfig *global_config; g_return_val_if_fail (!error || !*error, FALSE); @@ -771,8 +808,11 @@ update_dns (NMDnsManager *self, _LOGD ("update-dns: updating resolv.conf"); } + data = nm_config_get_data (priv->config); + global_config = nm_config_data_get_global_dns_config (data); + /* Update hash with config we're applying */ - compute_hash (self, priv->hash); + compute_hash (self, global_config, priv->hash); rc.nameservers = g_ptr_array_new (); rc.searches = g_ptr_array_new (); @@ -780,33 +820,37 @@ update_dns (NMDnsManager *self, rc.nis_domain = NULL; rc.nis_servers = g_ptr_array_new (); - if (priv->ip4_vpn_config) - merge_one_ip4_config (&rc, priv->ip4_vpn_config); - if (priv->ip4_device_config) - merge_one_ip4_config (&rc, priv->ip4_device_config); - - if (priv->ip6_vpn_config) - merge_one_ip6_config (&rc, priv->ip6_vpn_config); - if (priv->ip6_device_config) - merge_one_ip6_config (&rc, priv->ip6_device_config); - - for (iter = priv->configs; iter; iter = g_slist_next (iter)) { - if ( (iter->data == priv->ip4_vpn_config) - || (iter->data == priv->ip4_device_config) - || (iter->data == priv->ip6_vpn_config) - || (iter->data == priv->ip6_device_config)) - continue; + if (global_config) + merge_global_dns_config (&rc, global_config); + else { + if (priv->ip4_vpn_config) + merge_one_ip4_config (&rc, priv->ip4_vpn_config); + if (priv->ip4_device_config) + merge_one_ip4_config (&rc, priv->ip4_device_config); + + if (priv->ip6_vpn_config) + merge_one_ip6_config (&rc, priv->ip6_vpn_config); + if (priv->ip6_device_config) + merge_one_ip6_config (&rc, priv->ip6_device_config); + + for (iter = priv->configs; iter; iter = g_slist_next (iter)) { + if ( (iter->data == priv->ip4_vpn_config) + || (iter->data == priv->ip4_device_config) + || (iter->data == priv->ip6_vpn_config) + || (iter->data == priv->ip6_device_config)) + continue; - if (NM_IS_IP4_CONFIG (iter->data)) { - NMIP4Config *config = NM_IP4_CONFIG (iter->data); + if (NM_IS_IP4_CONFIG (iter->data)) { + NMIP4Config *config = NM_IP4_CONFIG (iter->data); - merge_one_ip4_config (&rc, config); - } else if (NM_IS_IP6_CONFIG (iter->data)) { - NMIP6Config *config = NM_IP6_CONFIG (iter->data); + merge_one_ip4_config (&rc, config); + } else if (NM_IS_IP6_CONFIG (iter->data)) { + NMIP6Config *config = NM_IP6_CONFIG (iter->data); - merge_one_ip6_config (&rc, config); - } else - g_assert_not_reached (); + merge_one_ip6_config (&rc, config); + } else + g_assert_not_reached (); + } } /* If the hostname is a FQDN ("dcbw.example.com"), then add the domain part of it @@ -879,13 +923,15 @@ update_dns (NMDnsManager *self, caching = TRUE; } - build_plugin_config_lists (self, &vpn_configs, &dev_configs, &other_configs); + if (!global_config) + build_plugin_config_lists (self, &vpn_configs, &dev_configs, &other_configs); _LOGD ("update-dns: updating plugin %s", plugin_name); if (!nm_dns_plugin_update (plugin, vpn_configs, dev_configs, other_configs, + global_config, priv->hostname)) { _LOGW ("update-dns: plugin %s update failed", plugin_name); @@ -1212,7 +1258,7 @@ nm_dns_manager_end_updates (NMDnsManager *self, const char *func) priv = NM_DNS_MANAGER_GET_PRIVATE (self); g_return_if_fail (priv->updates_queue > 0); - compute_hash (self, new); + compute_hash (self, nm_config_data_get_global_dns_config (nm_config_get_data (priv->config)), new); changed = (memcmp (new, priv->prev_hash, sizeof (new)) != 0) ? TRUE : FALSE; _LOGD ("(%s): DNS configuration %s", func, changed ? "changed" : "did not change"); @@ -1346,7 +1392,8 @@ config_changed_cb (NMConfig *config, if (NM_FLAGS_ANY (changes, NM_CONFIG_CHANGE_SIGHUP | NM_CONFIG_CHANGE_SIGUSR1 | NM_CONFIG_CHANGE_DNS_MODE | - NM_CONFIG_CHANGE_RC_MANAGER)) { + NM_CONFIG_CHANGE_RC_MANAGER | + NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG)) { if (!update_dns (self, TRUE, &error)) { _LOGW ("could not commit DNS changes: %s", error->message); g_clear_error (&error); @@ -1361,10 +1408,11 @@ nm_dns_manager_init (NMDnsManager *self) _LOGt ("creating..."); + priv->config = g_object_ref (nm_config_get ()); /* Set the initial hash */ - compute_hash (self, NM_DNS_MANAGER_GET_PRIVATE (self)->hash); + compute_hash (self, nm_config_data_get_global_dns_config (nm_config_get_data (priv->config)), + NM_DNS_MANAGER_GET_PRIVATE (self)->hash); - priv->config = g_object_ref (nm_config_get ()); g_signal_connect (G_OBJECT (priv->config), NM_CONFIG_SIGNAL_CONFIG_CHANGED, G_CALLBACK (config_changed_cb), diff --git a/src/dns-manager/nm-dns-plugin.c b/src/dns-manager/nm-dns-plugin.c index a53a7fca0a..5b3cc69fa4 100644 --- a/src/dns-manager/nm-dns-plugin.c +++ b/src/dns-manager/nm-dns-plugin.c @@ -56,6 +56,7 @@ nm_dns_plugin_update (NMDnsPlugin *self, const GSList *vpn_configs, const GSList *dev_configs, const GSList *other_configs, + const NMGlobalDnsConfig *global_config, const char *hostname) { g_return_val_if_fail (NM_DNS_PLUGIN_GET_CLASS (self)->update != NULL, FALSE); @@ -64,6 +65,7 @@ nm_dns_plugin_update (NMDnsPlugin *self, vpn_configs, dev_configs, other_configs, + global_config, hostname); } diff --git a/src/dns-manager/nm-dns-plugin.h b/src/dns-manager/nm-dns-plugin.h index 0a46759850..7ecaa424dc 100644 --- a/src/dns-manager/nm-dns-plugin.h +++ b/src/dns-manager/nm-dns-plugin.h @@ -21,6 +21,8 @@ #include "nm-default.h" +#include "nm-config-data.h" + #define NM_TYPE_DNS_PLUGIN (nm_dns_plugin_get_type ()) #define NM_DNS_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DNS_PLUGIN, NMDnsPlugin)) #define NM_DNS_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DNS_PLUGIN, NMDnsPluginClass)) @@ -46,13 +48,15 @@ typedef struct { * NMIP4Config or NMIP6Config objects from VPN connections, while * 'dev_configs' is a list of NMPI4Config or NMIP6Config objects from * active devices. 'other_configs' represent other IP configuration that - * may be in-use. Configs of the same IP version are sorted in priority + * may be in-use. 'global_config' is the optional global DNS + * configuration. Configs of the same IP version are sorted in priority * order. */ gboolean (*update) (NMDnsPlugin *self, const GSList *vpn_configs, const GSList *dev_configs, const GSList *other_configs, + const NMGlobalDnsConfig *global_config, const char *hostname); /* Subclasses should override and return TRUE if they start a local @@ -91,6 +95,7 @@ gboolean nm_dns_plugin_update (NMDnsPlugin *self, const GSList *vpn_configs, const GSList *dev_configs, const GSList *other_configs, + const NMGlobalDnsConfig *global_config, const char *hostname); /* For subclasses/plugins */ diff --git a/src/dns-manager/nm-dns-unbound.c b/src/dns-manager/nm-dns-unbound.c index 5723bf8229..08a7cd3ae0 100644 --- a/src/dns-manager/nm-dns-unbound.c +++ b/src/dns-manager/nm-dns-unbound.c @@ -31,6 +31,7 @@ update (NMDnsPlugin *plugin, const GSList *vpn_configs, const GSList *dev_configs, const GSList *other_configs, + const NMGlobalDnsConfig *global_config, const char *hostname) { /* TODO: We currently call a script installed with the dnssec-trigger diff --git a/src/nm-auth-utils.h b/src/nm-auth-utils.h index c6cad52a6d..6eb0fda07e 100644 --- a/src/nm-auth-utils.h +++ b/src/nm-auth-utils.h @@ -35,6 +35,7 @@ #define NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM "org.freedesktop.NetworkManager.settings.modify.system" #define NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN "org.freedesktop.NetworkManager.settings.modify.own" #define NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME "org.freedesktop.NetworkManager.settings.modify.hostname" +#define NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS "org.freedesktop.NetworkManager.settings.modify.global-dns" typedef struct NMAuthChain NMAuthChain; diff --git a/src/nm-config-data.c b/src/nm-config-data.c index a804e61a93..1d0d362577 100644 --- a/src/nm-config-data.c +++ b/src/nm-config-data.c @@ -72,10 +72,25 @@ typedef struct { 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, @@ -91,6 +106,7 @@ 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)) @@ -534,6 +550,492 @@ nm_config_data_log (const NMConfigData *self, const char *prefix) /************************************************************************/ +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_nonnull (domain); + 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 (!keyfile || !nm_config_keyfile_get_boolean (keyfile, group, NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_ENABLE, FALSE)) + 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, @@ -683,6 +1185,9 @@ nm_config_data_diff (NMConfigData *old_data, NMConfigData *new_data) 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; } @@ -804,6 +1309,8 @@ finalize (GObject *gobject) 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); @@ -858,6 +1365,10 @@ constructed (GObject *object) 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); } diff --git a/src/nm-config-data.h b/src/nm-config-data.h index 8b745f2eb0..4c0be72a8b 100644 --- a/src/nm-config-data.h +++ b/src/nm-config-data.h @@ -76,6 +76,7 @@ typedef enum { /*< flags >*/ NM_CONFIG_CHANGE_NO_AUTO_DEFAULT = (1L << 8), NM_CONFIG_CHANGE_DNS_MODE = (1L << 9), NM_CONFIG_CHANGE_RC_MANAGER = (1L << 10), + NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG = (1L << 11), _NM_CONFIG_CHANGE_LAST, NM_CONFIG_CHANGE_ALL = ((_NM_CONFIG_CHANGE_LAST - 1) << 1) - 1, @@ -89,6 +90,9 @@ typedef struct { GObjectClass parent; } NMConfigDataClass; +typedef struct _NMGlobalDnsConfig NMGlobalDnsConfig; +typedef struct _NMGlobalDnsDomain NMGlobalDnsDomain; + GType nm_config_data_get_type (void); NMConfigData *nm_config_data_new (const char *config_main_file, @@ -124,6 +128,7 @@ const char *nm_config_data_get_rc_manager (const NMConfigData *self); gboolean nm_config_data_get_ignore_carrier (const NMConfigData *self, NMDevice *device); gboolean nm_config_data_get_assume_ipv6ll_only (const NMConfigData *self, NMDevice *device); +NMGlobalDnsConfig *nm_config_data_get_global_dns_config (const NMConfigData *self); char *nm_config_data_get_connection_default (const NMConfigData *self, const char *property, @@ -135,6 +140,22 @@ gboolean nm_config_data_is_intern_atomic_group (const NMConfigData *self, const GKeyFile *nm_config_data_clone_keyfile_intern (const NMConfigData *self); +const char *const *nm_global_dns_config_get_searches (const NMGlobalDnsConfig *dns); +const char *const *nm_global_dns_config_get_options (const NMGlobalDnsConfig *dns); +guint nm_global_dns_config_get_num_domains (const NMGlobalDnsConfig *dns); +NMGlobalDnsDomain *nm_global_dns_config_get_domain (const NMGlobalDnsConfig *dns, guint i); +NMGlobalDnsDomain *nm_global_dns_config_lookup_domain (const NMGlobalDnsConfig *dns, const char *name); +const char *nm_global_dns_domain_get_name (const NMGlobalDnsDomain *domain); +const char *const *nm_global_dns_domain_get_servers (const NMGlobalDnsDomain *domain); +const char *const *nm_global_dns_domain_get_options (const NMGlobalDnsDomain *domain); +gboolean nm_global_dns_config_is_internal (const NMGlobalDnsConfig *dns); +gboolean nm_global_dns_config_is_empty (const NMGlobalDnsConfig *dns); +void nm_global_dns_config_update_checksum (const NMGlobalDnsConfig *dns, GChecksum *sum); +void nm_global_dns_config_free (NMGlobalDnsConfig *conf); + +NMGlobalDnsConfig *nm_global_dns_config_from_dbus (const GValue *value, GError **error); +void nm_global_dns_config_to_dbus (const NMGlobalDnsConfig *dns_config, GValue *value); + /* private accessors */ GKeyFile *_nm_config_data_get_keyfile (const NMConfigData *self); GKeyFile *_nm_config_data_get_keyfile_user (const NMConfigData *self); diff --git a/src/nm-config.c b/src/nm-config.c index 0ccb6e6af2..9edfac1b0a 100644 --- a/src/nm-config.c +++ b/src/nm-config.c @@ -1184,6 +1184,24 @@ intern_config_read (const char *filename, } out: + /* + * If user configuration specifies global DNS options, the DNS + * options in internal configuration must be deleted. Otherwise a + * deletion of options from user configuration may cause the + * internal options to appear again. + */ + if (nm_config_keyfile_get_boolean (keyfile_conf, NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS, NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_ENABLE, FALSE)) { + if (g_key_file_remove_group (keyfile_intern, NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS, NULL)) + needs_rewrite = TRUE; + for (g = 0; groups && groups[g]; g++) { + if ( g_str_has_prefix (groups[g], NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN) + && groups[g][STRLEN (NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN)]) { + g_key_file_remove_group (keyfile_intern, groups[g], NULL); + needs_rewrite = TRUE; + } + } + } + g_key_file_unref (keyfile); if (out_needs_rewrite) @@ -1413,6 +1431,73 @@ nm_config_get_device_match_spec (const GKeyFile *keyfile, const char *group, con /************************************************************************/ +gboolean +nm_config_set_global_dns (NMConfig *self, NMGlobalDnsConfig *global_dns, GError **error) +{ + NMConfigPrivate *priv; + GKeyFile *keyfile; + char **groups; + const NMGlobalDnsConfig *old_global_dns; + guint i; + + g_return_val_if_fail (NM_IS_CONFIG (self), FALSE); + + priv = NM_CONFIG_GET_PRIVATE (self); + g_return_val_if_fail (priv->config_data, FALSE); + + old_global_dns = nm_config_data_get_global_dns_config (priv->config_data); + if (old_global_dns && !nm_global_dns_config_is_internal (old_global_dns)) { + g_set_error_literal (error, 1, 0, + "Global DNS configuration already set via configuration file"); + return FALSE; + } + + keyfile = nm_config_data_clone_keyfile_intern (priv->config_data); + + /* Remove existing groups */ + g_key_file_remove_group (keyfile, NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS, NULL); + groups = g_key_file_get_groups (keyfile, NULL); + for (i = 0; groups[i]; i++) { + if (g_str_has_prefix (groups[i], NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN)) + g_key_file_remove_group (keyfile, groups[i], NULL); + } + g_strfreev (groups); + + /* An empty configuration removes everything from internal configuration file */ + if (nm_global_dns_config_is_empty (global_dns)) + goto done; + + /* Set new values */ + g_key_file_set_string (keyfile, NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS, NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_ENABLE, "yes"); + + nm_config_keyfile_set_string_list (keyfile, NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS, + "searches", nm_global_dns_config_get_searches (global_dns), + -1); + + nm_config_keyfile_set_string_list (keyfile, NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS, + "options", nm_global_dns_config_get_options (global_dns), + -1); + + for (i = 0; i < nm_global_dns_config_get_num_domains (global_dns); i++) { + NMGlobalDnsDomain *domain = nm_global_dns_config_get_domain (global_dns, i); + gs_free char *group_name; + + group_name = g_strdup_printf (NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN "%s", + nm_global_dns_domain_get_name (domain)); + + nm_config_keyfile_set_string_list (keyfile, group_name, "servers", + nm_global_dns_domain_get_servers (domain), -1); + nm_config_keyfile_set_string_list (keyfile, group_name, "options", + nm_global_dns_domain_get_options (domain), -1); + } + +done: + nm_config_set_values (self, keyfile, TRUE, FALSE); + g_key_file_unref (keyfile); + + return TRUE; +} + /** * nm_config_set_values: * @self: the NMConfig instance @@ -1593,6 +1678,8 @@ _change_flags_one_to_string (NMConfigChangeFlags flag) return "dns-mode"; case NM_CONFIG_CHANGE_RC_MANAGER: return "rc-manager"; + case NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG: + return "global-dns-config"; default: g_return_val_if_reached ("unknown"); } diff --git a/src/nm-config.h b/src/nm-config.h index 59fd5cf39a..0e96c3e85c 100644 --- a/src/nm-config.h +++ b/src/nm-config.h @@ -49,17 +49,20 @@ G_BEGIN_DECLS #define NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN ".intern." #define NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION "connection" +#define NM_CONFIG_KEYFILE_GROUPPREFIX_GLOBAL_DNS_DOMAIN "global-dns-domain-" #define NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST ".test-append-stringlist" #define NM_CONFIG_KEYFILE_GROUP_MAIN "main" #define NM_CONFIG_KEYFILE_GROUP_LOGGING "logging" #define NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY "connectivity" +#define NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS "global-dns" #define NM_CONFIG_KEYFILE_GROUP_KEYFILE "keyfile" #define NM_CONFIG_KEYFILE_GROUP_IFUPDOWN "ifupdown" #define NM_CONFIG_KEYFILE_GROUP_IFNET "ifnet" #define NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND "backend" +#define NM_CONFIG_KEYFILE_KEY_GLOBAL_DNS_ENABLE "enable" #define NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS ".was" #define NM_CONFIG_KEYFILE_KEY_IFNET_AUTO_REFRESH "auto_refresh" #define NM_CONFIG_KEYFILE_KEY_IFNET_MANAGED "managed" @@ -69,6 +72,11 @@ G_BEGIN_DECLS #define NM_CONFIG_KEYFILE_KEYPREFIX_WAS ".was." #define NM_CONFIG_KEYFILE_KEYPREFIX_SET ".set." +#define NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS \ + NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN NM_CONFIG_KEYFILE_GROUP_GLOBAL_DNS +#define NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN \ + NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN NM_CONFIG_KEYFILE_GROUPPREFIX_GLOBAL_DNS_DOMAIN + typedef struct NMConfigCmdLineOptions NMConfigCmdLineOptions; struct _NMConfig { @@ -139,6 +147,8 @@ GSList *nm_config_get_device_match_spec (const GKeyFile *keyfile, const char *gr void _nm_config_sort_groups (char **groups, gsize ngroups); +gboolean nm_config_set_global_dns (NMConfig *self, NMGlobalDnsConfig *global_dns, GError **error); + G_END_DECLS #endif /* __NETWORKMANAGER_CONFIG_H__ */ diff --git a/src/nm-manager.c b/src/nm-manager.c index 107039f40a..1253f99188 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -181,6 +181,7 @@ enum { PROP_ACTIVATING_CONNECTION, PROP_DEVICES, PROP_METERED, + PROP_GLOBAL_DNS_CONFIGURATION, /* Not exported */ PROP_HOSTNAME, @@ -424,6 +425,9 @@ _config_changed_cb (NMConfig *config, NMConfigData *config_data, NMConfigChangeF NM_CONNECTIVITY_INTERVAL, nm_config_data_get_connectivity_interval (config_data), NM_CONNECTIVITY_RESPONSE, nm_config_data_get_connectivity_response (config_data), NULL); + + if (NM_FLAGS_HAS (changes, NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG)) + g_object_notify (G_OBJECT (self), NM_MANAGER_GLOBAL_DNS_CONFIGURATION); } /************************************************************************/ @@ -4430,7 +4434,6 @@ typedef struct { char *audit_prop_value; GType interface_type; const char *glib_propname; - gboolean set_enable; } PropertyFilterData; static void @@ -4453,9 +4456,12 @@ prop_set_auth_done_cb (NMAuthChain *chain, PropertyFilterData *pfd = user_data; NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (pfd->self); NMAuthCallResult result; - GDBusMessage *reply; + GDBusMessage *reply = NULL; const char *error_message; NMExportedObject *object; + const NMGlobalDnsConfig *global_dns; + gs_unref_variant GVariant *value = NULL; + GVariant *args; priv->auth_chains = g_slist_remove (priv->auth_chains, chain); result = nm_auth_chain_get_result (chain, pfd->permission); @@ -4485,9 +4491,29 @@ prop_set_auth_done_cb (NMAuthChain *chain, goto done; } - /* ... but set the property on the @object itself. It would be correct to set the property - * on the skeleton interface, but as it is now, the result is the same. */ - g_object_set (object, pfd->glib_propname, pfd->set_enable, NULL); + args = g_dbus_message_get_body (pfd->message); + g_variant_get (args, "(&s&sv)", NULL, NULL, &value); + g_assert (pfd->glib_propname); + + if (!strcmp (pfd->glib_propname, NM_MANAGER_GLOBAL_DNS_CONFIGURATION)) { + g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("a{sv}"))); + global_dns = nm_config_data_get_global_dns_config (nm_config_get_data (priv->config)); + + if (global_dns && !nm_global_dns_config_is_internal (global_dns)) { + reply = g_dbus_message_new_method_error (pfd->message, + NM_PERM_DENIED_ERROR, + (error_message = "Global DNS configuration already set via configuration file")); + goto done; + } + /* ... but set the property on the @object itself. It would be correct to set the property + * on the skeleton interface, but as it is now, the result is the same. */ + g_object_set (object, pfd->glib_propname, value, NULL); + } else { + g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)); + /* the same here */ + g_object_set (object, pfd->glib_propname, g_variant_get_boolean (value), NULL); + } + reply = g_dbus_message_new_method_reply (pfd->message); g_dbus_message_set_body (reply, g_variant_new_tuple (NULL, 0)); error_message = NULL; @@ -4552,14 +4578,15 @@ prop_filter (GDBusConnection *connection, gpointer user_data) { gs_unref_object NMManager *self = NULL; - GVariant *args, *value = NULL; + GVariant *args; const char *propiface = NULL; const char *propname = NULL; const char *glib_propname = NULL, *permission = NULL; const char *audit_op = NULL; - gboolean set_enable; GType interface_type = G_TYPE_INVALID; PropertyFilterData *pfd; + const GVariantType *expected_type = G_VARIANT_TYPE_BOOLEAN; + gs_unref_variant GVariant *value = NULL; self = g_weak_ref_get (user_data); if (!self) @@ -4576,17 +4603,10 @@ prop_filter (GDBusConnection *connection, || g_strcmp0 (g_dbus_message_get_member (message), "Set") != 0) return message; - /* Only filter calls with correct arguments (all filtered properties are boolean) */ args = g_dbus_message_get_body (message); if (!g_variant_is_of_type (args, G_VARIANT_TYPE ("(ssv)"))) return message; g_variant_get (args, "(&s&sv)", &propiface, &propname, &value); - if (!g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)) { - g_variant_unref (value); - return message; - } - set_enable = g_variant_get_boolean (value); - g_variant_unref (value); /* Only filter calls to filtered properties, on existing objects */ if (!strcmp (propiface, NM_DBUS_INTERFACE)) { @@ -4602,6 +4622,11 @@ prop_filter (GDBusConnection *connection, glib_propname = NM_MANAGER_WIMAX_ENABLED; permission = NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX; audit_op = NM_AUDIT_OP_RADIO_CONTROL; + } else if (!strcmp (propname, "GlobalDnsConfiguration")) { + glib_propname = NM_MANAGER_GLOBAL_DNS_CONFIGURATION; + permission = NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS; + audit_op = NM_AUDIT_OP_NET_CONTROL; + expected_type = G_VARIANT_TYPE ("a{sv}"); } else return message; interface_type = NMDBUS_TYPE_MANAGER_SKELETON; @@ -4620,6 +4645,9 @@ prop_filter (GDBusConnection *connection, } else return message; + if (!g_variant_is_of_type (value, expected_type)) + return message; + /* This filter function is called from a gdbus worker thread which we can't * make other D-Bus calls from. In particular, we cannot call * org.freedesktop.DBus.GetConnectionUnixUser to find the remote UID. @@ -4632,9 +4660,13 @@ prop_filter (GDBusConnection *connection, pfd->permission = permission; pfd->interface_type = interface_type; pfd->glib_propname = glib_propname; - pfd->set_enable = set_enable; pfd->audit_op = audit_op; - pfd->audit_prop_value = g_strdup_printf ("%s:%d", pfd->glib_propname, pfd->set_enable); + if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)) { + pfd->audit_prop_value = g_strdup_printf ("%s:%d", pfd->glib_propname, + g_variant_get_boolean (value)); + } else + pfd->audit_prop_value = g_strdup (pfd->glib_propname); + g_idle_add (do_set_property_check, pfd); return NULL; @@ -5028,6 +5060,8 @@ get_property (GObject *object, guint prop_id, { NMManager *self = NM_MANAGER (object); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); + NMConfigData *config_data; + const NMGlobalDnsConfig *dns_config; const char *type; switch (prop_id) { @@ -5097,6 +5131,11 @@ get_property (GObject *object, guint prop_id, case PROP_METERED: g_value_set_uint (value, priv->metered); break; + case PROP_GLOBAL_DNS_CONFIGURATION: + config_data = nm_config_get_data (priv->config); + dns_config = nm_config_data_get_global_dns_config (config_data); + nm_global_dns_config_to_dbus (dns_config, value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -5109,6 +5148,8 @@ set_property (GObject *object, guint prop_id, { NMManager *self = NM_MANAGER (object); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); + NMGlobalDnsConfig *dns_config; + GError *error = NULL; switch (prop_id) { case PROP_NETWORKING_ENABLED: @@ -5128,6 +5169,18 @@ set_property (GObject *object, guint prop_id, case PROP_WIMAX_ENABLED: /* WIMAX is depreacted. This does nothing. */ break; + case PROP_GLOBAL_DNS_CONFIGURATION: + dns_config = nm_global_dns_config_from_dbus (value, &error); + if (!error) + nm_config_set_global_dns (priv->config, dns_config, &error); + + nm_global_dns_config_free (dns_config); + + if (error) { + nm_log_dbg (LOGD_CORE, "set global DNS failed with error: %s", error->message); + g_error_free (error); + } + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -5395,6 +5448,21 @@ nm_manager_class_init (NMManagerClass *manager_class) G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + /** + * NMManager:global-dns-configuration: + * + * The global DNS configuration. + * + * Since: 1.2 + **/ + g_object_class_install_property + (object_class, PROP_GLOBAL_DNS_CONFIGURATION, + g_param_spec_variant (NM_MANAGER_GLOBAL_DNS_CONFIGURATION, "", "", + G_VARIANT_TYPE ("a{sv}"), + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + /* signals */ signals[DEVICE_ADDED] = g_signal_new ("device-added", diff --git a/src/nm-manager.h b/src/nm-manager.h index 39123f8b41..7807f450c8 100644 --- a/src/nm-manager.h +++ b/src/nm-manager.h @@ -49,6 +49,7 @@ #define NM_MANAGER_ACTIVATING_CONNECTION "activating-connection" #define NM_MANAGER_DEVICES "devices" #define NM_MANAGER_METERED "metered" +#define NM_MANAGER_GLOBAL_DNS_CONFIGURATION "global-dns-configuration" /* Not exported */ #define NM_MANAGER_HOSTNAME "hostname" diff --git a/src/tests/config/Makefile.am b/src/tests/config/Makefile.am index cc1ffb387b..6ad9876f1f 100644 --- a/src/tests/config/Makefile.am +++ b/src/tests/config/Makefile.am @@ -30,6 +30,8 @@ TESTS = test-config EXTRA_DIST = \ NetworkManager.conf \ bad.conf \ + global-dns-disabled.conf \ + global-dns-invalid.conf \ conf.d/00-overrides.conf \ conf.d/10-more.conf \ conf.d/90-last.conf diff --git a/src/tests/config/NetworkManager.conf b/src/tests/config/NetworkManager.conf index a750c801ee..da7b1fd421 100644 --- a/src/tests/config/NetworkManager.conf +++ b/src/tests/config/NetworkManager.conf @@ -81,3 +81,19 @@ ord.key07=A-0.3.07 ord.key08=A-0.3.08 ord.key09=A-0.3.09 ord.ovw01=A-0.3.ovw01 + +[global-dns] +enable=yes +searches=foo.com,bar.org +options=debug,edns0 + +[global-dns-domain-*] +servers=1.1.1.1,bad,1::128 +options=opt1,opt2 + +[global-dns-domain-example.com] +servers=2.2.2.2 + +# Invalid section: 'servers' key is missing +[global-dns-domain-test.com] +options=opt3 diff --git a/src/tests/config/global-dns-disabled.conf b/src/tests/config/global-dns-disabled.conf new file mode 100644 index 0000000000..53ccd832e2 --- /dev/null +++ b/src/tests/config/global-dns-disabled.conf @@ -0,0 +1,8 @@ +[global-dns] +enable=no +searches=foo.com +options=timeout:5 + +[global-dns-domain-*] +servers=1.2.3.4 +options=myoption diff --git a/src/tests/config/global-dns-invalid.conf b/src/tests/config/global-dns-invalid.conf new file mode 100644 index 0000000000..10ecc9b04c --- /dev/null +++ b/src/tests/config/global-dns-invalid.conf @@ -0,0 +1,10 @@ +# Invalid configuration, since there isn't a default domain section + +[global-dns] +enable=yes +searches=foo.com +options=timeout:5 + +[global-dns-domain-test.com] +servers=1.2.3.4 +options=myoption diff --git a/src/tests/config/test-config.c b/src/tests/config/test-config.c index b021fe028a..d3b2db6920 100644 --- a/src/tests/config/test-config.c +++ b/src/tests/config/test-config.c @@ -244,6 +244,83 @@ test_config_override (void) } static void +test_config_global_dns (void) +{ + NMConfig *config; + const NMGlobalDnsConfig *dns; + NMGlobalDnsDomain *domain; + const char *const *strv; + + config = setup_config (NULL, SRCDIR "/NetworkManager.conf", "", NULL, + "/no/such/dir", "", NULL); + + dns = nm_config_data_get_global_dns_config (nm_config_get_data_orig (config)); + g_assert (dns); + + strv = nm_global_dns_config_get_searches (dns); + g_assert (strv); + g_assert_cmpuint (g_strv_length ((char **) strv), ==, 2); + g_assert_cmpstr (strv[0], ==, "foo.com"); + g_assert_cmpstr (strv[1], ==, "bar.org"); + + strv = nm_global_dns_config_get_options (dns); + g_assert (strv); + g_assert_cmpuint (g_strv_length ((char **) strv), ==, 2); + g_assert_cmpstr (strv[0], ==, "debug"); + g_assert_cmpstr (strv[1], ==, "edns0"); + + g_assert_cmpuint (nm_global_dns_config_get_num_domains (dns), ==, 2); + + /* Default domain */ + domain = nm_global_dns_config_lookup_domain (dns, "*"); + g_assert (domain); + + strv = nm_global_dns_domain_get_servers (domain); + g_assert (strv); + g_assert_cmpuint (g_strv_length ((char **) strv), ==, 2); + g_assert_cmpstr (strv[0], ==, "1.1.1.1"); + g_assert_cmpstr (strv[1], ==, "1::128"); + + strv = nm_global_dns_domain_get_options (domain); + g_assert (strv); + g_assert_cmpuint (g_strv_length ((char **) strv), ==, 2); + g_assert_cmpstr (strv[0], ==, "opt1"); + g_assert_cmpstr (strv[1], ==, "opt2"); + + /* 'example.com' domain */ + domain = nm_global_dns_config_lookup_domain (dns, "example.com"); + g_assert (domain); + + strv = nm_global_dns_domain_get_servers (domain); + g_assert (strv); + g_assert_cmpuint (g_strv_length ((char **) strv), ==, 1); + g_assert_cmpstr (strv[0], ==, "2.2.2.2"); + + strv = nm_global_dns_domain_get_options (domain); + g_assert (!strv || g_strv_length ((char **) strv) == 0); + + /* Non-existent domain 'test.com' */ + domain = nm_global_dns_config_lookup_domain (dns, "test.com"); + g_assert (!domain); + + g_object_unref (config); + + /* Check that a file without "enable=yes" gives a NULL configuration */ + config = setup_config (NULL, SRCDIR "/global-dns-disabled.conf", "", NULL, + "/no/such/dir", "", NULL); + dns = nm_config_data_get_global_dns_config (nm_config_get_data_orig (config)); + g_assert (!dns); + g_object_unref (config); + + /* Check that a file without a default domain section gives a NULL configuration */ + config = setup_config (NULL, SRCDIR "/global-dns-invalid.conf", "", NULL, + "/no/such/dir", "", NULL); + dns = nm_config_data_get_global_dns_config (nm_config_get_data_orig (config)); + g_assert (!dns); + g_object_unref (config); +} + +static void test_config_no_auto_default (void) { NMConfig *config; @@ -852,6 +929,7 @@ main (int argc, char **argv) g_test_add_func ("/config/confdir-parse-error", test_config_confdir_parse_error); g_test_add_func ("/config/set-values", test_config_set_values); + g_test_add_func ("/config/global-dns", test_config_global_dns); g_test_add_func ("/config/signal", test_config_signal); |