summary refs log tree commit diff
path: root/libnm-core/nm-keyfile-reader.c
diff options
context:
space:
mode:
Diffstat (limited to 'libnm-core/nm-keyfile-reader.c')
-rw-r--r--libnm-core/nm-keyfile-reader.c1969
1 files changed, 1969 insertions, 0 deletions
diff --git a/libnm-core/nm-keyfile-reader.c b/libnm-core/nm-keyfile-reader.c
new file mode 100644
index 00000000..20b5a3fc
--- /dev/null
+++ b/libnm-core/nm-keyfile-reader.c
@@ -0,0 +1,1969 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager system settings service - keyfile plugin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2008 - 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-keyfile-internal.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <linux/pkt_sched.h>
+
+#include "nm-common-macros.h"
+#include "nm-core-internal.h"
+#include "nm-keyfile-utils.h"
+
+#include "nm-setting-user.h"
+
+typedef struct {
+	NMConnection *connection;
+	GKeyFile *keyfile;
+	const char *base_dir;
+	NMKeyfileReadHandler handler;
+	void *user_data;
+	GError *error;
+	const char *group;
+	NMSetting *setting;
+} KeyfileReaderInfo;
+
+
+static void
+_handle_warn (KeyfileReaderInfo *info,
+              const char *property_name,
+              NMKeyfileWarnSeverity severity,
+              char *message)
+{
+	NMKeyfileReadTypeDataWarn type_data = {
+		.group = info->group,
+		.setting = info->setting,
+		.property_name = property_name,
+		.severity = severity,
+		.message = message,
+	};
+
+	info->handler (info->keyfile,
+	               info->connection,
+	               NM_KEYFILE_READ_TYPE_WARN,
+	               &type_data,
+	               info->user_data,
+	               &info->error);
+	g_free (message);
+}
+#define handle_warn(arg_info, arg_property_name, arg_severity, ...) \
+	({ \
+		KeyfileReaderInfo *_info = (arg_info); \
+		\
+		if (_info->handler) { \
+			_handle_warn (_info, (arg_property_name), (arg_severity), \
+			              g_strdup_printf (__VA_ARGS__)); \
+		} \
+		_info->error == NULL; \
+	})
+
+/* Some setting properties also contain setting names, such as
+ * NMSettingConnection's 'type' property (which specifies the base type of the
+ * connection, e.g. ethernet or wifi) or 'slave-type' (specifies type of slave
+ * connection, e.g. bond or bridge). This function handles translating those
+ * properties' values to the real setting name if they are an alias.
+ */
+static void
+setting_alias_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	char *s;
+	const char *key_setting_name;
+
+	s = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+	if (s) {
+		key_setting_name = nm_keyfile_plugin_get_setting_name_for_alias (s);
+		g_object_set (G_OBJECT (setting),
+		              key, key_setting_name ? key_setting_name : s,
+		              NULL);
+		g_free (s);
+	}
+}
+
+static void
+read_array_of_uint (GKeyFile *file,
+                    NMSetting *setting,
+                    const char *key)
+{
+	gs_unref_array GArray *array = NULL;
+	gsize length;
+	gsize i;
+	gs_free int *tmp = NULL;
+
+	tmp = nm_keyfile_plugin_kf_get_integer_list (file, nm_setting_get_name (setting), key, &length, NULL);
+	if (length > G_MAXUINT)
+		return;
+
+	array = g_array_sized_new (FALSE, FALSE, sizeof (guint), length);
+
+	for (i = 0; i < length; i++) {
+		if (tmp[i] < 0)
+			return;
+		g_array_append_val (array, tmp[i]);
+	}
+
+	g_object_set (setting, key, array, NULL);
+}
+
+static gboolean
+get_one_int (KeyfileReaderInfo *info, const char *property_name, const char *str, guint32 max_val, guint32 *out)
+{
+	gint64 tmp;
+
+	g_return_val_if_fail (!info == !property_name, FALSE);
+
+	if (!str || !str[0]) {
+		if (property_name)
+			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("ignoring missing number"));
+		return FALSE;
+	}
+
+	tmp = _nm_utils_ascii_str_to_int64 (str, 10, 0, max_val, -1);
+	if (tmp == -1) {
+		if (property_name) {
+			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("ignoring invalid number '%s'"),
+			            str);
+		}
+		return FALSE;
+	}
+
+	*out = (guint32) tmp;
+	return TRUE;
+}
+
+static gpointer
+build_address (KeyfileReaderInfo *info, int family, const char *address_str, guint32 plen, const char *property_name)
+{
+	NMIPAddress *addr;
+	GError *error = NULL;
+
+	g_return_val_if_fail (address_str, NULL);
+
+	addr = nm_ip_address_new (family, address_str, plen, &error);
+	if (!addr) {
+		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid %s address: %s"),
+		            family == AF_INET ? "IPv4" : "IPv6", error->message);
+		g_error_free (error);
+	}
+
+	return addr;
+}
+
+static gpointer
+build_route (KeyfileReaderInfo *info,
+             const char *property_name,
+             int family,
+             const char *dest_str, guint32 plen,
+             const char *gateway_str, const char *metric_str)
+{
+	NMIPRoute *route;
+	guint32 u32;
+	gint64 metric = -1;
+	GError *error = NULL;
+
+	g_return_val_if_fail (plen, NULL);
+	g_return_val_if_fail (dest_str, NULL);
+
+	/* Next hop */
+	if (gateway_str && gateway_str[0]) {
+		if (!nm_utils_ipaddr_valid (family, gateway_str)) {
+			/* Try workaround for routes written by broken keyfile writer.
+			 * Due to bug bgo#719851, an older version of writer would have
+			 * written "a:b:c:d::/plen,metric" if the gateway was ::, instead
+			 * of "a:b:c:d::/plen,,metric" or "a:b:c:d::/plen,::,metric"
+			 * Try workaround by interpreting gateway_str as metric to accept such
+			 * invalid routes. This broken syntax should not be not officially
+			 * supported.
+			 **/
+			if (   family == AF_INET6
+			    && !metric_str
+			    && get_one_int (NULL, NULL, gateway_str, G_MAXUINT32, &u32)) {
+				metric = u32;
+				gateway_str = NULL;
+			} else {
+				if (!info->error) {
+					handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+					             _("ignoring invalid gateway '%s' for %s route"),
+					             gateway_str, family == AF_INET ? "IPv4" : "IPv6");
+				}
+				return NULL;
+			}
+		}
+	} else
+		gateway_str = NULL;
+
+	/* parse metric, default to -1 */
+	if (metric_str) {
+		if (!get_one_int (info, property_name, metric_str, G_MAXUINT32, &u32))
+			return NULL;
+		metric = u32;
+	}
+
+	route = nm_ip_route_new (family, dest_str, plen, gateway_str,
+	                         metric,
+	                         &error);
+	if (!route) {
+		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid %s route: %s"),
+		             family == AF_INET ? "IPv4" : "IPv6",
+		             error->message);
+		g_error_free (error);
+	}
+
+	return route;
+}
+
+/* On success, returns pointer to the zero-terminated field (original @current).
+ * The @current * pointer target is set to point to the rest of the input
+ * or %NULL if there is no more input. Sets error to %NULL for convenience.
+ *
+ * On failure, returns %NULL (unspecified). The @current pointer target is
+ * resets to its original value to allow skipping fields. The @error target
+ * is set to the character that breaks the parsing or %NULL if @current was %NULL.
+ *
+ * When @current target is %NULL, gracefully fail returning %NULL while
+ * leaving the @current target %NULL end setting @error to %NULL;
+ */
+static const char *
+read_field (char **current, const char **out_err_str, const char *characters, const char *delimiters)
+{
+	const char *start;
+
+	nm_assert (current);
+	nm_assert (out_err_str);
+	nm_assert (characters);
+	nm_assert (delimiters);
+
+	*out_err_str = NULL;
+
+	if (!*current) {
+		/* graceful failure, leave '*current' NULL */
+		return NULL;
+	}
+
+	/* fail on empty input */
+	if (!**current)
+		return NULL;
+
+	/* remember beginning of input */
+	start = *current;
+
+	while (**current && strchr (characters, **current))
+		(*current)++;
+	if (**current)
+		if (strchr (delimiters, **current)) {
+			/* success, more data available */
+			*(*current)++ = '\0';
+			return start;
+		} else {
+			/* error, bad character */
+			*out_err_str = *current;
+			*current = (char *) start;
+			return NULL;
+		}
+	else {
+		/* success, end of input */
+		*current = NULL;
+		return start;
+	}
+}
+
+#define IP_ADDRESS_CHARS "0123456789abcdefABCDEF:.%"
+#define DIGITS "0123456789"
+#define DELIMITERS "/;,"
+
+
+/* The following IPv4 and IPv6 address formats are supported:
+ *
+ * address (DEPRECATED)
+ * address/plen
+ * address/gateway (DEPRECATED)
+ * address/plen,gateway
+ *
+ * The following IPv4 and IPv6 route formats are supported:
+ *
+ * address/plen (NETWORK dev DEVICE)
+ * address/plen,gateway (NETWORK via GATEWAY dev DEVICE)
+ * address/plen,,metric (NETWORK dev DEVICE metric METRIC)
+ * address/plen,gateway,metric (NETWORK via GATEWAY dev DEVICE metric METRIC)
+ *
+ * For backward, forward and sideward compatibility, slash (/),
+ * semicolon (;) and comma (,) are interchangable. The choice of
+ * separator in the above examples is therefore not significant.
+ *
+ * Leaving out the prefix length is discouraged and DEPRECATED. The
+ * default value of IPv6 prefix length was 64 and has not been
+ * changed. The default for IPv4 is now 24, which is the closest
+ * IPv4 equivalent. These defaults may just as well be changed to
+ * match the iproute2 defaults (32 for IPv4 and 128 for IPv6).
+ */
+static gpointer
+read_one_ip_address_or_route (KeyfileReaderInfo *info,
+                              const char *property_name,
+                              const char *setting_name,
+                              const char *key_name,
+                              gboolean ipv6,
+                              gboolean route,
+                              char **out_gateway,
+                              NMSetting *setting)
+{
+	guint plen;
+	gpointer result;
+	const char *address_str;
+	const char *plen_str;
+	const char *gateway_str;
+	const char *metric_str;
+	const char *err_str = NULL;
+	char *current;
+	gs_free char *value = NULL;
+	gs_free char *value_orig = NULL;
+
+#define VALUE_ORIG()   (value_orig ? value_orig : (value_orig = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key_name, NULL)))
+
+	value = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key_name, NULL);
+	if (!value)
+		return NULL;
+
+	current = value;
+
+	/* get address field */
+	address_str = read_field (&current, &err_str, IP_ADDRESS_CHARS, DELIMITERS);
+	if (err_str) {
+		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("unexpected character '%c' for address %s: '%s' (position %td)"),
+		             *err_str, key_name, VALUE_ORIG (), err_str - current);
+		return NULL;
+	}
+	/* get prefix length field (skippable) */
+	plen_str = read_field (&current, &err_str, DIGITS, DELIMITERS);
+	/* get gateway field */
+	gateway_str = read_field (&current, &err_str, IP_ADDRESS_CHARS, DELIMITERS);
+	if (err_str) {
+		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("unexpected character '%c' for %s: '%s' (position %td)"),
+		             *err_str, key_name, VALUE_ORIG (), err_str - current);
+		return NULL;
+	}
+	/* for routes, get metric */
+	if (route) {
+		metric_str = read_field (&current, &err_str, DIGITS, DELIMITERS);
+		if (err_str) {
+			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("unexpected character '%c' in prefix length for %s: '%s' (position %td)"),
+			             *err_str, key_name, VALUE_ORIG (), err_str - current);
+			return NULL;
+		}
+	} else
+		metric_str = NULL;
+	if (current) {
+		/* there is still some data */
+		if (*current) {
+			/* another field follows */
+			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("garbage at the end of value %s: '%s'"),
+			             key_name, VALUE_ORIG ());
+			return NULL;
+		} else {
+			/* semicolon at the end of input */
+			if (!handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_INFO,
+			                  _("deprecated semicolon at the end of value %s: '%s'"),
+			                  key_name, VALUE_ORIG ()))
+				return NULL;
+		}
+	}
+
+#define DEFAULT_PREFIX(for_route, for_ipv6) ( (for_route) ? ( (for_ipv6) ? 128 : 24 ) : ( (for_ipv6) ? 64 : 24 ) )
+
+	/* parse plen, fallback to defaults */
+	if (plen_str) {
+		if (   !get_one_int (info, property_name, plen_str, ipv6 ? 128 : 32, &plen)
+		    || (route && plen == 0)) {
+			plen = DEFAULT_PREFIX (route, ipv6);
+			if (   info->error
+			    || !handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                     _("invalid prefix length for %s '%s', defaulting to %d"),
+			                     key_name, VALUE_ORIG (), plen))
+				return NULL;
+		}
+	} else {
+		plen = DEFAULT_PREFIX (route, ipv6);
+		if (!handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+		                  _("missing prefix length for %s '%s', defaulting to %d"),
+		                  key_name, VALUE_ORIG (), plen))
+			return NULL;
+	}
+
+	/* build the appropriate data structure for NetworkManager settings */
+	if (route) {
+		result = build_route (info, property_name,
+		                      ipv6 ? AF_INET6 : AF_INET,
+		                      address_str, plen, gateway_str, metric_str);
+	} else {
+		result = build_address (info, ipv6 ? AF_INET6 : AF_INET,
+		                        address_str, plen, property_name);
+		if (!result)
+			return NULL;
+		if (out_gateway && gateway_str)
+			*out_gateway = g_strdup (gateway_str);
+	}
+
+#undef VALUE_ORIG
+
+	return result;
+}
+
+static void
+fill_route_attributes (GKeyFile *kf, NMIPRoute *route, const char *setting, const char *key, int family)
+{
+	gs_free char *value = NULL;
+	gs_unref_hashtable GHashTable *hash = NULL;
+	GHashTableIter iter;
+	char *name;
+	GVariant *variant;
+
+	value = nm_keyfile_plugin_kf_get_string (kf, setting, key, NULL);
+	if (!value || !value[0])
+		return;
+
+	hash = nm_utils_parse_variant_attributes (value, ',', '=', TRUE,
+	                                          nm_ip_route_get_variant_attribute_spec (),
+	                                          NULL);
+	if (hash) {
+		g_hash_table_iter_init (&iter, hash);
+		while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &variant)) {
+			if (nm_ip_route_attribute_validate (name, variant, family, NULL, NULL))
+				nm_ip_route_set_attribute (route, name, g_variant_ref (variant));
+		}
+	}
+}
+
+static void
+ip_address_or_route_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	gboolean ipv6 = !strcmp (setting_name, "ipv6");
+	gboolean routes = !strcmp (key, "routes");
+	static const char *key_names_routes[] = { "route", "routes", NULL };
+	static const char *key_names_addresses[] = { "address", "addresses", NULL };
+	const char **key_names = routes ? key_names_routes : key_names_addresses;
+	char *gateway = NULL;
+	GPtrArray *list;
+	GDestroyNotify free_func;
+	int i;
+
+	if (routes)
+		free_func = (GDestroyNotify) nm_ip_route_unref;
+	else
+		free_func = (GDestroyNotify) nm_ip_address_unref;
+	list = g_ptr_array_new_with_free_func (free_func);
+
+	for (i = -1; i < 1000; i++) {
+		const char **key_basename;
+
+		for (key_basename = key_names; *key_basename; key_basename++) {
+			char *key_name;
+			gpointer item;
+			char options_key[128];
+
+			/* -1 means no suffix */
+			if (i >= 0)
+				key_name = g_strdup_printf ("%s%d", *key_basename, i);
+			else
+				key_name = g_strdup (*key_basename);
+
+			item = read_one_ip_address_or_route (info, key, setting_name, key_name, ipv6, routes,
+			                                     gateway ? NULL : &gateway, setting);
+			if (item && routes) {
+				nm_sprintf_buf (options_key, "%s_options", key_name);
+				fill_route_attributes (info->keyfile, item, setting_name, options_key, ipv6 ? AF_INET6 : AF_INET);
+			}
+
+			g_free (key_name);
+
+			if (info->error) {
+				g_ptr_array_unref (list);
+				g_free (gateway);
+				return;
+			}
+			if (item)
+				g_ptr_array_add (list, item);
+
+		}
+	}
+
+	if (list->len >= 1)
+		g_object_set (setting, key, list, NULL);
+
+	if (gateway) {
+		g_object_set (setting, "gateway", gateway, NULL);
+		g_free (gateway);
+	}
+
+	g_ptr_array_unref (list);
+}
+
+static void
+ip4_dns_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	GPtrArray *array;
+	gsize length;
+	char **list, **iter;
+	int ret;
+
+	list = nm_keyfile_plugin_kf_get_string_list (info->keyfile, setting_name, key, &length, NULL);
+	if (!list || !g_strv_length (list))
+		return;
+
+	array = g_ptr_array_sized_new (length + 1);
+	for (iter = list; *iter; iter++) {
+		guint32 addr;
+
+		ret = inet_pton (AF_INET, *iter, &addr);
+		if (ret <= 0) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("ignoring invalid DNS server IPv4 address '%s'"),
+			                  *iter)) {
+				g_ptr_array_unref (array);
+				g_strfreev (list);
+				return;
+			}
+			continue;
+		}
+
+		g_ptr_array_add (array, *iter);
+	}
+	g_ptr_array_add (array, NULL);
+
+	g_object_set (setting, key, array->pdata, NULL);
+	g_ptr_array_unref (array);
+	g_strfreev (list);
+}
+
+static void
+ip6_dns_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	GPtrArray *array = NULL;
+	gsize length;
+	char **list, **iter;
+	int ret;
+
+	list = nm_keyfile_plugin_kf_get_string_list (info->keyfile, setting_name, key, &length, NULL);
+	if (!list || !g_strv_length (list))
+		return;
+
+	array = g_ptr_array_sized_new (length + 1);
+
+	for (iter = list; *iter; iter++) {
+		struct in6_addr addr;
+
+		ret = inet_pton (AF_INET6, *iter, &addr);
+		if (ret <= 0) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("ignoring invalid DNS server IPv6 address '%s'"),
+			                  *iter)) {
+				g_ptr_array_unref (array);
+				g_strfreev (list);
+				return;
+			}
+			continue;
+		}
+
+		g_ptr_array_add (array, *iter);
+	}
+	g_ptr_array_add (array, NULL);
+
+	g_object_set (setting, key, array->pdata, NULL);
+	g_ptr_array_unref (array);
+	g_strfreev (list);
+}
+
+static void
+ip6_addr_gen_mode_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	NMSettingIP6ConfigAddrGenMode addr_gen_mode;
+	const char *setting_name = nm_setting_get_name (setting);
+	gs_free char *s = NULL;
+
+	s = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+	if (s) {
+		if (!nm_utils_enum_from_str (nm_setting_ip6_config_addr_gen_mode_get_type (), s,
+		                             (int *) &addr_gen_mode, NULL)) {
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid option '%s', use one of [%s]"),
+			             s, "eui64,stable-privacy");
+			return;
+		}
+	} else
+		addr_gen_mode = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64;
+
+	g_object_set (G_OBJECT (setting), key, (gint) addr_gen_mode, NULL);
+}
+
+static void
+mac_address_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key, gsize enforce_length, gboolean cloned_mac_addr)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	gs_free char *tmp_string = NULL;
+	const char *p, *mac_str;
+	gs_free guint8 *buf_arr = NULL;
+	guint buf_len = 0;
+	gsize length;
+
+	tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+
+	if (   cloned_mac_addr
+	    && NM_CLONED_MAC_IS_SPECIAL (tmp_string)) {
+		mac_str = tmp_string;
+		goto out;
+	}
+
+	if (tmp_string && tmp_string[0]) {
+		/* Look for enough ':' characters to signify a MAC address */
+		guint i = 0;
+
+		p = tmp_string;
+		while (*p) {
+			if (*p == ':')
+				i++;
+			p++;
+		}
+
+		if (enforce_length == 0 || enforce_length == i+1) {
+			/* If we found enough it's probably a string-format MAC address */
+			buf_len = i + 1;
+			buf_arr = g_new (guint8, buf_len);
+			if (!nm_utils_hwaddr_aton (tmp_string, buf_arr, buf_len))
+				g_clear_pointer (&buf_arr, g_free);
+		}
+	}
+	g_clear_pointer (&tmp_string, g_free);
+
+	if (!buf_arr) {
+		gs_free int *tmp_list = NULL;
+
+		/* Old format; list of ints */
+		tmp_list = nm_keyfile_plugin_kf_get_integer_list (info->keyfile, setting_name, key, &length, NULL);
+		if (length > 0 && (enforce_length == 0 || enforce_length == length)) {
+			gsize i;
+
+			buf_len = length;
+			buf_arr = g_new (guint8, buf_len);
+			for (i = 0; i < length; i++) {
+				int val = tmp_list[i];
+
+				if (val < 0 || val > 255) {
+					handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+					             _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"),
+					             val);
+					return;
+				}
+				buf_arr[i] = (guint8) val;
+			}
+		}
+	}
+
+	if (!buf_arr) {
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid MAC address"));
+		return;
+	}
+
+	tmp_string = nm_utils_hwaddr_ntoa (buf_arr, buf_len);
+	mac_str = tmp_string;
+
+out:
+	g_object_set (setting, key, mac_str, NULL);
+}
+
+static void
+mac_address_parser_ETHER (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	mac_address_parser (info, setting, key, ETH_ALEN, FALSE);
+}
+
+static void
+mac_address_parser_ETHER_cloned (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	mac_address_parser (info, setting, key, ETH_ALEN, TRUE);
+}
+
+static void
+mac_address_parser_INFINIBAND (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	mac_address_parser (info, setting, key, INFINIBAND_ALEN, FALSE);
+}
+
+static void
+read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key)
+{
+	gs_strfreev char **keys = NULL;
+	const char *const*iter;
+	const char *setting_name = nm_setting_get_name (setting);
+	gboolean is_vpn;
+
+	keys = nm_keyfile_plugin_kf_get_keys (file, setting_name, NULL, NULL);
+	if (!keys || !*keys)
+		return;
+
+	if (   (is_vpn = NM_IS_SETTING_VPN (setting))
+	    || NM_IS_SETTING_BOND (setting)) {
+		for (iter = (const char *const*) keys; *iter; iter++) {
+			gs_free char *to_free = NULL;
+			gs_free char *value = NULL;
+			const char *name;
+
+			value = nm_keyfile_plugin_kf_get_string (file, setting_name, *iter, NULL);
+			if (!value)
+				continue;
+
+			name = nm_keyfile_key_decode (*iter, &to_free);
+
+			if (is_vpn) {
+				/* Add any item that's not a class property to the data hash */
+				if (!g_object_class_find_property (G_OBJECT_GET_CLASS (setting), name))
+					nm_setting_vpn_add_data_item (NM_SETTING_VPN (setting), name, value);
+			} else {
+				if (strcmp (name, "interface-name"))
+					nm_setting_bond_add_option (NM_SETTING_BOND (setting), name, value);
+			}
+		}
+		return;
+	}
+
+	if (NM_IS_SETTING_USER (setting)) {
+		gs_unref_hashtable GHashTable *data = NULL;
+
+		data = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+		for (iter = (const char *const*) keys; *iter; iter++) {
+			gs_free char *to_free = NULL;
+			char *value = NULL;
+			const char *name;
+
+			value = nm_keyfile_plugin_kf_get_string (file, setting_name, *iter, NULL);
+			if (!value)
+				continue;
+			name = nm_keyfile_key_decode (*iter, &to_free);
+			g_hash_table_insert (data,
+			                     g_steal_pointer (&to_free) ?: g_strdup (name),
+			                     value);
+		}
+		g_object_set (setting, NM_SETTING_USER_DATA, data, NULL);
+	}
+}
+
+static gsize
+unescape_semicolons (char *str)
+{
+	gsize i, j;
+
+	for (i = 0, j = 0; str[i]; ) {
+		if (str[i] == '\\' && str[i+1] == ';')
+			i++;
+		str[j++] = str[i++];;
+	}
+	str[j] = '\0';
+	return j;
+}
+
+static GBytes *
+get_bytes (KeyfileReaderInfo *info,
+           const char *setting_name,
+           const char *key,
+           gboolean zero_terminate,
+           gboolean unescape_semicolon)
+{
+	gs_free char *tmp_string = NULL;
+	gboolean may_be_int_list = TRUE;
+	gsize length;
+
+	/* New format: just a string
+	 * Old format: integer list; e.g. 11;25;38;
+	 */
+	tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+	if (!tmp_string)
+		return NULL;
+
+	/* if the string is empty, we return an empty GBytes array.
+	 * Note that for NM_SETTING_802_1X_PASSWORD_RAW both %NULL and
+	 * an empty GBytes are valid, and shall be destinguished. */
+	if (!tmp_string[0]) {
+		/* note that even if @zero_terminate is TRUE, we return an empty
+		 * byte-array. The reason is that zero_terminate is there to terminate
+		 * *valid* strings. It's not there to terminated invalid (empty) strings.
+		 */
+		return g_bytes_new_take (tmp_string, 0);
+	}
+
+	for (length = 0; tmp_string[length]; length++) {
+		const char ch = tmp_string[length];
+
+		if (   !g_ascii_isspace (ch)
+		    && !g_ascii_isdigit (ch)
+		    && ch != ';') {
+			may_be_int_list = FALSE;
+			length += strlen (&tmp_string[length]);
+			break;
+		}
+	}
+
+	/* Try to parse the string as a integer list. */
+	if (may_be_int_list && length > 0) {
+		gs_free guint8 *bin_data = NULL;
+		const char *const s = tmp_string;
+		gsize i, d;
+		const gsize BIN_DATA_LEN = (length / 2 + 3);
+
+		bin_data = g_malloc (BIN_DATA_LEN);
+
+#define DIGIT(c) ((c) - '0')
+		i = 0;
+		d = 0;
+		while (TRUE) {
+			int n;
+
+			/* leading whitespace */
+			while (g_ascii_isspace (s[i]))
+				i++;
+			if (s[i] == '\0')
+				break;
+			/* then expect 1 to 3 digits */
+			if (!g_ascii_isdigit (s[i])) {
+				d = 0;
+				break;
+			}
+			n = DIGIT (s[i]);
+			i++;
+			if (g_ascii_isdigit (s[i])) {
+				n = 10 * n + DIGIT (s[i]);
+				i++;
+				if (g_ascii_isdigit (s[i])) {
+					n = 10 * n + DIGIT (s[i]);
+					i++;
+				}
+			}
+			if (n > 255) {
+				d = 0;
+				break;
+			}
+
+			bin_data[d++] = n;
+			nm_assert (d < BIN_DATA_LEN);
+
+			/* allow whitespace after the digit. */
+			while (g_ascii_isspace (s[i]))
+				i++;
+			/* need a semicolon as separator. */
+			if (s[i] != ';') {
+				d = 0;
+				break;
+			}
+			i++;
+		}
+#undef DIGIT
+
+		/* Old format; list of ints. We already did a strict validation of the
+		 * string format before. We expect that this conversion cannot fail. */
+		if (d > 0) {
+			/* note that @zero_terminate does not add a terminating '\0' to
+			 * binary data as an integer list.
+			 *
+			 * But we add a '\0' to the bin_data pointer, just to avoid somebody
+			 * (erronously!) reading the binary data as C-string.
+			 *
+			 * @d itself does not entail the '\0'. */
+			nm_assert (d + 1 <= BIN_DATA_LEN);
+			bin_data = g_realloc (bin_data, d + 1);
+			bin_data[d] = '\0';
+			return g_bytes_new_take (g_steal_pointer (&bin_data), d);
+		}
+	}
+
+	/* Handle as a simple string (ie, new format) */
+	if (unescape_semicolon)
+		length = unescape_semicolons (tmp_string);
+	if (zero_terminate)
+		length++;
+	if (length == 0)
+		return NULL;
+	tmp_string = g_realloc (tmp_string, length + (zero_terminate ? 0 : 1));
+	return g_bytes_new_take (g_steal_pointer (&tmp_string), length);
+}
+
+static void
+ssid_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	GBytes *bytes;
+
+	bytes = get_bytes (info, setting_name, key, FALSE, TRUE);
+	if (bytes) {
+		g_object_set (setting, key, bytes, NULL);
+		g_bytes_unref (bytes);
+	} else if (!info->error) {
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid SSID"));
+	}
+}
+
+static void
+password_raw_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	GBytes *bytes;
+
+	bytes = get_bytes (info, setting_name, key, FALSE, TRUE);
+	if (bytes) {
+		g_object_set (setting, key, bytes, NULL);
+		g_bytes_unref (bytes);
+	} else if (!info->error) {
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid raw password"));
+	}
+}
+
+static char *
+get_cert_path (const char *base_dir, const guint8 *cert_path, gsize cert_path_len)
+{
+	const char *base;
+	char *p = NULL, *path, *tmp;
+
+	g_return_val_if_fail (base_dir != NULL, NULL);
+	g_return_val_if_fail (cert_path != NULL, NULL);
+
+	base = path = g_malloc0 (cert_path_len + 1);
+	memcpy (path, cert_path, cert_path_len);
+
+	if (path[0] == '/')
+		return path;
+
+	p = strrchr (path, '/');
+	if (p)
+		base = p + 1;
+
+	tmp = g_build_path ("/", base_dir, base, NULL);
+	g_free (path);
+	return tmp;
+}
+
+static const char *certext[] = { ".pem", ".cert", ".crt", ".cer", ".p12", ".der", ".key" };
+
+static gboolean
+has_cert_ext (const char *path)
+{
+	int i;
+
+	for (i = 0; i < G_N_ELEMENTS (certext); i++) {
+		if (g_str_has_suffix (path, certext[i]))
+			return TRUE;
+	}
+	return FALSE;
+}
+
+static gboolean
+handle_as_scheme (KeyfileReaderInfo *info, GBytes *bytes, NMSetting *setting, const char *key)
+{
+	const char *data;
+	gsize data_len, bin_len;
+
+	data = g_bytes_get_data (bytes, &data_len);
+
+	g_return_val_if_fail (data && data_len > 0, FALSE);
+
+	/* to be a scheme, @data must be a zero terminated string, which is counted by @data_len */
+	if (data[data_len - 1] != '\0')
+		return FALSE;
+	data_len--;
+
+	/* It's the PATH scheme, can just set plain data.
+	 * In this case, @data_len includes */
+	if (   data_len >= NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)
+	    && g_str_has_prefix (data, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)) {
+		if (nm_setting_802_1x_check_cert_scheme (data, data_len + 1, NULL) == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+			const char *path = &data[NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)];
+			gs_free char *path_free = NULL;
+
+			if (path[0] != '/') {
+				/* we want to read absolute paths because we use keyfile as exchange
+				 * between different processes which might not have the same cwd. */
+				path = path_free = get_cert_path (info->base_dir, (const guint8 *) path,
+				                                  data_len - NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH));
+			}
+
+			g_object_set (setting, key, bytes, NULL);
+			if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
+				handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE,
+				             _("certificate or key file '%s' does not exist"),
+				             path);
+			}
+		} else {
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid key/cert value path \"%s\""), data);
+		}
+		return TRUE;
+	}
+	if (   data_len >= NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PKCS11)
+	    && g_str_has_prefix (data, NM_KEYFILE_CERT_SCHEME_PREFIX_PKCS11)) {
+		if (nm_setting_802_1x_check_cert_scheme (data, data_len + 1, NULL) == NM_SETTING_802_1X_CK_SCHEME_PKCS11) {
+			g_object_set (setting, key, bytes, NULL);
+		} else {
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid PKCS#11 URI \"%s\""), data);
+		}
+		return TRUE;
+	}
+	if (   data_len > NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)
+	    && g_str_has_prefix (data, NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)) {
+		const char *cdata = data + NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB);
+		guchar *bin;
+		GBytes *bytes2;
+		gsize i;
+		gboolean valid_base64;
+
+		data_len -= NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB);
+
+		/* Let's be strict here. We expect valid base64, no funny stuff!!
+		 * We didn't write such invalid data ourselfes and refuse to read it as blob. */
+		if ((valid_base64 = (data_len % 4 == 0))) {
+			for (i = 0; i < data_len; i++) {
+				char c = cdata[i];
+
+				if (!(   (c >= 'a' && c <= 'z')
+				      || (c >= 'A' && c <= 'Z')
+				      || (c >= '0' && c <= '9')
+				      || (c == '+' || c == '/'))) {
+					if (c != '=' || i < data_len - 2)
+						valid_base64 = FALSE;
+					else {
+						for (; i < data_len; i++) {
+							if (cdata[i] != '=')
+								valid_base64 = FALSE;
+						}
+					}
+					break;
+				}
+			}
+		}
+		if (!valid_base64) {
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid key/cert value data:;base64, is not base64"));
+			return TRUE;
+		}
+
+		bin = g_base64_decode (cdata, &bin_len);
+
+		g_return_val_if_fail (bin_len > 0, FALSE);
+		if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) {
+			/* The blob probably starts with "file://". Setting the cert data will confuse NMSetting8021x.
+			 * In fact this is a limitation of NMSetting8021x which does not support setting blobs that start
+			 * with file://. Just warn and return TRUE to signal that we ~handled~ the setting. */
+			g_free (bin);
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid key/cert value data:;base64,file://"));
+		} else {
+			bytes2 = g_bytes_new_take (bin, bin_len);
+			g_object_set (setting, key, bytes2, NULL);
+			g_bytes_unref (bytes2);
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+char *
+nm_keyfile_detect_unqualified_path_scheme (const char *base_dir,
+                                           gconstpointer pdata,
+                                           gsize data_len,
+                                           gboolean consider_exists,
+                                           gboolean *out_exists)
+{
+	const char *data = pdata;
+	gboolean exists = FALSE;
+	gboolean success = FALSE;
+	gsize validate_len;
+	char *path;
+	GByteArray *tmp;
+
+	g_return_val_if_fail (base_dir && base_dir[0] == '/', NULL);
+
+	if (!pdata)
+		return NULL;
+	if (data_len == -1)
+		data_len = strlen (data);
+	if (data_len > 500 || data_len < 1)
+		return NULL;
+
+	/* If there's a trailing zero tell g_utf8_validate() to validate until the zero */
+	if (data[data_len - 1] == '\0') {
+		/* setting it to -1, would mean we accept data to contain NUL characters before the
+		 * end. Don't accept any NUL in [0 .. data_len-1[ . */
+		validate_len = data_len - 1;
+	} else
+		validate_len = data_len;
+	if (   validate_len == 0
+	    || g_utf8_validate ((const char *) data, validate_len, NULL) == FALSE)
+		 return NULL;
+
+	/* Might be a bare path without the file:// prefix; in that case
+	 * if it's an absolute path, use that, otherwise treat it as a
+	 * relative path to the current directory.
+	 */
+
+	path = get_cert_path (base_dir, (const guint8 *) data, data_len);
+	if (   !memchr (data, '/', data_len)
+	    && !has_cert_ext (path)) {
+		if (!consider_exists)
+			goto out;
+		exists = g_file_test (path, G_FILE_TEST_EXISTS);
+		if (!exists)
+			goto out;
+	} else if (out_exists)
+		exists = g_file_test (path, G_FILE_TEST_EXISTS);
+
+	/* Construct the proper value as required for the PATH scheme */
+	tmp = g_byte_array_sized_new (strlen (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) + strlen (path) + 1);
+	g_byte_array_append (tmp, (const guint8 *) NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, strlen (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH));
+	g_byte_array_append (tmp, (const guint8 *) path, strlen (path) + 1);
+	if (nm_setting_802_1x_check_cert_scheme (tmp->data, tmp->len, NULL) == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+		g_free (path);
+		path = (char *) g_byte_array_free (tmp, FALSE);
+		/* when returning TRUE, we must also be sure that @data_len does not look like
+		 * the deprecated format of list of integers. With this implementation that is the
+		 * case, as long as @consider_exists is FALSE. */
+		success = TRUE;
+	} else
+		g_byte_array_unref (tmp);
+
+out:
+	if (!success) {
+		g_free (path);
+		return NULL;
+	}
+	if (out_exists)
+		*out_exists = exists;
+	return path;
+}
+
+static gboolean
+handle_as_path (KeyfileReaderInfo *info,
+                GBytes *bytes,
+                NMSetting *setting,
+                const char *key)
+{
+	const guint8 *data;
+	gsize data_len;
+	char *path;
+	gboolean exists = FALSE;
+	GBytes *val;
+
+	data = g_bytes_get_data (bytes, &data_len);
+
+	path = nm_keyfile_detect_unqualified_path_scheme (info->base_dir, data, data_len, TRUE, &exists);
+	if (!path)
+		return FALSE;
+
+	/* Construct the proper value as required for the PATH scheme */
+	val = g_bytes_new_take (path, strlen (path) + 1);
+	g_object_set (setting, key, val, NULL);
+
+	/* Warn if the certificate didn't exist */
+	if (!exists) {
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE,
+		             _("certificate or key file '%s' does not exist"),
+		             path);
+	}
+	g_bytes_unref (val);
+
+	return TRUE;
+}
+
+static void
+cert_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	gs_unref_bytes GBytes *bytes = NULL;
+	gsize bin_len;
+	const char *bin;
+
+	bytes = get_bytes (info, setting_name, key, TRUE, FALSE);
+	if (bytes) {
+		/* Try as a path + scheme (ie, starts with "file://") */
+		if (handle_as_scheme (info, bytes, setting, key))
+			return;
+		if (info->error)
+			return;
+
+		/* If not, it might be a plain path */
+		if (handle_as_path (info, bytes, setting, key))
+			return;
+		if (info->error)
+			return;
+
+		bin = g_bytes_get_data (bytes, &bin_len);
+		if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) {
+			/* The blob probably starts with "file://" but contains invalid characters for a path.
+			 * Setting the cert data will confuse NMSetting8021x.
+			 * In fact, NMSetting8021x does not support setting such binary data, so just warn and
+			 * continue. */
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid key/cert value is not a valid blob"));
+		} else
+			g_object_set (setting, key, bytes, NULL);
+	} else if (!info->error) {
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("invalid key/cert value"));
+	}
+}
+
+static void
+parity_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	NMSettingSerialParity parity;
+	int int_val;
+	gs_free char *str_val = NULL;
+
+	/* Keyfile traditionally stored this as the ASCII value for 'E', 'o', or 'n'.
+	 * We now accept either that or the (case-insensitive) character itself (but
+	 * still always write it the old way, for backward compatibility).
+	 */
+	int_val = nm_keyfile_plugin_kf_get_integer (info->keyfile, setting_name, key, NULL);
+	if (!int_val) {
+		str_val = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+		if (str_val) {
+			if (str_val[0] && !str_val[1])
+				int_val = str_val[0];
+			else {
+				/* This will hit the warning below */
+				int_val = 'X';
+			}
+		}
+	}
+
+	if (!int_val)
+		return;
+
+	switch (int_val) {
+	case 'E':
+	case 'e':
+		parity = NM_SETTING_SERIAL_PARITY_EVEN;
+		break;
+	case 'O':
+	case 'o':
+		parity = NM_SETTING_SERIAL_PARITY_ODD;
+		break;
+	case 'N':
+	case 'n':
+		parity = NM_SETTING_SERIAL_PARITY_NONE;
+		break;
+	default:
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("invalid parity value '%s'"),
+		             str_val ? str_val : "");
+		return;
+	}
+
+	g_object_set (setting, key, parity, NULL);
+}
+
+static void
+team_config_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	gs_free char *conf = NULL;
+	gs_free_error GError *error = NULL;
+
+	conf = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+	if (conf && conf[0] && !nm_utils_is_json_object (conf, &error)) {
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid team configuration: %s"),
+		             error->message);
+		g_clear_pointer (&conf, g_free);
+	}
+
+	g_object_set (G_OBJECT (setting), key, conf, NULL);
+}
+
+static void
+qdisc_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	GPtrArray *qdiscs;
+	gs_strfreev gchar **keys = NULL;
+	gsize n_keys = 0;
+	int i;
+
+	qdiscs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_qdisc_unref);
+
+	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL);
+	if (!keys || n_keys == 0)
+		return;
+
+	for (i = 0; i < n_keys; i++) {
+		NMTCQdisc *qdisc;
+		const char *qdisc_parent;
+		gs_free char *qdisc_rest = NULL;
+		gs_free char *qdisc_str = NULL;
+		gs_free_error GError *err = NULL;
+
+		if (!g_str_has_prefix (keys[i], "qdisc."))
+			continue;
+
+		qdisc_parent = keys[i] + sizeof ("qdisc.") - 1;
+		qdisc_rest = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, keys[i], NULL);
+		qdisc_str = g_strdup_printf ("%s%s %s",
+		                             _nm_utils_parse_tc_handle (qdisc_parent, NULL) != TC_H_UNSPEC ? "parent " : "",
+		                             qdisc_parent,
+		                             qdisc_rest);
+
+		qdisc = nm_utils_tc_qdisc_from_str (qdisc_str, &err);
+		if (!qdisc) {
+			handle_warn (info, keys[i], NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid qdisc: %s"),
+			             err->message);
+		} else {
+			g_ptr_array_add (qdiscs, qdisc);
+		}
+	}
+
+	if (qdiscs->len >= 1)
+		g_object_set (setting, key, qdiscs, NULL);
+
+	g_ptr_array_unref (qdiscs);
+}
+
+static void
+tfilter_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	GPtrArray *tfilters;
+	gs_strfreev gchar **keys = NULL;
+	gsize n_keys = 0;
+	int i;
+
+	tfilters = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_tfilter_unref);
+
+	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL);
+	if (!keys || n_keys == 0)
+		return;
+
+	for (i = 0; i < n_keys; i++) {
+		NMTCTfilter *tfilter;
+		const char *tfilter_parent;
+		gs_free char *tfilter_rest = NULL;
+		gs_free char *tfilter_str = NULL;
+		gs_free_error GError *err = NULL;
+
+		if (!g_str_has_prefix (keys[i], "tfilter."))
+			continue;
+
+		tfilter_parent = keys[i] + sizeof ("tfilter.") - 1;
+		tfilter_rest = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, keys[i], NULL);
+		tfilter_str = g_strdup_printf ("%s%s %s",
+		                             _nm_utils_parse_tc_handle (tfilter_parent, NULL) != TC_H_UNSPEC ? "parent " : "",
+		                             tfilter_parent,
+		                             tfilter_rest);
+
+		tfilter = nm_utils_tc_tfilter_from_str (tfilter_str, &err);
+		if (!tfilter) {
+			handle_warn (info, keys[i], NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid tfilter: %s"),
+			             err->message);
+		} else {
+			g_ptr_array_add (tfilters, tfilter);
+		}
+	}
+
+	if (tfilters->len >= 1)
+		g_object_set (setting, key, tfilters, NULL);
+
+	g_ptr_array_unref (tfilters);
+}
+
+typedef struct {
+	const char *setting_name;
+	const char *key;
+	gboolean check_for_key;
+	void (*parser) (KeyfileReaderInfo *info, NMSetting *setting, const char *key);
+} KeyParser;
+
+/* A table of keys that require further parsing/conversion because they are
+ * stored in a format that can't be automatically read using the key's type.
+ * i.e. IPv4 addresses, which are stored in NetworkManager as guint32, but are
+ * stored in keyfiles as strings, eg "10.1.1.2" or IPv6 addresses stored
+ * in struct in6_addr internally, but as string in keyfiles.
+ */
+static KeyParser key_parsers[] = {
+	{ NM_SETTING_CONNECTION_SETTING_NAME,
+	  NM_SETTING_CONNECTION_TYPE,
+	  TRUE,
+	  setting_alias_parser },
+	{ NM_SETTING_BRIDGE_SETTING_NAME,
+	  NM_SETTING_BRIDGE_MAC_ADDRESS,
+	  TRUE,
+	  mac_address_parser_ETHER },
+	{ NM_SETTING_IP4_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_ADDRESSES,
+	  FALSE,
+	  ip_address_or_route_parser },
+	{ NM_SETTING_IP6_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_ADDRESSES,
+	  FALSE,
+	  ip_address_or_route_parser },
+	{ NM_SETTING_IP4_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_ROUTES,
+	  FALSE,
+	  ip_address_or_route_parser },
+	{ NM_SETTING_IP6_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_ROUTES,
+	  FALSE,
+	  ip_address_or_route_parser },
+	{ NM_SETTING_IP4_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_DNS,
+	  FALSE,
+	  ip4_dns_parser },
+	{ NM_SETTING_IP6_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP_CONFIG_DNS,
+	  FALSE,
+	  ip6_dns_parser },
+	{ NM_SETTING_IP6_CONFIG_SETTING_NAME,
+	  NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
+	  FALSE,
+	  ip6_addr_gen_mode_parser },
+	{ NM_SETTING_WIRED_SETTING_NAME,
+	  NM_SETTING_WIRED_MAC_ADDRESS,
+	  TRUE,
+	  mac_address_parser_ETHER },
+	{ NM_SETTING_WIRED_SETTING_NAME,
+	  NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
+	  TRUE,
+	  mac_address_parser_ETHER_cloned },
+	{ NM_SETTING_WIRELESS_SETTING_NAME,
+	  NM_SETTING_WIRELESS_MAC_ADDRESS,
+	  TRUE,
+	  mac_address_parser_ETHER },
+	{ NM_SETTING_WIRELESS_SETTING_NAME,
+	  NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS,
+	  TRUE,
+	  mac_address_parser_ETHER_cloned },
+	{ NM_SETTING_WIRELESS_SETTING_NAME,
+	  NM_SETTING_WIRELESS_BSSID,
+	  TRUE,
+	  mac_address_parser_ETHER },
+	{ NM_SETTING_BLUETOOTH_SETTING_NAME,
+	  NM_SETTING_BLUETOOTH_BDADDR,
+	  TRUE,
+	  mac_address_parser_ETHER },
+	{ NM_SETTING_INFINIBAND_SETTING_NAME,
+	  NM_SETTING_INFINIBAND_MAC_ADDRESS,
+	  TRUE,
+	  mac_address_parser_INFINIBAND },
+	{ NM_SETTING_WIMAX_SETTING_NAME,
+	  NM_SETTING_WIMAX_MAC_ADDRESS,
+	  TRUE,
+	  mac_address_parser_ETHER },
+	{ NM_SETTING_WIRELESS_SETTING_NAME,
+	  NM_SETTING_WIRELESS_SSID,
+	  TRUE,
+	  ssid_parser },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_PASSWORD_RAW,
+	  TRUE,
+	  password_raw_parser },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_CA_CERT,
+	  TRUE,
+	  cert_parser },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_CLIENT_CERT,
+	  TRUE,
+	  cert_parser },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_PRIVATE_KEY,
+	  TRUE,
+	  cert_parser },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_PHASE2_CA_CERT,
+	  TRUE,
+	  cert_parser },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_PHASE2_CLIENT_CERT,
+	  TRUE,
+	  cert_parser },
+	{ NM_SETTING_802_1X_SETTING_NAME,
+	  NM_SETTING_802_1X_PHASE2_PRIVATE_KEY,
+	  TRUE,
+	  cert_parser },
+	{ NM_SETTING_SERIAL_SETTING_NAME,
+	  NM_SETTING_SERIAL_PARITY,
+	  TRUE,
+	  parity_parser },
+	{ NM_SETTING_TEAM_SETTING_NAME,
+	  NM_SETTING_TEAM_CONFIG,
+	  TRUE,
+	  team_config_parser },
+	{ NM_SETTING_TEAM_PORT_SETTING_NAME,
+	  NM_SETTING_TEAM_CONFIG,
+	  TRUE,
+	  team_config_parser },
+        { NM_SETTING_TC_CONFIG_SETTING_NAME,
+          NM_SETTING_TC_CONFIG_QDISCS,
+	  FALSE,
+          qdisc_parser },
+        { NM_SETTING_TC_CONFIG_SETTING_NAME,
+          NM_SETTING_TC_CONFIG_TFILTERS,
+	  FALSE,
+          tfilter_parser },
+	{ NULL, NULL, FALSE }
+};
+
+static void
+set_default_for_missing_key (NMSetting *setting, const char *property)
+{
+	/* Set a value different from the default value of the property's spec */
+}
+
+static void
+read_one_setting_value (NMSetting *setting,
+                        const char *key,
+                        const GValue *value,
+                        GParamFlags flags,
+                        gpointer user_data)
+{
+	KeyfileReaderInfo *info = user_data;
+	GKeyFile *keyfile = info->keyfile;
+	const char *setting_name;
+	int errsv;
+	GType type;
+	gs_free_error GError *err = NULL;
+	gboolean check_for_key = TRUE;
+	KeyParser *parser = &key_parsers[0];
+
+	if (info->error)
+		return;
+
+	/* Property is not writable */
+	if (!(flags & G_PARAM_WRITABLE))
+		return;
+
+	/* Setting name gets picked up from the keyfile's section name instead */
+	if (!strcmp (key, NM_SETTING_NAME))
+		return;
+
+	/* Don't read the NMSettingConnection object's 'read-only' property */
+	if (   NM_IS_SETTING_CONNECTION (setting)
+	    && !strcmp (key, NM_SETTING_CONNECTION_READ_ONLY))
+		return;
+
+	if (   (   NM_IS_SETTING_TEAM (setting)
+	        || NM_IS_SETTING_TEAM_PORT (setting))
+	    && !NM_IN_STRSET (key, NM_SETTING_TEAM_CONFIG)) {
+		/* silently ignore all team properties (except "config"). */
+		return;
+	}
+
+	setting_name = nm_setting_get_name (setting);
+
+	/* Look through the list of handlers for non-standard format key values */
+	while (parser->setting_name) {
+		if (!strcmp (parser->setting_name, setting_name) && !strcmp (parser->key, key)) {
+			check_for_key = parser->check_for_key;
+			break;
+		}
+		parser++;
+	}
+
+	if (NM_IS_SETTING_VPN (setting))
+		check_for_key = FALSE;
+	else if (NM_IS_SETTING_USER (setting))
+		check_for_key = FALSE;
+	else if (NM_IS_SETTING_BOND (setting))
+		check_for_key = FALSE;
+
+	/* Check for the exact key in the GKeyFile if required.  Most setting
+	 * properties map 1:1 to a key in the GKeyFile, but for those properties
+	 * like IP addresses and routes where more than one value is actually
+	 * encoded by the setting property, this won't be true.
+	 */
+	if (check_for_key && !nm_keyfile_plugin_kf_has_key (keyfile, setting_name, key, &err)) {
+		/* Key doesn't exist or an error ocurred, thus nothing to do. */
+		if (err) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("error loading setting value: %s"),
+			                  err->message))
+				goto out_error;
+		}
+
+		/* Allow default values different than in property spec */
+		set_default_for_missing_key (setting, key);
+		return;
+	}
+
+	/* If there's a custom parser for this key, handle that before the generic
+	 * parsers below.
+	 */
+	if (parser->setting_name) {
+		(*parser->parser) (info, setting, key);
+		return;
+	}
+
+	type = G_VALUE_TYPE (value);
+
+	if (type == G_TYPE_STRING) {
+		char *str_val;
+
+		str_val = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL);
+		g_object_set (setting, key, str_val, NULL);
+		g_free (str_val);
+	} else if (type == G_TYPE_UINT) {
+		int int_val;
+
+		int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, NULL);
+		if (int_val < 0) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("invalid negative value (%i)"),
+			                  int_val))
+				goto out_error;
+		}
+		g_object_set (setting, key, int_val, NULL);
+	} else if (type == G_TYPE_INT) {
+		int int_val;
+
+		int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, NULL);
+		g_object_set (setting, key, int_val, NULL);
+	} else if (type == G_TYPE_BOOLEAN) {
+		gboolean bool_val;
+
+		bool_val = nm_keyfile_plugin_kf_get_boolean (keyfile, setting_name, key, NULL);
+		g_object_set (setting, key, bool_val, NULL);
+	} else if (type == G_TYPE_CHAR) {
+		int int_val;
+
+		int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, NULL);
+		if (int_val < G_MININT8 || int_val > G_MAXINT8) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("invalid char value (%i)"),
+			                  int_val))
+				goto out_error;
+		}
+
+		g_object_set (setting, key, int_val, NULL);
+	} else if (type == G_TYPE_UINT64) {
+		char *tmp_str;
+		guint64 uint_val;
+
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, NULL);
+		uint_val = g_ascii_strtoull (tmp_str, NULL, 10);
+		g_free (tmp_str);
+		g_object_set (setting, key, uint_val, NULL);
+	} else if (type == G_TYPE_INT64) {
+		gs_free char *tmp_str = NULL;
+		gint64 int_val;
+
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, NULL);
+		int_val = _nm_utils_ascii_str_to_int64 (tmp_str, 10, G_MININT64, G_MAXINT64, 0);
+		errsv = errno;
+		if (errsv) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("invalid int64 value (%s)"),
+			                  tmp_str))
+				goto out_error;
+		} else
+			g_object_set (setting, key, int_val, NULL);
+	} else if (type == G_TYPE_BYTES) {
+		gint *tmp;
+		GByteArray *array;
+		GBytes *bytes;
+		gsize length;
+		int i;
+		gboolean already_warned = FALSE;
+
+		tmp = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_name, key, &length, NULL);
+
+		array = g_byte_array_sized_new (length);
+		for (i = 0; i < length; i++) {
+			int val = tmp[i];
+			unsigned char v = (unsigned char) (val & 0xFF);
+
+			if (val < 0 || val > 255) {
+				if (   !already_warned
+				    && !handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+				                     _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"),
+				                     val)) {
+					g_byte_array_unref (array);
+					g_free (tmp);
+					goto out_error;
+				}
+				already_warned = TRUE;
+			} else
+				g_byte_array_append (array, (const unsigned char *) &v, sizeof (v));
+		}
+
+		bytes = g_byte_array_free_to_bytes (array);
+		g_object_set (setting, key, bytes, NULL);
+		g_bytes_unref (bytes);
+		g_free (tmp);
+	} else if (type == G_TYPE_STRV) {
+		gchar **sa;
+		gsize length;
+
+		sa = nm_keyfile_plugin_kf_get_string_list (keyfile, setting_name, key, &length, NULL);
+		g_object_set (setting, key, sa, NULL);
+		g_strfreev (sa);
+	} else if (type == G_TYPE_HASH_TABLE) {
+		read_hash_of_string (keyfile, setting, key);
+	} else if (type == G_TYPE_ARRAY) {
+		read_array_of_uint (keyfile, setting, key);
+	} else if (G_VALUE_HOLDS_FLAGS (value)) {
+		guint64 uint_val;
+
+		/* Flags are guint but GKeyFile has no uint reader, just uint64 */
+		uint_val = nm_keyfile_plugin_kf_get_uint64 (keyfile, setting_name, key, &err);
+		if (!err) {
+			if (uint_val <= G_MAXUINT)
+				g_object_set (setting, key, (guint) uint_val, NULL);
+			else {
+				if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+				                  _("too large FLAGS property '%s' (%llu)"),
+				                  G_VALUE_TYPE_NAME (value), (unsigned long long) uint_val))
+					goto out_error;
+			}
+		}
+	} else if (G_VALUE_HOLDS_ENUM (value)) {
+		gint int_val;
+
+		int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, &err);
+		if (!err)
+			g_object_set (setting, key, (gint) int_val, NULL);
+	} else {
+		if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		                 _("unhandled setting property type '%s'"),
+		                 G_VALUE_TYPE_NAME (value)))
+			goto out_error;
+	}
+out_error:
+	return;
+}
+
+static NMSetting *
+read_setting (KeyfileReaderInfo *info)
+{
+	const char *alias;
+	GType type;
+
+	alias = nm_keyfile_plugin_get_setting_name_for_alias (info->group);
+	if (!alias)
+		alias = info->group;
+
+	type = nm_setting_lookup_type (alias);
+	if (type) {
+		NMSetting *setting = g_object_new (type, NULL);
+
+		info->setting = setting;
+		nm_setting_enumerate_values (setting, read_one_setting_value, info);
+		info->setting = NULL;
+		if (!info->error)
+			return setting;
+
+		g_object_unref (setting);
+	} else {
+		handle_warn (info, NULL, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("invalid setting name '%s'"), info->group);
+	}
+
+	return NULL;
+}
+
+static void
+read_vpn_secrets (KeyfileReaderInfo *info, NMSettingVpn *s_vpn)
+{
+	char **keys, **iter;
+
+	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, NULL, NULL);
+	for (iter = keys; *iter; iter++) {
+		char *secret;
+
+		secret = nm_keyfile_plugin_kf_get_string (info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, *iter, NULL);
+		if (secret) {
+			nm_setting_vpn_add_secret (s_vpn, *iter, secret);
+			g_free (secret);
+		}
+	}
+	g_strfreev (keys);
+}
+
+/**
+ * nm_keyfile_read:
+ * @keyfile: the keyfile from which to create the connection
+ * @keyfile_name: keyfile allows missing connection id and uuid
+ *   and NetworkManager will create those when reading a connection
+ *   from file. By providing a filename you can reproduce that behavior,
+ *   but of course, it can only recreate the same UUID if you provide the
+ *   same filename as NetworkManager core daemon would.
+ *   @keyfile_name has only a relevance for setting the id or uuid if it
+ *   is missing and as fallback for @base_dir.
+ * @base_dir: when reading certificates from files with relative name,
+ *   the relative path is made absolute using @base_dir.
+ *   If @base_dir is missing, first try to get the pathname from @keyfile_name
+ *   (if it is given as absolute path). As last, fallback to the current path.
+ * @handler: read handler
+ * @user_data: user data for read handler
+ * @error: error
+ *
+ * Tries to create a NMConnection from a keyfile. The resulting keyfile is
+ * not normalized and might not even verify.
+ *
+ * Returns: (transfer full): on success, returns the created connection.
+ */
+NMConnection *
+nm_keyfile_read (GKeyFile *keyfile,
+                 const char *keyfile_name,
+                 const char *base_dir,
+                 NMKeyfileReadHandler handler,
+                 void *user_data,
+                 GError **error)
+{
+	NMConnection *connection = NULL;
+	NMSettingConnection *s_con;
+	NMSetting *setting;
+	gchar **groups;
+	gsize length;
+	int i;
+	gboolean vpn_secrets = FALSE;
+	KeyfileReaderInfo info = { 0 };
+	gs_free char *base_dir_free = NULL;
+
+	g_return_val_if_fail (keyfile, NULL);
+	g_return_val_if_fail (!error || !*error, NULL);
+
+	if (!base_dir) {
+		/* basedir is not given. Prefer it from the keyfile_name */
+		if (keyfile_name && keyfile_name[0] == '/') {
+			base_dir = base_dir_free = g_path_get_dirname (keyfile_name);
+		} else {
+			/* if keyfile is not given or not an absolute path, fallback
+			 * to current working directory. */
+			base_dir = base_dir_free = g_get_current_dir ();
+		}
+	} else
+		g_return_val_if_fail ("/", NULL);
+
+	connection = nm_simple_connection_new ();
+
+	info.connection = connection;
+	info.keyfile = (GKeyFile *) keyfile;
+	info.base_dir = base_dir;
+	info.handler = handler;
+	info.user_data = user_data;
+
+	groups = g_key_file_get_groups (keyfile, &length);
+	if (!groups)
+		length = 0;
+	for (i = 0; i < length; i++) {
+		/* Only read out secrets when needed */
+		if (!strcmp (groups[i], NM_KEYFILE_GROUP_VPN_SECRETS)) {
+			vpn_secrets = TRUE;
+			continue;
+		}
+
+		info.group = groups[i];
+		setting = read_setting (&info);
+		info.group = NULL;
+		if (info.error)
+			goto out_error;
+		if (setting)
+			nm_connection_add_setting (connection, setting);
+	}
+	g_strfreev (groups);
+
+	s_con = nm_connection_get_setting_connection (connection);
+	if (!s_con) {
+		s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
+		nm_connection_add_setting (connection, NM_SETTING (s_con));
+	}
+
+	/* Make sure that we have 'id' even if not explictly specified in the keyfile */
+	if (   keyfile_name
+	    && !nm_setting_connection_get_id (s_con)) {
+		char *base_name;
+
+		base_name = g_path_get_basename (keyfile_name);
+		g_object_set (s_con, NM_SETTING_CONNECTION_ID, base_name, NULL);
+		g_free (base_name);
+	}
+
+	/* Make sure that we have 'uuid' even if not explictly specified in the keyfile */
+	if (   keyfile_name
+	    && !nm_setting_connection_get_uuid (s_con)) {
+		char *hashed_uuid;
+
+		hashed_uuid = _nm_utils_uuid_generate_from_strings ("keyfile", keyfile_name, NULL);
+		g_object_set (s_con, NM_SETTING_CONNECTION_UUID, hashed_uuid, NULL);
+		g_free (hashed_uuid);
+	}
+
+	/* Make sure that we have 'interface-name' even if it was specified in the
+	 * "wrong" (ie, deprecated) group.
+	 */
+	if (   !nm_setting_connection_get_interface_name (s_con)
+	    && nm_setting_connection_get_connection_type (s_con)) {
+		char *interface_name;
+
+		interface_name = g_key_file_get_string (keyfile,
+		                                        nm_setting_connection_get_connection_type (s_con),
+		                                        "interface-name",
+		                                        NULL);
+		if (interface_name) {
+			g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, interface_name, NULL);
+			g_free (interface_name);
+		}
+	}
+
+	/* Handle vpn secrets after the 'vpn' setting was read */
+	if (vpn_secrets) {
+		NMSettingVpn *s_vpn;
+
+		s_vpn = nm_connection_get_setting_vpn (connection);
+		if (s_vpn) {
+			read_vpn_secrets (&info, s_vpn);
+			if (info.error)
+				goto out_error;
+		}
+	}
+
+	return connection;
+out_error:
+	g_propagate_error (error, info.error);
+	g_free (connection);
+	return NULL;
+}