Commit c83ac1ed authored by Beniamino Galvani's avatar Beniamino Galvani

manager: export DNS global configuration D-Bus property

parent dd6fbe7b
......@@ -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.
......
......@@ -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>
......@@ -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;
......
......@@ -812,6 +812,188 @@ load_global_dns (GKeyFile *keyfile, gboolean internal)
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)
{
......
......@@ -153,6 +153,9 @@ 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);
......
......@@ -1431,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
......
......@@ -147,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__ */
......
......@@ -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",
......
......@@ -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"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment