about summary refs log tree commit diff
path: root/src/NetworkManagerUtils.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2016-01-20 16:26:51 +0100
committerMichael Biebl <biebl@debian.org>2016-01-20 16:26:51 +0100
commit494f296a3baab08522617b24b1f126d8f9a17502 (patch)
treec8ef32fb0dd1c4ff35a0b38e787abb58692de0cd /src/NetworkManagerUtils.c
parent54f6333410ffd570e62717d9e77c5c987175e397 (diff)
Imported Upstream version 1.1.90 upstream/1.1.90
Diffstat (limited to 'src/NetworkManagerUtils.c')
-rw-r--r--src/NetworkManagerUtils.c880
1 files changed, 651 insertions, 229 deletions
diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c
index 15e03276..fb680d96 100644
--- a/src/NetworkManagerUtils.c
+++ b/src/NetworkManagerUtils.c
@@ -21,9 +21,6 @@
 
 #include "config.h"
 
-#include <glib.h>
-#include <gio/gio.h>
-#include <glib/gi18n.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <string.h>
@@ -32,14 +29,15 @@
 #include <resolv.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <sys/stat.h>
 #include <linux/if.h>
 #include <linux/if_infiniband.h>
 
+#include "nm-default.h"
 #include "NetworkManagerUtils.h"
 #include "nm-platform.h"
 #include "nm-utils.h"
 #include "nm-core-internal.h"
-#include "nm-logging.h"
 #include "nm-device.h"
 #include "nm-setting-connection.h"
 #include "nm-setting-ip4-config.h"
@@ -47,8 +45,6 @@
 #include "nm-setting-wireless.h"
 #include "nm-setting-wireless-security.h"
 #include "nm-auth-utils.h"
-#include "nm-dbus-glib-types.h"
-#include "gsystem-local-alloc.h"
 
 /*
  * Some toolchains (E.G. uClibc 0.9.33 and earlier) don't export
@@ -113,6 +109,120 @@ _nm_utils_set_testing (NMUtilsTestFlags flags)
 	}
 }
 
+/*****************************************************************************/
+
+G_DEFINE_QUARK (nm-utils-error-quark, nm_utils_error)
+
+void
+nm_utils_error_set_cancelled (GError **error,
+                              gboolean is_disposing,
+                              const char *instance_name)
+{
+	if (is_disposing) {
+		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING,
+		             "Disposing %s instance",
+		             instance_name && *instance_name ? instance_name : "source");
+	} else {
+		g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+		                     "Request cancelled");
+	}
+}
+
+gboolean
+nm_utils_error_is_cancelled (GError *error,
+                             gboolean consider_is_disposing)
+{
+	if (error) {
+		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			return TRUE;
+		if (   consider_is_disposing
+		    && g_error_matches (error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING))
+			return TRUE;
+	}
+	return FALSE;
+}
+
+/*****************************************************************************/
+
+static GSList *_singletons = NULL;
+static gboolean _singletons_shutdown = FALSE;
+
+static void
+_nm_singleton_instance_weak_cb (gpointer data,
+                                GObject *where_the_object_was)
+{
+	_singletons = g_slist_remove (_singletons, where_the_object_was);
+}
+
+static void __attribute__((destructor))
+_nm_singleton_instance_destroy (void)
+{
+	_singletons_shutdown = TRUE;
+
+	while (_singletons) {
+		GObject *instance = _singletons->data;
+
+		_singletons = g_slist_delete_link (_singletons, _singletons);
+
+		g_object_weak_unref (instance, _nm_singleton_instance_weak_cb, NULL);
+
+		if (instance->ref_count > 1)
+			nm_log_dbg (LOGD_CORE, "disown %s singleton (%p)", G_OBJECT_TYPE_NAME (instance), instance);
+
+		g_object_unref (instance);
+	}
+}
+
+void
+_nm_singleton_instance_register_destruction (GObject *instance)
+{
+	g_return_if_fail (G_IS_OBJECT (instance));
+
+	/* Don't allow registration after shutdown. We only destroy the singletons
+	 * once. */
+	g_return_if_fail (!_singletons_shutdown);
+
+	g_object_weak_ref (instance, _nm_singleton_instance_weak_cb, NULL);
+
+	_singletons = g_slist_prepend (_singletons, instance);
+}
+
+/*****************************************************************************/
+
+gint
+nm_utils_ascii_str_to_bool (const char *str,
+                            gint default_value)
+{
+	gsize len;
+	char *s = NULL;
+
+	if (!str)
+		return default_value;
+
+	while (str[0] && g_ascii_isspace (str[0]))
+		str++;
+
+	if (!str[0])
+		return default_value;
+
+	len = strlen (str);
+	if (g_ascii_isspace (str[len - 1])) {
+		s = g_strdup (str);
+		g_strchomp (s);
+		str = s;
+	}
+
+	if (!g_ascii_strcasecmp (str, "true") || !g_ascii_strcasecmp (str, "yes") || !g_ascii_strcasecmp (str, "on") || !g_ascii_strcasecmp (str, "1"))
+		default_value = TRUE;
+	else if (!g_ascii_strcasecmp (str, "false") || !g_ascii_strcasecmp (str, "no") || !g_ascii_strcasecmp (str, "off") || !g_ascii_strcasecmp (str, "0"))
+		default_value = FALSE;
+	if (s)
+		g_free (s);
+	return default_value;
+}
+
+/*****************************************************************************/
+
 /*
  * nm_ethernet_address_is_valid:
  * @addr: pointer to a binary or ASCII Ethernet address
@@ -433,7 +543,7 @@ out:
 
 typedef struct {
 	pid_t pid;
-	guint64 log_domain;
+	NMLogDomain log_domain;
 	union {
 		struct {
 			gint64 wait_start_us;
@@ -455,7 +565,7 @@ typedef struct {
 #define LOG_NAME_ARGS log_name,(long)pid
 
 static KillChildAsyncData *
-_kc_async_data_alloc (pid_t pid, guint64 log_domain, const char *log_name, NMUtilsKillChildAsyncCb callback, void *user_data)
+_kc_async_data_alloc (pid_t pid, NMLogDomain log_domain, const char *log_name, NMUtilsKillChildAsyncCb callback, void *user_data)
 {
 	KillChildAsyncData *data;
 	size_t log_name_len;
@@ -570,7 +680,7 @@ _kc_invoke_callback_idle (gpointer user_data)
 }
 
 static void
-_kc_invoke_callback (pid_t pid, guint64 log_domain, const char *log_name, NMUtilsKillChildAsyncCb callback, void *user_data, gboolean success, int child_status)
+_kc_invoke_callback (pid_t pid, NMLogDomain log_domain, const char *log_name, NMUtilsKillChildAsyncCb callback, void *user_data, gboolean success, int child_status)
 {
 	KillChildAsyncData *data;
 
@@ -602,7 +712,7 @@ _kc_invoke_callback (pid_t pid, guint64 log_domain, const char *log_name, NMUtil
  * supports only one watcher per child.
  **/
 void
