summary refs log tree commit diff
path: root/src/settings/plugins/keyfile/reader.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/settings/plugins/keyfile/reader.c')
-rw-r--r--src/settings/plugins/keyfile/reader.c1349
1 files changed, 62 insertions, 1287 deletions
diff --git a/src/settings/plugins/keyfile/reader.c b/src/settings/plugins/keyfile/reader.c
index 1f0953e0..bd08c17d 100644
--- a/src/settings/plugins/keyfile/reader.c
+++ b/src/settings/plugins/keyfile/reader.c
@@ -15,1253 +15,83 @@
  * 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 - 2011 Red Hat, Inc.
+ * Copyright (C) 2015 Red Hat, Inc.
  */
 
 #include "config.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 "nm-core-internal.h"
-#include "nm-dbus-glib-types.h"
-#include "nm-glib-compat.h"
-#include "nm-system-config-interface.h"
-#include "nm-logging.h"
 #include "reader.h"
-#include "common.h"
-#include "utils.h"
-#include "nm-core-internal.h"
-#include "NetworkManagerUtils.h"
-
-/* 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 (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	char *s;
-	const char *key_setting_name;
-
-	s = nm_keyfile_plugin_kf_get_string (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 gboolean
-read_array_of_uint (GKeyFile *file,
-                    NMSetting *setting,
-                    const char *key)
-{
-	GArray *array = NULL;
-	gsize length;
-	int i;
-	gint *tmp;
-
-	tmp = nm_keyfile_plugin_kf_get_integer_list (file, nm_setting_get_name (setting), key, &length, NULL);
-	array = g_array_sized_new (FALSE, FALSE, sizeof (guint32), length);
-	g_return_val_if_fail (array != NULL, FALSE);
-
-	for (i = 0; i < length; i++)
-		g_array_append_val (array, tmp[i]);
-
-	g_object_set (setting, key, array, NULL);
-	g_array_unref (array);
-
-	return TRUE;
-}
-
-static gboolean
-get_one_int (const char *str, guint32 max_val, const char *key_name, guint32 *out)
-{
-	long tmp;
-	char *endptr;
-
-	if (!str || !str[0]) {
-		if (key_name)
-			nm_log_warn (LOGD_SETTINGS, "%s: ignoring missing number %s", __func__, key_name);
-		return FALSE;
-	}
-
-	errno = 0;
-	tmp = strtol (str, &endptr, 10);
-	if (errno || (tmp < 0) || (tmp > max_val) || *endptr != 0) {
-		if (key_name)
-			nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid number %s '%s'", __func__, key_name, str);
-		return FALSE;
-	}
-
-	*out = (guint32) tmp;
-	return TRUE;
-}
-
-static gpointer
-build_address (int family, const char *address_str, guint32 plen)
-{
-	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) {
-		nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid %s address: %s", __func__,
-		             family == AF_INET ? "IPv4" : "IPv6",
-		             error->message);
-		g_error_free (error);
-	}
-
-	return addr;
-}
-
-static gpointer
-build_route (int family,
-             const char *dest_str, guint32 plen,
-             const char *gateway_str, const char *metric_str,
-             const char *key_name)
-{
-	NMIPRoute *route;
-	guint32 metric = 0;
-	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 (gateway_str, G_MAXUINT32, NULL, &metric))
-				gateway_str = NULL;
-			else {
-				nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid gateway '%s'", __func__, gateway_str);
-				return NULL;
-			}
-		}
-	} else
-		gateway_str = NULL;
-
-	/* parse metric, default to 0 */
-	if (metric_str) {
-		if (!get_one_int (metric_str, G_MAXUINT32, key_name, &metric))
-			return NULL;
-	}
-
-	route = nm_ip_route_new (family, dest_str, plen, gateway_str,
-	                         metric ? (gint64) metric : -1,
-	                         &error);
-	if (!route) {
-		nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid %s route: %s", __func__,
-		             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 char *
-read_field (char **current, char **error, const char *characters, const char *delimiters)
-{
-	char *start;
-
-	g_return_val_if_fail (current, NULL);
-	g_return_val_if_fail (error, NULL);
-	g_return_val_if_fail (characters, NULL);
-	g_return_val_if_fail (delimiters, NULL);
-
-	if (!*current) {
-		/* graceful failure, leave '*current' NULL */
-		*error = 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 */
-			*error = NULL;
-			*(*current)++ = '\0';
-			return start;
-		} else {
-			/* error, bad character */
-			*error = *current;
-			*current = start;
-			return NULL;
-		}
-	else {
-		/* success, end of input */
-		*error = NULL;
-		*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 (GKeyFile *file,
-                              const char *setting_name,
-                              const char *key_name,
-                              gboolean ipv6,
-                              gboolean route,
-                              char **out_gateway)
-{
-	guint32 plen = G_MAXUINT32;
-	gpointer result;
-	char *address_str, *plen_str, *gateway_str, *metric_str, *value, *current, *error;
-
-	current = value = nm_keyfile_plugin_kf_get_string (file, setting_name, key_name, NULL);
-	if (!value)
-		return NULL;
-
-	/* get address field */
-	address_str = read_field (&current, &error, IP_ADDRESS_CHARS, DELIMITERS);
-	if (error) {
-		nm_log_warn (LOGD_SETTINGS, "keyfile: Unexpected character '%c' in '%s.%s' address (position %td of '%s').",
-		             *error, setting_name, key_name, error - current, current);
-		goto error;
-	}
-	/* get prefix length field (skippable) */
-	plen_str = read_field (&current, &error, DIGITS, DELIMITERS);
-	/* get gateway field */
-	gateway_str = read_field (&current, &error, IP_ADDRESS_CHARS, DELIMITERS);
-	if (error) {
-		nm_log_warn (LOGD_SETTINGS, "keyfile: Unexpected character '%c' in '%s.%s' %s (position %td of '%s').",
-		             *error, setting_name, key_name,
-		             plen_str ? "gateway" : "gateway or prefix length",
-		             error - current, current);
-		goto error;
-	}
-	/* for routes, get metric */
-	if (route) {
-		metric_str = read_field (&current, &error, DIGITS, DELIMITERS);
-		if (error) {
-			nm_log_warn (LOGD_SETTINGS, "keyfile: Unexpected character '%c' in '%s.%s' prefix length (position %td of '%s').",
-			             *error, setting_name, key_name, error - current, current);
-			goto error;
-		}
-	} else
-		metric_str = NULL;
-	if (current) {
-		/* there is still some data */
-		if (*current) {
-			/* another field follows */
-			nm_log_warn (LOGD_SETTINGS, "keyfile: %s.%s: Garbage at the and of the line: %s",
-			             setting_name, key_name, current);
-			goto error;
-		} else {
-			/* semicolon at the end of input */
-			nm_log_info (LOGD_SETTINGS, "keyfile: %s.%s: Deprecated semicolon at the end of value.",
-			             setting_name, key_name);
-		}
-	}
-
-#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 (plen_str, ipv6 ? 128 : 32, key_name, &plen)
-		    || (route && plen == 0)) {
-			plen = DEFAULT_PREFIX (route, ipv6);
-			nm_log_warn (LOGD_SETTINGS, "keyfile: invalid prefix length '%s' in '%s.%s', defaulting to %d",
-			             plen_str, setting_name, key_name, plen);
-		}
-	} else {
-		plen = DEFAULT_PREFIX (route, ipv6);
-		nm_log_warn (LOGD_SETTINGS, "keyfile: Missing prefix length in '%s.%s', defaulting to %d",
-		             setting_name, key_name, plen);
-	}
-
-	/* build the appropriate data structure for NetworkManager settings */
-	if (route) {
-		result = build_route (ipv6 ? AF_INET6 : AF_INET,
-		                      address_str, plen, gateway_str, metric_str,
-		                      key_name);
-	} else {
-		result = build_address (ipv6 ? AF_INET6 : AF_INET,
-		                        address_str, plen);
-		if (out_gateway && gateway_str)
-			*out_gateway = g_strdup (gateway_str);
-	}
 
-	g_free (value);
-	return result;
-error:
-	g_free (value);
-	return NULL;
-}
+#include "nm-logging.h"
+#include "nm-keyfile-internal.h"
+#include "NetworkManagerUtils.h"
 
-static void
-ip_address_or_route_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path)
+static const char *
+_fmt_warn (const char *group, NMSetting *setting, const char *property_name, const char *message, char **out_message)
 {
-	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);
+	const char *setting_name = setting ? nm_setting_get_name (setting) : NULL;
 
-	for (i = -1; i < 1000; i++) {
-		const char **key_basename;
-		
-		for (key_basename = key_names; *key_basename; key_basename++) {
-			char *key_name;
-			gpointer item;
+	if (group) {
+		char *res;
 
-			/* -1 means no suffix */
-			if (i >= 0)
-				key_name = g_strdup_printf ("%s%d", *key_basename, i);
+		if (setting_name) {
+			if (property_name && !strcmp (group, setting_name))
+				res = g_strdup_printf ("%s.%s: %s", group, property_name, message);
+			else if (property_name)
+				res = g_strdup_printf ("%s/%s.%s: %s", group, setting_name, property_name, message);
+			else if (!strcmp (group, setting_name))
+				res = g_strdup_printf ("%s: %s", group, message);
 			else
-				key_name = g_strdup (*key_basename);
-
-			item = read_one_ip_address_or_route (keyfile, setting_name, key_name, ipv6, routes,
-			                                     gateway ? NULL : &gateway);
-			if (item)
-				g_ptr_array_add (list, item);
-
-			g_free (key_name);
-		}
-	}
-
-	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 (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path)
-{
-	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 (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) {
-			nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid DNS server address '%s'", __func__, *iter);
-			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 (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path)
-{
-	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 (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) {
-			nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid DNS server IPv6 address '%s'", __func__, *iter);
-			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
-mac_address_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path, gsize enforce_length)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	char *tmp_string = NULL, *p, *mac_str;
-	gint *tmp_list;
-	GByteArray *array = NULL;
-	gsize length;
-
-	p = tmp_string = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL);
-	if (tmp_string && tmp_string[0]) {
-		/* Look for enough ':' characters to signify a MAC address */
-		guint i = 0;
-
-		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 */
-			array = g_byte_array_sized_new (i+1);
-			g_byte_array_set_size (array, i+1);
-			if (!nm_utils_hwaddr_aton (tmp_string, array->data, array->len)) {
-				g_byte_array_unref (array);
-				array = NULL;
-			}
-		}
-	}
-	g_free (tmp_string);
-
-	if (array == NULL) {
-		/* Old format; list of ints */
-		tmp_list = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_name, key, &length, NULL);
-		if (length > 0 && (enforce_length == 0 || enforce_length == length)) {
-			gsize i;
-
-			array = g_byte_array_sized_new (length);
-			for (i = 0; i < length; i++) {
-				int val = tmp_list[i];
-				const guint8 v = (guint8) (val & 0xFF);
-
-				if (val < 0 || val > 255) {
-					nm_log_warn (LOGD_SETTINGS, "%s: %s / %s ignoring invalid byte element '%d' (not "
-					             " between 0 and 255 inclusive)", __func__, setting_name,
-					             key, val);
-					g_byte_array_free (array, TRUE);
-					array = NULL;
-					break;
-				}
-				g_byte_array_append (array, &v, 1);
-			}
-		}
-		g_free (tmp_list);
-	}
-
-	if (!array) {
-		nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid MAC address for %s / %s",
-		             __func__, setting_name, key);
-		return;
-	}
-
-	mac_str = nm_utils_hwaddr_ntoa (array->data, array->len);
-	g_object_set (setting, key, mac_str, NULL);
-	g_free (mac_str);
-	g_byte_array_free (array, TRUE);
-}
-
-static void
-mac_address_parser_ETHER (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path)
-{
-	mac_address_parser (setting, key, keyfile, keyfile_path, ETH_ALEN);
-}
-
-static void
-mac_address_parser_INFINIBAND (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path)
-{
-	mac_address_parser (setting, key, keyfile, keyfile_path, INFINIBAND_ALEN);
-}
-
-static void
-read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key)
-{
-	char **keys, **iter;
-	char *value;
-	const char *setting_name = nm_setting_get_name (setting);
-
-	keys = nm_keyfile_plugin_kf_get_keys (file, setting_name, NULL, NULL);
-	if (!keys || !*keys)
-		return;
-
-	for (iter = keys; *iter; iter++) {
-		value = nm_keyfile_plugin_kf_get_string (file, setting_name, *iter, NULL);
-		if (!value)
-			continue;
-
-		if (NM_IS_SETTING_VPN (setting)) {
-			/* Add any item that's not a class property to the data hash */
-			if (!g_object_class_find_property (G_OBJECT_GET_CLASS (setting), *iter))
-				nm_setting_vpn_add_data_item (NM_SETTING_VPN (setting), *iter, value);
-		}
-		if (NM_IS_SETTING_BOND (setting)) {
-			if (strcmp (*iter, "interface-name"))
-				nm_setting_bond_add_option (NM_SETTING_BOND (setting), *iter, value);
-		}
-		g_free (value);
-	}
-	g_strfreev (keys);
-}
-
-static void
-unescape_semicolons (char *str)
-{
-	int i;
-	gsize len = strlen (str);
-
-	for (i = 0; i < len; i++) {
-		if (str[i] == '\\' && str[i+1] == ';') {
-			memmove(str + i, str + i + 1, len - (i + 1));
-			len--;
-		}
-		str[len] = '\0';
-	}
-}
-
-static GBytes *
-get_bytes (GKeyFile *keyfile,
-           const char *setting_name,
-           const char *key,
-           gboolean zero_terminate,
-           gboolean unescape_semicolon)
-{
-	GByteArray *array = NULL;
-	char *tmp_string;
-	gint *tmp_list;
-	gsize length;
-	int i;
-
-	/* New format: just a string
-	 * Old format: integer list; e.g. 11;25;38;
-	 */
-	tmp_string = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL);
-	if (tmp_string) {
-		GRegex *regex;
-		GMatchInfo *match_info;
-		const char *pattern = "^[[:space:]]*[[:digit:]]{1,3}[[:space:]]*;([[:space:]]*[[:digit:]]{1,3}[[:space:]]*;)*([[:space:]]*)?$";
-
-		regex = g_regex_new (pattern, 0, 0, NULL);
-		g_regex_match (regex, tmp_string, 0, &match_info);
-		if (!g_match_info_matches (match_info)) {
-			/* Handle as a simple string (ie, new format) */
-			if (unescape_semicolon)
-				unescape_semicolons (tmp_string);
-			length = strlen (tmp_string);
-			if (zero_terminate)
-				length++;
-			array = g_byte_array_sized_new (length);
-			g_byte_array_append (array, (guint8 *) tmp_string, length);
-		}
-		g_match_info_free (match_info);
-		g_regex_unref (regex);
-		g_free (tmp_string);
-	}
-
-	if (!array) {
-		/* Old format; list of ints */
-		tmp_list = 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_list[i];
-			unsigned char v = (unsigned char) (val & 0xFF);
-
-			if (val < 0 || val > 255) {
-				nm_log_warn (LOGD_SETTINGS, "%s: %s / %s ignoring invalid byte element '%d' (not "
-				             " between 0 and 255 inclusive)", __func__, setting_name,
-				             key, val);
-			} else
-				g_byte_array_append (array, (const unsigned char *) &v, sizeof (v));
-		}
-		g_free (tmp_list);
-	}
-
-	if (array->len == 0) {
-		g_byte_array_free (array, TRUE);
-		return NULL;
+				res = g_strdup_printf ("%s/%s: %s", group, setting_name, message);
+		} else
+			res = g_strdup_printf ("%s: %s", group, message);
+		*out_message = res;
+		return res;
 	} else