-nm_utils_kill_child_async (pid_t pid, int sig, guint64 log_domain,
+nm_utils_kill_child_async (pid_t pid, int sig, NMLogDomain log_domain,
                            const char *log_name, guint32 wait_before_kill_msec,
                            NMUtilsKillChildAsyncCb callback, void *user_data)
 {
@@ -707,7 +817,7 @@ _sleep_duration_convert_ms_to_us (guint32 sleep_duration_msec)
  * In case of error, errno is preserved to contain the last reason of failure.
  **/
 gboolean
-nm_utils_kill_child_sync (pid_t pid, int sig, guint64 log_domain, const char *log_name,
+nm_utils_kill_child_sync (pid_t pid, int sig, NMLogDomain log_domain, const char *log_name,
                           int *child_status, guint32 wait_before_kill_msec,
                           guint32 sleep_duration_msec)
 {
@@ -885,7 +995,7 @@ out:
  * process with PID @pid is gone, the process is a zombie, or @max_wait_msec expires.
  **/
 void
-nm_utils_kill_process_sync (pid_t pid, guint64 start_time, int sig, guint64 log_domain,
+nm_utils_kill_process_sync (pid_t pid, guint64 start_time, int sig, NMLogDomain log_domain,
                             const char *log_name, guint32 wait_before_kill_msec,
                             guint32 sleep_duration_msec, guint32 max_wait_msec)
 {
@@ -1084,6 +1194,22 @@ nm_utils_find_helper(const char *progname, const char *try_first, GError **error
 #define DEVICE_TYPE_TAG "type:"
 #define SUBCHAN_TAG "s390-subchannels:"
 #define EXCEPT_TAG "except:"
+#define MATCH_TAG_CONFIG_NM_VERSION             "nm-version:"
+#define MATCH_TAG_CONFIG_NM_VERSION_MIN         "nm-version-min:"
+#define MATCH_TAG_CONFIG_NM_VERSION_MAX         "nm-version-max:"
+#define MATCH_TAG_CONFIG_ENV                    "env:"
+
+#define _spec_has_prefix(pspec, tag) \
+	({ \
+		const char **_spec = (pspec); \
+		gboolean _has = FALSE; \
+		\
+		if (!g_ascii_strncasecmp (*_spec, (""tag), STRLEN (tag))) { \
+			*_spec += STRLEN (tag); \
+			_has = TRUE; \
+		} \
+		_has; \
+	})
 
 static const char *
 _match_except (const char *spec_str, gboolean *out_except)
@@ -1284,6 +1410,9 @@ nm_match_spec_s390_subchannels (const GSList *specs, const char *subchannels)
 
 	g_return_val_if_fail (subchannels != NULL, NM_MATCH_SPEC_NO_MATCH);
 
+	if (!specs)
+		return NM_MATCH_SPEC_NO_MATCH;
+
 	if (!parse_subchannels (subchannels, &a, &b, &c))
 		return NM_MATCH_SPEC_NO_MATCH;
 
@@ -1310,6 +1439,108 @@ nm_match_spec_s390_subchannels (const GSList *specs, const char *subchannels)
 	return match;
 }
 
+static gboolean
+_match_config_nm_version (const char *str, const char *tag, guint cur_nm_version)
+{
+	gs_free char *s_ver = NULL;
+	gs_strfreev char **s_ver_tokens = NULL;
+	gint v_maj = -1, v_min = -1, v_mic = -1;
+	guint c_maj = -1, c_min = -1, c_mic = -1;
+	guint n_tokens;
+
+	s_ver = g_strdup (str);
+	g_strstrip (s_ver);
+
+	/* Let's be strict with the accepted format here. No funny stuff!! */
+
+	if (s_ver[strspn (s_ver, ".0123456789")] != '\0')
+		return FALSE;
+
+	s_ver_tokens = g_strsplit (s_ver, ".", -1);
+	n_tokens = g_strv_length (s_ver_tokens);
+	if (n_tokens == 0 || n_tokens > 3)
+		return FALSE;
+
+	v_maj = _nm_utils_ascii_str_to_int64 (s_ver_tokens[0], 10, 0, 0xFFFF, -1);
+	if (v_maj < 0)
+		return FALSE;
+	if (n_tokens >= 2) {
+		v_min = _nm_utils_ascii_str_to_int64 (s_ver_tokens[1], 10, 0, 0xFF, -1);
+		if (v_min < 0)
+			return FALSE;
+	}
+	if (n_tokens >= 3) {
+		v_mic = _nm_utils_ascii_str_to_int64 (s_ver_tokens[2], 10, 0, 0xFF, -1);
+		if (v_mic < 0)
+			return FALSE;
+	}
+
+	nm_decode_version (cur_nm_version, &c_maj, &c_min, &c_mic);
+
+#define CHECK_AND_RETURN_FALSE(cur, val, tag, is_last_digit) \
+	G_STMT_START { \
+		if (!strcmp (tag, MATCH_TAG_CONFIG_NM_VERSION_MIN)) { \
+			if (cur < val) \
+				return FALSE; \
+		} else if (!strcmp (tag, MATCH_TAG_CONFIG_NM_VERSION_MAX)) { \
+			if (cur > val) \
+				return FALSE; \
+		} else { \
+			if (cur != val) \
+				return FALSE; \
+		} \
+		if (!(is_last_digit)) { \
+			if (cur != val) \
+				return FALSE; \
+		} \
+	} G_STMT_END
+	if (v_mic >= 0)
+		CHECK_AND_RETURN_FALSE (c_mic, v_mic, tag, TRUE);
+	if (v_min >= 0)
+		CHECK_AND_RETURN_FALSE (c_min, v_min, tag, v_mic < 0);
+	CHECK_AND_RETURN_FALSE (c_maj, v_maj, tag, v_min < 0);
+	return TRUE;
+}
+
+NMMatchSpecMatchType
+nm_match_spec_match_config (const GSList *specs, guint cur_nm_version, const char *env)
+{
+	const GSList *iter;
+	NMMatchSpecMatchType match = NM_MATCH_SPEC_NO_MATCH;
+
+	if (!specs)
+		return NM_MATCH_SPEC_NO_MATCH;
+
+	for (iter = specs; iter; iter = g_slist_next (iter)) {
+		const char *spec_str = iter->data;
+		gboolean except;
+		gboolean v_match;
+
+		if (!spec_str || !*spec_str)
+			continue;
+
+		spec_str = _match_except (spec_str, &except);
+
+		if (_spec_has_prefix (&spec_str, MATCH_TAG_CONFIG_NM_VERSION))
+			v_match = _match_config_nm_version (spec_str, MATCH_TAG_CONFIG_NM_VERSION, cur_nm_version);
+		else if (_spec_has_prefix (&spec_str, MATCH_TAG_CONFIG_NM_VERSION_MIN))
+			v_match = _match_config_nm_version (spec_str, MATCH_TAG_CONFIG_NM_VERSION_MIN, cur_nm_version);
+		else if (_spec_has_prefix (&spec_str, MATCH_TAG_CONFIG_NM_VERSION_MAX))
+			v_match = _match_config_nm_version (spec_str, MATCH_TAG_CONFIG_NM_VERSION_MAX, cur_nm_version);
+		else if (_spec_has_prefix (&spec_str, MATCH_TAG_CONFIG_ENV))
+			v_match = env && env[0] && !strcmp (spec_str, env);
+		else
+			continue;
+
+		if (v_match) {
+			if (except)
+				return NM_MATCH_SPEC_NEG_MATCH;
+			match = NM_MATCH_SPEC_MATCH;
+		}
+	}
+	return match;
+}
+
 /**
  * nm_match_spec_split:
  * @value: the string of device specs
@@ -1495,125 +1726,229 @@ nm_match_spec_join (GSList *specs)
 	return g_string_free (str, FALSE);
 }
 
-const char *
-nm_utils_get_shared_wifi_permission (NMConnection *connection)
-{
-	NMSettingWireless *s_wifi;
-	NMSettingWirelessSecurity *s_wsec;
-	const char *method = NULL;
+/*****************************************************************************/
 
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) != 0)
-		return NULL;  /* Not shared */
+char _nm_utils_to_string_buffer[];
 
-	s_wifi = nm_connection_get_setting_wireless (connection);
-	if (s_wifi) {
-		s_wsec = nm_connection_get_setting_wireless_security (connection);
-		if (s_wsec)
-			return NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED;
-		else
-			return NM_AUTH_PERMISSION_WIFI_SHARE_OPEN;
+void
+nm_utils_to_string_buffer_init (char **buf, gsize *len)
+{
+	if (!*buf) {
+		*buf = _nm_utils_to_string_buffer;
+		*len = sizeof (_nm_utils_to_string_buffer);
 	}
-
-	return NULL;
 }
 
-/*********************************/
-
-static void
-nm_gvalue_destroy (gpointer data)
+gboolean
+nm_utils_to_string_buffer_init_null (gconstpointer obj, char **buf, gsize *len)
 {
-	GValue *value = (GValue *) data;
-
-	g_value_unset (value);
-	g_slice_free (GValue, value);
+	nm_utils_to_string_buffer_init (buf, len);
+	if (!obj) {
+		g_strlcpy (*buf, "(null)", *len);
+		return FALSE;
+	}
+	return TRUE;
 }
 
-GHashTable *
-value_hash_create (void)
+void
+nm_utils_strbuf_append_c (char **buf, gsize *len, char c)
 {
-	return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, nm_gvalue_destroy);
+	switch (*len) {
+	case 0:
+		return;
+	case 1:
+		(*buf)[0] = '\0';
+		*len = 0;
+		(*buf)++;
+		return;
+	default:
+		(*buf)[0] = c;
+		(*buf)[1] = '\0';
+		(*len)--;
+		(*buf)++;
+		return;
+	}
 }
 
 void
-value_hash_add (GHashTable *hash,
-				const char *key,
-				GValue *value)
+nm_utils_strbuf_append_str (char **buf, gsize *len, const char *str)
 {
-	g_hash_table_insert (hash, g_strdup (key), value);
+	gsize src_len;
+
+	switch (*len) {
+	case 0:
+		return;
+	case 1:
+		if (!str || !*str) {
+			(*buf)[0] = '\0';
+			return;
+		}
+		(*buf)[0] = '\0';
+		*len = 0;
+		(*buf)++;
+		return;
+	default:
+		if (!str || !*str) {
+			(*buf)[0] = '\0';
+			return;
+		}
+		src_len = g_strlcpy (*buf, str, *len);
+		if (src_len >= *len) {
+			*buf = &(*buf)[*len];
+			*len = 0;
+		} else {
+			*buf = &(*buf)[src_len];
+			*len -= src_len;
+		}
+		return;
+	}
 }
 
 void
-value_hash_add_str (GHashTable *hash,
-					const char *key,
-					const char *str)
+nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...)
 {
-	GValue *value;
+	char *p = *buf;
+	va_list args;
+	gint retval;
 
-	value = g_slice_new0 (GValue);
-	g_value_init (value, G_TYPE_STRING);
-	g_value_set_string (value, str);
+	if (*len == 0)
+		return;
+
+	va_start (args, format);
+	retval = g_vsnprintf (p, *len, format, args);
+	va_end (args);
 
-	value_hash_add (hash, key, value);
+	if (retval >= *len) {
+		*buf = &p[*len];
+		*len = 0;
+	} else {
+		*buf = &p[retval];
+		*len -= retval;
+	}
 }
 
-void
-value_hash_add_object_path (GHashTable *hash,
-							const char *key,
-							const char *op)
+const char *
+nm_utils_flags2str (const NMUtilsFlags2StrDesc *descs,
+                    gsize n_descs,
+                    unsigned flags,
+                    char *buf,
+                    gsize len)
 {
-	GValue *value;
+	gsize i;
+	char *p;
 
-	value = g_slice_new0 (GValue);
-	g_value_init (value, DBUS_TYPE_G_OBJECT_PATH);
-	g_value_set_boxed (value, op);
+#if NM_MORE_ASSERTS > 10
+	nm_assert (descs);
+	nm_assert (n_descs > 0);
+	for (i = 0; i < n_descs; i++) {
+		gsize j;
 
-	value_hash_add (hash, key, value);
-}
+		nm_assert (descs[i].flag && nm_utils_is_power_of_two (descs[i].flag));
+		nm_assert (descs[i].name && descs[i].name[0]);
+		for (j = 0; j < i; j++)
+			nm_assert (descs[j].flag != descs[i].flag);
+	}
+#endif
 
-void
-value_hash_add_uint (GHashTable *hash,
-					 const char *key,
-					 guint32 val)
-{
-	GValue *value;
+	nm_utils_to_string_buffer_init (&buf, &len);
 
-	value = g_slice_new0 (GValue);
-	g_value_init (value, G_TYPE_UINT);
-	g_value_set_uint (value, val);
+	if (!len)
+		return buf;
 
-	value_hash_add (hash, key, value);
-}
+	buf[0] = '\0';
+	if (!flags) {
+		return buf;
+	}
 
-void
-value_hash_add_bool (GHashTable *hash,
-					 const char *key,
-					 gboolean val)
+	p = buf;
+	for (i = 0; flags && i < n_descs; i++) {
+		if (NM_FLAGS_HAS (flags, descs[i].flag)) {
+			flags &= ~descs[i].flag;
+
+			if (buf[0] != '\0')
+				nm_utils_strbuf_append_c (&p, &len, ',');
+			nm_utils_strbuf_append_str (&p, &len, descs[i].name);
+		}
+	}
+	if (flags) {
+		if (buf[0] != '\0')
+			nm_utils_strbuf_append_c (&p, &len, ',');
+		nm_utils_strbuf_append (&p, &len, "0x%x", flags);
+	}
+	return buf;
+};
+
+/*****************************************************************************/
+
+const char *
+nm_utils_enum2str (const NMUtilsEnum2StrDesc *descs,
+                   gsize n_descs,
+                   int val,
+                   char *buf,
+                   gsize len)
 {
-	GValue *value;
+	gsize i;
 
-	value = g_slice_new0 (GValue);
-	g_value_init (value, G_TYPE_BOOLEAN);
-	g_value_set_boolean (value, val);
+#if NM_MORE_ASSERTS > 10
+	nm_assert (descs);
+	nm_assert (n_descs > 0);
+	for (i = 0; i < n_descs; i++) {
+		gsize j;
 
-	value_hash_add (hash, key, value);
-}
+		nm_assert (descs[i].name && descs[i].name[0]);
+		for (j = 0; j < i; j++)
+			nm_assert (descs[j].value != descs[i].value);
+	}
+#endif
 
-void
-value_hash_add_object_property (GHashTable *hash,
-                                const char *key,
-                                GObject *object,
-                                const char *prop,
-                                GType val_type)
+	nm_utils_to_string_buffer_init (&buf, &len);
+
+	if (!len)
+		return buf;
+
+	for (i = 0; i < n_descs; i++) {
+		if (val == descs[i].value) {
+			g_strlcpy (buf, descs[i].name, len);
+			return buf;
+		}
+	}
+
+	g_snprintf (buf, len, "(%d)", val);
+	return buf;
+};
+
+/*****************************************************************************/
+
+/**
+ * nm_utils_get_shared_wifi_permission:
+ * @connection: the NMConnection to lookup the permission.
+ *
+ * Returns: a static string of the wifi-permission (if any) or %NULL.
+ */
+const char *
+nm_utils_get_shared_wifi_permission (NMConnection *connection)
 {
-	GValue *value;
+	NMSettingWireless *s_wifi;
+	NMSettingWirelessSecurity *s_wsec;
+	const char *method = NULL;
+
+	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) != 0)
+		return NULL;  /* Not shared */
 