-		return g_byte_array_free_to_bytes (array);
-}
-
-static void
-ssid_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	GBytes *bytes;
-
-	bytes = get_bytes (keyfile, setting_name, key, FALSE, TRUE);
-	if (bytes) {
-		g_object_set (setting, key, bytes, NULL);
-		g_bytes_unref (bytes);
-	} else {
-		nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid SSID for %s / %s",
-		             __func__, setting_name, key);
-	}
-}
-
-static void
-password_raw_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	GBytes *bytes;
-
-	bytes = get_bytes (keyfile, setting_name, key, FALSE, TRUE);
-	if (bytes) {
-		g_object_set (setting, key, bytes, NULL);
-		g_bytes_unref (bytes);
-	} else {
-		nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid raw password for %s / %s",
-		             __func__, setting_name, key);
-	}
-}
-
-static char *
-get_cert_path (const char *keyfile_path, const guint8 *cert_path, gsize cert_path_len)
-{
-	const char *base;
-	char *p = NULL, *path, *dirname, *tmp;
-
-	g_return_val_if_fail (keyfile_path != 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;
-
-	dirname = g_path_get_dirname (keyfile_path);
-	tmp = g_build_path ("/", dirname, base, NULL);
-	g_free (dirname);
-	g_free (path);
-	return tmp;
-}
-
-#define SCHEME_PATH "file://"
-
-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;
+		return message;
 }
 
 static gboolean