-	value = g_slice_new0 (GValue);
-	g_value_init (value, val_type);
-	g_object_get_property (object, prop, value);
-	value_hash_add (hash, key, value);
+	s_wifi = nm_connection_get_setting_wireless (connection);
+	if (s_wifi) {
+		s_wsec = nm_connection_get_setting_wireless_security (connection);
+		if (s_wsec)
+			return NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED;
+		else
+			return NM_AUTH_PERMISSION_WIFI_SHARE_OPEN;
+	}
+
+	return NULL;
 }
 
+/*********************************/
 
 static char *
 get_new_connection_name (const GSList *existing,
@@ -1857,6 +2192,56 @@ nm_utils_read_resolv_conf_nameservers (const char *rc_contents)
 	return nameservers;
 }
 
+/**
+ * nm_utils_read_resolv_conf_dns_options():
+ * @rc_contents: contents of a resolv.conf; or %NULL to read /etc/resolv.conf
+ *
+ * Reads all dns options out of @rc_contents or /etc/resolv.conf and returns
+ * them.
+ *
+ * Returns: a #GPtrArray of 'char *' elements of each option
+ */
+GPtrArray *
+nm_utils_read_resolv_conf_dns_options (const char *rc_contents)
+{
+	GPtrArray *options = NULL;
+	char *contents = NULL;
+	char **lines, **line_iter;
+	char **tokens, **token_iter;
+	char *p;
+
+	if (rc_contents)
+		contents = g_strdup (rc_contents);
+	else {
+		if (!g_file_get_contents (_PATH_RESCONF, &contents, NULL, NULL))
+			return NULL;
+	}
+
+	options = g_ptr_array_new_full (3, g_free);
+
+	lines = g_strsplit_set (contents, "\r\n", -1);
+	for (line_iter = lines; *line_iter; line_iter++) {
+		if (!g_str_has_prefix (*line_iter, "options"))
+			continue;
+		p = *line_iter + strlen ("options");
+		if (!g_ascii_isspace (*p++))
+			continue;
+
+		tokens = g_strsplit (p, " ", 0);
+		for (token_iter = tokens; token_iter && *token_iter; token_iter++) {
+			g_strstrip (*token_iter);
+			if (!*token_iter[0])
+				continue;
+			g_ptr_array_add (options, g_strdup (*token_iter));
+		}
+		g_strfreev (tokens);
+	}
+	g_strfreev (lines);
+	g_free (contents);
+
+	return options;
+}
+
 static GHashTable *
 check_property_in_hash (GHashTable *hash,
                         const char *s_name,
@@ -2933,7 +3318,6 @@ nm_utils_get_ipv6_interface_identifier (NMLinkType link_type,
 	}
 	return FALSE;
 }
-
 void
 nm_utils_ipv6_addr_set_interface_identfier (struct in6_addr *addr,
                                             const NMUtilsIPv6IfaceId iid)
@@ -2948,160 +3332,123 @@ nm_utils_ipv6_interface_identfier_get_from_addr (NMUtilsIPv6IfaceId *iid,
 	memcpy (iid, addr->s6_addr + 8, 8);
 }
 
-/**
- * nm_utils_connection_hash_to_dict:
- * @hash: a hashed #NMConnection
- *
- * Returns: a (floating) #GVariant equivalent to @hash.
- */
-GVariant *
-nm_utils_connection_hash_to_dict (GHashTable *hash)
-{
-	GValue val = { 0, };
-	GVariant *variant;
-
-	if (!hash)
-		return NULL;
-
-	g_value_init (&val, DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT);
-	g_value_set_boxed (&val, hash);
-	variant = dbus_g_value_build_g_variant (&val);
-	g_value_unset (&val);
-
-	return variant;
-}
+static gboolean
+_set_stable_privacy (struct in6_addr *addr,
+                     const char *ifname,
+                     const char *uuid,
+                     guint dad_counter,
+                     gchar *secret_key,
+                     gsize key_len,
+                     GError **error)
+{
+	GChecksum *sum;
+	guint8 digest[32];
+	guint32 tmp[2];
+	gsize len = sizeof (digest);
+
+	g_return_val_if_fail (key_len, FALSE);
+
+	/* Documentation suggests that this can fail.
+	 * Maybe in case of a missing algorithm in crypto library? */
+	sum = g_checksum_new (G_CHECKSUM_SHA256);
+	if (!sum) {
+		g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		                     "Can't create a SHA256 hash");
+		return FALSE;
+	}
 