-handle_as_scheme (GBytes *bytes, NMSetting *setting, const char *key)
-{
-	const guint8 *data;
-	gsize data_len;
-
-	data = g_bytes_get_data (bytes, &data_len);
+_handler_read (GKeyFile *keyfile,
+               NMConnection *connection,
+               NMKeyfileReadType type,
+               void *type_data,
+               void *user_data,
+               GError **error)
+{
+	if (type == NM_KEYFILE_READ_TYPE_WARN) {
+		NMKeyfileReadTypeDataWarn *warn_data = type_data;
+		NMLogLevel level;
+		char *message_free = NULL;
+
+		if (warn_data->severity > NM_KEYFILE_WARN_SEVERITY_WARN)
+			level = LOGL_ERR;
+		else if (warn_data->severity >= NM_KEYFILE_WARN_SEVERITY_WARN)
+			level = LOGL_WARN;
+		else if (warn_data->severity == NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE)
+			level = LOGL_WARN;
+		else
+			level = LOGL_INFO;
 
-	/* It's the PATH scheme, can just set plain data */
-	if (   (data_len > strlen (SCHEME_PATH))
-	    && g_str_has_prefix ((const char *) data, SCHEME_PATH)
-	    && (data[data_len - 1] == '\0')) {
-		g_object_set (setting, key, bytes, NULL);
+		nm_log (level, LOGD_SETTINGS, "keyfile: %s",
+		        _fmt_warn (warn_data->group, warn_data->setting,
+		                   warn_data->property_name, warn_data->message,
+		                   &message_free));
+		g_free (message_free);
 		return TRUE;
 	}
 	return FALSE;
 }
 
-static gboolean
-handle_as_path (GBytes *bytes,
-                NMSetting *setting,
-                const char *key,
-                const char *keyfile_path)
-{
-	const guint8 *data;
-	gsize data_len;
-	gsize validate_len;
-	char *path;
-	gboolean exists, success = FALSE;
-
-	data = g_bytes_get_data (bytes, &data_len);
-	if (data_len > 500 || data_len < 1)
-		return FALSE;
-
-	/* If there's a trailing NULL tell g_utf8_validate() to to until the NULL */
-	if (data[data_len - 1] == '\0')
-		validate_len = -1;
-	else
-		validate_len = data_len;
-
-	if (g_utf8_validate ((const char *) data, validate_len, NULL) == FALSE)
-		return FALSE;
-
-	/* 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 (keyfile_path, data, data_len);
-	exists = g_file_test (path, G_FILE_TEST_EXISTS);
-	if (   exists
-	    || memchr (data, '/', data_len)
-	    || has_cert_ext (path)) {
-		GByteArray *tmp;
-		GBytes *val;
-
-		/* Construct the proper value as required for the PATH scheme */
-		tmp = g_byte_array_sized_new (strlen (SCHEME_PATH) + strlen (path) + 1);
-		g_byte_array_append (tmp, (const guint8 *) SCHEME_PATH, strlen (SCHEME_PATH));
-		g_byte_array_append (tmp, (const guint8 *) path, strlen (path));
-		g_byte_array_append (tmp, (const guint8 *) "\0", 1);
-		val = g_byte_array_free_to_bytes (tmp);
-		g_object_set (setting, key, val, NULL);
-		g_bytes_unref (val);
-		success = TRUE;
-
-		/* Warn if the certificate didn't exist */
-		if (exists == FALSE)
-			nm_log_warn (LOGD_SETTINGS, "certificate or key %s does not exist", path);
-	}
-	g_free (path);
-
-	return success;
-}
-
-static void
-cert_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	GBytes *bytes;
-	gboolean success = FALSE;
-
-	bytes = get_bytes (keyfile, setting_name, key, TRUE, FALSE);
-	if (bytes) {
-		/* Try as a path + scheme (ie, starts with "file://") */
-		success = handle_as_scheme (bytes, setting, key);
-
-		/* If not, it might be a plain path */
-		if (success == FALSE)
-			success = handle_as_path (bytes, setting, key, keyfile_path);
-
-		/* If neither of those two, assume blob with certificate data */
-		if (success == FALSE)
-			g_object_set (setting, key, bytes, NULL);
-	} else {
-		nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid key/cert value for %s / %s",
-		             __func__, setting_name, key);
-	}
-
-	if (bytes)
-		g_bytes_unref (bytes);
-}
-
-static void
-parity_parser (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	NMSettingSerialParity parity;
-	int int_val;
-	char *str_val;
-
-	/* 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 (keyfile, setting_name, key, NULL);
-	if (!int_val) {
-		str_val = nm_keyfile_plugin_kf_get_string (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';
-			}
-		}
-		g_free (str_val);
-	}
-
-	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:
-		nm_log_warn (LOGD_SETTINGS, "%s: ignoring invalid value for %s / %s",
-		             __func__, setting_name, key);
-		return;
-	}
-
-	g_object_set (setting, key, parity, NULL);
-}
-
-typedef struct {
-	const char *setting_name;
-	const char *key;
-	gboolean check_for_key;
-	void (*parser) (NMSetting *setting, const char *key, GKeyFile *keyfile, const char *keyfile_path);
-} 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_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 },
-	{ 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 },
-	{ 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 },
-	{ NULL, NULL, FALSE }
-};
-
-typedef struct {
-	GKeyFile *keyfile;
-	const char *keyfile_path;
-} ReadInfo;
-
-static void
-read_one_setting_value (NMSetting *setting,
-                        const char *key,
-                        const GValue *value,
-                        GParamFlags flags,
-                        gpointer user_data)
-{
-	ReadInfo *info = user_data;
-	const char *setting_name;
-	int errsv;
-	GType type;
-	GError *err = NULL;
-	gboolean check_for_key = TRUE;
-	KeyParser *parser = &key_parsers[0];
-
-	/* 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;
-
-	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++;
-	}
-
-	/* VPN properties don't have the exact key name */
-	if (NM_IS_SETTING_VPN (setting))
-		check_for_key = FALSE;
-
-	/* Bonding 'options' don't have the exact key name. The options are right under [bond] group. */
-	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 (info->keyfile, setting_name, key, &err)) {
-		/* Key doesn't exist or an error ocurred, thus nothing to do. */
-		if (err) {
-			nm_log_warn (LOGD_SETTINGS, "Error loading setting '%s' value: %s", setting_name, err->message);
-			g_error_free (err);
-		}
-		return;
-	}
-
-	/* If there's a custom parser for this key, handle that before the generic
-	 * parsers below.
-	 */
-	if (parser->setting_name) {
-		(*parser->parser) (setting, key, info->keyfile, info->keyfile_path);
-		return;
-	}
-
-	type = G_VALUE_TYPE (value);
-
-	if (type == G_TYPE_STRING) {
-		char *str_val;
-
-		str_val = nm_keyfile_plugin_kf_get_string (info->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 (info->keyfile, setting_name, key, NULL);
-		if (int_val < 0)
-			nm_log_warn (LOGD_SETTINGS, "Casting negative value (%i) to uint", int_val);
-		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 (info->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 (info->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 (info->keyfile, setting_name, key, NULL);
-		if (int_val < G_MININT8 || int_val > G_MAXINT8)
-			nm_log_warn (LOGD_SETTINGS, "Casting value (%i) to char", int_val);
-
-		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 (info->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) {
-		char *tmp_str;
-		gint64 int_val;
-
-		tmp_str = nm_keyfile_plugin_kf_get_value (info->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)
-			nm_log_warn (LOGD_SETTINGS, "Invalid int64 value (%s)", tmp_str);
-		else
-			g_object_set (setting, key, int_val, NULL);
-		g_free (tmp_str);
- 	} else if (type == G_TYPE_BYTES) {
-		gint *tmp;
-		GByteArray *array;
-		GBytes *bytes;
-		gsize length;
-		int i;
-
-		tmp = nm_keyfile_plugin_kf_get_integer_list (info->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) {
-				nm_log_warn (LOGD_SETTINGS, "%s: %s / %s ignoring invalid byte element '%d' (not "
-				             " between 0 and 255 inclusive)", __func__, setting_name,
-				             key, val);
-			} 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 (info->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 (info->keyfile, setting, key);
-	} else if (type == G_TYPE_ARRAY) {
-		if (!read_array_of_uint (info->keyfile, setting, key)) {
-			nm_log_warn (LOGD_SETTINGS, "Unhandled setting property type (read): '%s/%s' : '%s'",
-			             setting_name, key, G_VALUE_TYPE_NAME (value));
-		}
-	} 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 (info->keyfile, setting_name, key, &err);
-		if (!err) {
-			if (uint_val <= G_MAXUINT)
-				g_object_set (setting, key, (guint) uint_val, NULL);
-			else {
-				nm_log_warn (LOGD_SETTINGS, "Too large FLAGS property (read): '%s/%s' : '%s'",
-				             setting_name, key, G_VALUE_TYPE_NAME (value));
-			}
-		}
-		g_clear_error (&err);
-	} else if (G_VALUE_HOLDS_ENUM (value)) {
-		gint int_val;
-
-		int_val = nm_keyfile_plugin_kf_get_integer (info->keyfile, setting_name, key, &err);
-		if (!err)
-			g_object_set (setting, key, (gint) int_val, NULL);
-		g_clear_error (&err);
-	} else {
-		nm_log_warn (LOGD_SETTINGS, "Unhandled setting property type (read): '%s/%s' : '%s'",
-		             setting_name, key, G_VALUE_TYPE_NAME (value));
-	}
-}
-
-static NMSetting *
-read_setting (GKeyFile *file, const char *keyfile_path, const char *group)
-{
-	NMSetting *setting = NULL;
-	ReadInfo info = { file, keyfile_path };
-	const char *alias;
-	GType type;
-
-	alias = nm_keyfile_plugin_get_setting_name_for_alias (group);
-	if (alias)
-		group = alias;
-
-	type = nm_setting_lookup_type (group);
-	if (type) {
-		setting = g_object_new (type, NULL);
-		nm_setting_enumerate_values (setting, read_one_setting_value, &info);
-	} else
-		nm_log_warn (LOGD_SETTINGS, "Invalid setting name '%s'", group);
-
-	return setting;
-}
-
-static void
-read_vpn_secrets (GKeyFile *file, NMSettingVpn *s_vpn)
-{
-	char **keys, **iter;
-
-	keys = nm_keyfile_plugin_kf_get_keys (file, VPN_SECRETS_GROUP, NULL, NULL);
-	for (iter = keys; *iter; iter++) {
-		char *secret;
-
-		secret = nm_keyfile_plugin_kf_get_string (file, VPN_SECRETS_GROUP, *iter, NULL);
-		if (secret) {
-			nm_setting_vpn_add_secret (s_vpn, *iter, secret);
-			g_free (secret);
-		}
-	}
-	g_strfreev (keys);
-}
-
 NMConnection *
 nm_keyfile_plugin_connection_from_file (const char *filename, GError **error)
 {
 	GKeyFile *key_file;
 	struct stat statbuf;
-	gboolean bad_permissions;
 	NMConnection *connection = NULL;
-	NMSettingConnection *s_con;
-	NMSetting *setting;
-	gchar **groups;
-	gsize length;
-	int i;
-	gboolean vpn_secrets = FALSE;
 	GError *verify_error = NULL;
 
 	if (stat (filename, &statbuf) != 0 || !S_ISREG (statbuf.st_mode)) {
@@ -1270,90 +100,34 @@ nm_keyfile_plugin_connection_from_file (const char *filename, GError **error)
 		return NULL;
 	}
 
-	bad_permissions = statbuf.st_mode & 0077;
-
-	if (bad_permissions) {
+	if (statbuf.st_mode & 0077) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "File permissions (%o) were insecure",
 		             statbuf.st_mode);
 		return NULL;
 	}
 
-	key_file = g_key_file_new ();
-	if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, error))
-		goto out;
-
-	connection = nm_simple_connection_new ();
-
-	groups = g_key_file_get_groups (key_file, &length);
-	for (i = 0; i < length; i++) {
-		/* Only read out secrets when needed */
-		if (!strcmp (groups[i], VPN_SECRETS_GROUP)) {
-			vpn_secrets = TRUE;
-			continue;
-		}
-
-		setting = read_setting (key_file, filename, groups[i]);
-		if (setting)
-			nm_connection_add_setting (connection, setting);
-	}
-
-	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 (!nm_setting_connection_get_id (s_con)) {
-		char *base_name;
-
-		base_name = g_path_get_basename (filename);
-		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 (!nm_setting_connection_get_uuid (s_con)) {
-		char *hashed_uuid;
-
-		hashed_uuid = nm_utils_uuid_generate_from_strings ("keyfile", filename, 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 (key_file,
-		                                        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);
+	if (!NM_FLAGS_HAS (nm_utils_get_testing (), NM_UTILS_TEST_NO_KEYFILE_OWNER_CHECK)) {
+		if (statbuf.st_uid != 0) {
+			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+			             "File owner (%o) is insecure",
+			             statbuf.st_mode);
+			return NULL;
 		}
 	}
 
-	/* 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 (key_file, s_vpn);
-	}
+	key_file = g_key_file_new ();
+	if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, error))
+		goto out;
 
-	g_strfreev (groups);
+	connection = nm_keyfile_read (key_file, filename, NULL, _handler_read, NULL, error);
+	if (!connection)
+		goto out;
 
 	/* Normalize and verify the connection */
 	if (!nm_connection_normalize (connection, NULL, NULL, &verify_error)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			         "invalid connection: %s",
+		             "invalid connection: %s",
 		             verify_error->message);
 		g_clear_error (&verify_error);
 		g_object_unref (connection);
@@ -1364,3 +138,4 @@ out:
 	g_key_file_free (key_file);
 	return connection;
 }
+