-/**
- * nm_utils_connection_dict_to_hash:
- * @dict: a #GVariant-serialized #NMConnection
- *
- * Returns: a #GHashTable equivalent to @dict.
- */
-GHashTable *
-nm_utils_connection_dict_to_hash (GVariant *dict)
-{
-	GValue val = { 0, };
+	key_len = MIN (key_len, G_MAXUINT32);
 
-	if (!dict)
-		return NULL;
+	g_checksum_update (sum, addr->s6_addr, 8);
+	g_checksum_update (sum, (const guchar *) ifname, strlen (ifname) + 1);
+	if (!uuid)
+		uuid = "";
+	g_checksum_update (sum, (const guchar *) uuid, strlen (uuid) + 1);
+	tmp[0] = htonl (dad_counter);
+	tmp[1] = htonl (key_len);
+	g_checksum_update (sum, (const guchar *) tmp, sizeof (tmp));
+	g_checksum_update (sum, (const guchar *) secret_key, key_len);
 
-	dbus_g_value_parse_g_variant (dict, &val);
-	return g_value_get_boxed (&val);
-}
+	g_checksum_get_digest (sum, digest, &len);
+	g_checksum_free (sum);
 
-GSList *
-nm_utils_ip4_routes_from_gvalue (const GValue *value)
-{
-	GPtrArray *routes;
-	int i;
-	GSList *list = NULL;
-
-	routes = (GPtrArray *) g_value_get_boxed (value);
-	for (i = 0; routes && (i < routes->len); i++) {
-		GArray *array = (GArray *) g_ptr_array_index (routes, i);
-		guint32 *array_val = (guint32 *) array->data;
-		NMIPRoute *route;
-		GError *error = NULL;
-
-		if (array->len < 4) {
-			g_warning ("Ignoring invalid IP4 route");
-			continue;
-		}
+	g_return_val_if_fail (len == 32, FALSE);
 
-		route = nm_ip_route_new_binary (AF_INET,
-		                                &array_val[0], array_val[1],
-		                                &array_val[2], array_val[3],
-		                                &error);
-		if (route)
-			list = g_slist_prepend (list, route);
-		else {
-			g_warning ("Ignoring invalid IP4 route: %s", error->message);
-			g_clear_error (&error);
-		}
-	}
+	memcpy (addr->s6_addr + 8, &digest[0], 8);
 
-	return g_slist_reverse (list);
+	return TRUE;
 }
 
-static gboolean
-_nm_utils_gvalue_array_validate (GValueArray *elements, guint n_expected, ...)
-{
-	va_list args;
-	GValue *tmp;
-	int i;
-	gboolean valid = FALSE;
+#define RFC7217_IDGEN_RETRIES 3
+/**
+ * nm_utils_ipv6_addr_set_stable_privacy:
+ *
+ * Extend the address prefix with an interface identifier using the
+ * RFC 7217 Stable Privacy mechanism.
+ *
+ * Returns: %TRUE on success, %FALSE if the address could not be generated.
+ */
+gboolean
+nm_utils_ipv6_addr_set_stable_privacy (struct in6_addr *addr,
+                                       const char *ifname,
+                                       const char *uuid,
+                                       guint dad_counter,
+                                       GError **error)
+{
+	gchar *secret_key = NULL;
+	gsize key_len = 0;
+	gboolean success = FALSE;
 
-	if (n_expected != elements->n_values)
+	if (dad_counter >= RFC7217_IDGEN_RETRIES) {
+		g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		                     "Too many DAD collisions");
 		return FALSE;
-
-	va_start (args, n_expected);
-	for (i = 0; i < n_expected; i++) {
-		tmp = g_value_array_get_nth (elements, i);
-		if (G_VALUE_TYPE (tmp) != va_arg (args, GType))
-			goto done;
 	}
-	valid = TRUE;
 
-done:
-	va_end (args);
-	return valid;
-}
-
-GSList *
-nm_utils_ip6_routes_from_gvalue (const GValue *value)
-{
-	GPtrArray *routes;
-	int i;
-	GSList *list = NULL;
-
-	routes = (GPtrArray *) g_value_get_boxed (value);
-	for (i = 0; routes && (i < routes->len); i++) {
-		GValueArray *route_values = (GValueArray *) g_ptr_array_index (routes, i);
-		GByteArray *dest, *next_hop;
-		guint prefix, metric;
-		NMIPRoute *route;
-		GError *error = NULL;
-
-		if (!_nm_utils_gvalue_array_validate (route_values, 4,
-		                                      DBUS_TYPE_G_UCHAR_ARRAY,
-		                                      G_TYPE_UINT,
-		                                      DBUS_TYPE_G_UCHAR_ARRAY,
-		                                      G_TYPE_UINT)) {
-			g_warning ("Ignoring invalid IP6 route");
-			continue;
+	/* Let's try to load a saved secret key first. */
+	if (g_file_get_contents (NMSTATEDIR "/secret_key", &secret_key, &key_len, NULL)) {
+		if (key_len < 16) {
+			g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+			                     "Key is too short to be usable");
+			key_len = 0;
 		}
+	} else {
+		int urandom = open ("/dev/urandom", O_RDONLY);
+		mode_t key_mask;
 
-		dest = g_value_get_boxed (g_value_array_get_nth (route_values, 0));
-		if (dest->len != 16) {
-			g_warning ("%s: ignoring invalid IP6 dest address of length %d",
-			           __func__, dest->len);
-			continue;
+		if (!urandom) {
+			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+			             "Can't open /dev/urandom: %s", strerror (errno));
+			return FALSE;
 		}
 
-		prefix = g_value_get_uint (g_value_array_get_nth (route_values, 1));
+		/* RFC7217 mandates the key SHOULD be at least 128 bits.
+		 * Let's use twice as much. */
+		key_len = 32;
+		secret_key = g_malloc (key_len);
 
-		next_hop = g_value_get_boxed (g_value_array_get_nth (route_values, 2));
-		if (next_hop->len != 16) {
-			g_warning ("%s: ignoring invalid IP6 next_hop address of length %d",
-			           __func__, next_hop->len);
-			continue;
+		key_mask = umask (0077);
+		if (read (urandom, secret_key, key_len) == key_len) {
+			if (!g_file_set_contents (NMSTATEDIR "/secret_key", secret_key, key_len, error)) {
+				g_prefix_error (error, "Can't write " NMSTATEDIR "/secret_key");
+				key_len = 0;
+			}
+		} else {
+			g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+			                     "Could not obtain a secret");
+			key_len = 0;
 		}
+		umask (key_mask);
+		close (urandom);
+	}
 
-		metric = g_value_get_uint (g_value_array_get_nth (route_values, 3));
-
-		route = nm_ip_route_new_binary (AF_INET6,
-		                                dest->data, prefix,
-		                                next_hop->data, metric,
-		                                &error);
-		if (route)
-			list = g_slist_prepend (list, route);
-		else {
-			g_warning ("Ignoring invalid IP6 route: %s", error->message);
-			g_clear_error (&error);
-		}
+	if (key_len) {
+		success = _set_stable_privacy (addr, ifname, uuid, dad_counter,
+		                               secret_key, key_len, error);
 	}
 
-	return g_slist_reverse (list);
+	g_free (secret_key);
+	return success;
 }
 
 /**
@@ -3121,3 +3468,78 @@ nm_utils_setpgid (gpointer unused G_GNUC_UNUSED)
 	pid = getpid ();
 	setpgid (pid, pid);
 }
+
+/**
+ * nm_utils_g_value_set_object_path:
+ * @value: a #GValue, initialized to store an object path
+ * @object: (allow-none): an #NMExportedObject
+ *
+ * Sets @value to @object's object path. If @object is %NULL, or not
+ * exported, @value is set to "/".
+ */
+void
+nm_utils_g_value_set_object_path (GValue *value, gpointer object)
+{
+	g_return_if_fail (!object || NM_IS_EXPORTED_OBJECT (object));
+
+	if (object && nm_exported_object_is_exported (object))
+		g_value_set_string (value, nm_exported_object_get_path (object));
+	else
+		g_value_set_string (value, "/");
+}
+
+/**
+ * nm_utils_g_value_set_object_path_array:
+ * @value: a #GValue, initialized to store an object path
+ * @objects: a #GSList of #NMExportedObjects
+ * @filter_func: (allow-none): function to call on each object in @objects
+ * @user_data: data to pass to @filter_func
+ *
+ * Sets @value to an array of object paths of the objects in @objects.
+ */
+void
+nm_utils_g_value_set_object_path_array (GValue *value,
+                                        GSList *objects,
+                                        NMUtilsObjectFunc filter_func,
+                                        gpointer user_data)
+{
+	char **paths;
+	guint i;
+	GSList *iter;
+
+	paths = g_new (char *, g_slist_length (objects) + 1);
+	for (i = 0, iter = objects; iter; iter = iter->next) {
+		NMExportedObject *object = iter->data;
+		const char *path;
+
+		path = nm_exported_object_get_path (object);
+		if (!path)
+			continue;
+		if (filter_func && !filter_func ((GObject *) object, user_data))
+			continue;
+		paths[i++] = g_strdup (path);
+	}
+	paths[i] = NULL;
+	g_value_take_boxed (value, paths);
+}
+
+/**
+ * nm_utils_g_value_set_strv:
+ * @value: a #GValue, initialized to store a #G_TYPE_STRV
+ * @strings: a #GPtrArray of strings
+ *
+ * Converts @strings to a #GStrv and stores it in @value.
+ */
+void
+nm_utils_g_value_set_strv (GValue *value, GPtrArray *strings)
+{
+	char **strv;
+	int i;
+
+	strv = g_new (char *, strings->len + 1);
+	for (i = 0; i < strings->len; i++)
+		strv[i] = g_strdup (strings->pdata[i]);
+	strv[i] = NULL;
+
+	g_value_take_boxed (value, strv);
+}