about summary refs log tree commit diff
path: root/libnm-core/nm-keyfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'libnm-core/nm-keyfile.c')
-rw-r--r--libnm-core/nm-keyfile.c1119
1 files changed, 809 insertions, 310 deletions
diff --git a/libnm-core/nm-keyfile.c b/libnm-core/nm-keyfile.c
index 5aa8a4ac..05c6bf97 100644
--- a/libnm-core/nm-keyfile.c
+++ b/libnm-core/nm-keyfile.c
@@ -23,17 +23,16 @@
 
 #include "nm-keyfile-internal.h"
 
-#include <errno.h>
 #include <stdlib.h>
 #include <stdio.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-utils/nm-secret-utils.h"
+#include "systemd/nm-sd-utils-shared.h"
 #include "nm-common-macros.h"
 #include "nm-core-internal.h"
 #include "nm-keyfile-utils.h"
@@ -96,6 +95,15 @@ _handle_warn (KeyfileReaderInfo *info,
 		_info->error == NULL; \
 	})
 
+/*****************************************************************************/
+
+static gboolean
+_secret_flags_persist_secret (NMSettingSecretFlags flags)
+{
+	return flags == NM_SETTING_SECRET_FLAG_NONE;
+}
+
+/*****************************************************************************/
 /* 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
@@ -106,17 +114,18 @@ 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;
+	gs_free char *s = NULL;
 
 	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 ?: s,
-		              NULL);
-		g_free (s);
-	}
+	if (!s)
+		return;
+
+	key_setting_name = nm_keyfile_plugin_get_setting_name_for_alias (s);
+	g_object_set (G_OBJECT (setting),
+	              key,
+	              key_setting_name ?: s,
+	              NULL);
 }
 
 static void
@@ -129,7 +138,7 @@ sriov_vfs_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
 	int i;
 
 	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL);
-	if (!keys || n_keys == 0)
+	if (n_keys == 0)
 		return;
 
 	vfs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_sriov_vf_unref);
@@ -372,7 +381,7 @@ read_field (char **current, const char **out_err_str, const char *characters, co
  * 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
+ * semicolon (;) and comma (,) are interchangeable. The choice of
  * separator in the above examples is therefore not significant.
  *
  * Leaving out the prefix length is discouraged and DEPRECATED. The
@@ -622,17 +631,16 @@ ip_address_or_route_parser (KeyfileReaderInfo *info, NMSetting *setting, const c
 	gs_free char *gateway = NULL;
 	gs_unref_ptrarray GPtrArray *list = NULL;
 	gs_strfreev char **keys = NULL;
-	gsize i_keys, keys_len;
+	gsize i_keys, n_keys;
 	gs_free IPAddrRouteBuildListData *build_list = NULL;
 	gsize i_build_list, build_list_len = 0;
 
-	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &keys_len, NULL);
-
-	if (keys_len == 0)
+	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL);
+	if (n_keys == 0)
 		return;
 
 	/* first create a list of all relevant keys, and sort them. */
-	for (i_keys = 0; i_keys < keys_len; i_keys++) {
+	for (i_keys = 0; i_keys < n_keys; i_keys++) {
 		const char *s_key = keys[i_keys];
 		gint32 key_idx;
 		gint8 key_type;
@@ -641,7 +649,7 @@ ip_address_or_route_parser (KeyfileReaderInfo *info, NMSetting *setting, const c
 			continue;
 
 		if (G_UNLIKELY (!build_list))
-			build_list = g_new (IPAddrRouteBuildListData, keys_len - i_keys);
+			build_list = g_new (IPAddrRouteBuildListData, n_keys - i_keys);
 
 		build_list[build_list_len].s_key = s_key;
 		build_list[build_list_len].key_idx = key_idx;
@@ -786,7 +794,6 @@ mac_address_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key
 	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);
 
@@ -819,6 +826,7 @@ mac_address_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key
 
 	if (!buf_arr) {
 		gs_free int *tmp_list = NULL;
+		gsize length;
 
 		/* Old format; list of ints */
 		tmp_list = nm_keyfile_plugin_kf_get_integer_list (info->keyfile, setting_name, key, &length, NULL);
@@ -879,9 +887,10 @@ read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key)
 	const char *const*iter;
 	const char *setting_name = nm_setting_get_name (setting);
 	gboolean is_vpn;
+	gsize n_keys;
 
-	keys = nm_keyfile_plugin_kf_get_keys (file, setting_name, NULL, NULL);
-	if (!keys || !*keys)
+	keys = nm_keyfile_plugin_kf_get_keys (file, setting_name, &n_keys, NULL);
+	if (n_keys == 0)
 		return;
 
 	if (   (is_vpn = NM_IS_SETTING_VPN (setting))
@@ -902,7 +911,7 @@ read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key)
 				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"))
+				if (!nm_streq (name, "interface-name"))
 					nm_setting_bond_add_option (NM_SETTING_BOND (setting), name, value);
 			}
 		}
@@ -1374,55 +1383,119 @@ cert_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
 	g_object_set (setting, key, bytes, NULL);
 }
 
+static int
+_parity_from_char (int ch)
+{
+#if NM_MORE_ASSERTS > 5
+	{
+		static char check = 0;
+
+		if (check == 0) {
+			nm_auto_unref_gtypeclass GEnumClass *klass = g_type_class_ref (NM_TYPE_SETTING_SERIAL_PARITY);
+			guint i;
+
+			check = 1;
+
+			/* In older versions, parity was G_TYPE_CHAR/gint8, and the character
+			 * value was stored as integer.
+			 * For example parity=69 equals parity=E, meaning NM_SETTING_SERIAL_PARITY_EVEN.
+			 *
+			 * That means, certain values are reserved. Assert that these numbers
+			 * are not reused when we extend NMSettingSerialParity enum.
+			 * Actually, since NM_SETTING_SERIAL_PARITY is g_param_spec_enum(),
+			 * we anyway cannot extend the enum without breaking API...
+			 *
+			 * [1] commit "a91e60902e libnm-core: make NMSettingSerial:parity an enum"
+			 * [2] https://cgit.freedesktop.org/NetworkManager/NetworkManager/commit/?id=a91e60902eabae1de93d61323dae6ac894b5d40f
+			 */
+			g_assert (G_IS_ENUM_CLASS (klass));
+			for (i = 0; i < klass->n_values; i++) {
+				const GEnumValue *v = &klass->values[i];
+				int num = v->value;
+
+				g_assert (_parity_from_char (num) == -1);
+				g_assert (!NM_IN_SET (num, 'e', 'E', 'o', 'O', 'n', 'N'));
+			}
+		}
+	}
+#endif
+
+	switch (ch) {
+	case 'E':
+	case 'e':
+		return NM_SETTING_SERIAL_PARITY_EVEN;
+	case 'O':
+	case 'o':
+		return NM_SETTING_SERIAL_PARITY_ODD;
+	case 'N':
+	case 'n':
+		return NM_SETTING_SERIAL_PARITY_NONE;
+	}
+
+	return -1;
+}
+
 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;
+	gs_free_error GError *err = NULL;
+	int parity;
+	gs_free char *tmp_str = NULL;
+	gint64 i64;
 
 	/* 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';
-			}
+	tmp_str = nm_keyfile_plugin_kf_get_value (info->keyfile, setting_name, key, &err);
+	if (err)
+		goto out_err;
+
+	if (   tmp_str
+	    && tmp_str[0] != '\0'
+	    && tmp_str[1] == '\0') {
+		/* the ASCII characters like 'E' are taken directly... */
+		parity = _parity_from_char (tmp_str[0]);
+		if (parity >= 0)
+			goto parity_good;
+	}
+
+	i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MININT64);
+	if (   i64 != G_MININT64
+	    && errno == 0) {
+
+		if ((parity = _parity_from_char (i64)) >= 0) {
+			/* another oddity: the string is a valid number. However, if the numeric values
+			 * is one of the supported ASCII codes, accept it (like 69 for 'E').
+			 */
+			goto parity_good;
 		}
+
+		/* Finally, take the numeric value as is. */
+		parity = i64;
+		goto parity_good;
 	}
 
-	if (!int_val)
-		return;
+	handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+	             _("invalid parity value '%s'"),
+	             tmp_str ?: "");
+	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 ?: "");
+parity_good:
+	nm_g_object_set_property_enum (G_OBJECT (setting), key, NM_TYPE_SETTING_SERIAL_PARITY, parity, &err);
+
+out_err:
+	if (!err)
+		return;
+	if (   err->domain == G_KEY_FILE_ERROR
+	    && NM_IN_SET (err->code, G_KEY_FILE_ERROR_GROUP_NOT_FOUND,
+	                             G_KEY_FILE_ERROR_KEY_NOT_FOUND)) {
+		/* ignore such errors. The key is not present. */
 		return;
 	}
-
-	g_object_set (setting, key, parity, NULL);
+	handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+	             _("invalid setting: %s"), err->message);
 }
 
 static void
@@ -1433,7 +1506,9 @@ team_config_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key
 	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)) {
+	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);
@@ -1455,7 +1530,7 @@ qdisc_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
 	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)
+	if (n_keys == 0)
 		return;
 
 	for (i = 0; i < n_keys; i++) {
@@ -1503,7 +1578,7 @@ tfilter_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
 	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)
+	if (n_keys == 0)
 		return;
 
 	for (i = 0; i < n_keys; i++) {
@@ -1663,16 +1738,21 @@ write_ip_values (GKeyFile *file,
                  const char *gateway,
                  gboolean is_route)
 {
-	GString *output;
-	int family, i;
-	const char *addr, *gw;
+	nm_auto_free_gstring GString *output = NULL;
+	int addr_family;
+	guint i;
+	const char *addr;
+	const char *gw;
 	guint32 plen;
-	char key_name[64], *key_name_idx;
+	char key_name[64];
+	char *key_name_idx;
 
 	if (!array->len)
 		return;
 
-	family = !strcmp (setting_name, NM_SETTING_IP4_CONFIG_SETTING_NAME) ? AF_INET : AF_INET6;
+	addr_family =   nm_streq (setting_name, NM_SETTING_IP4_CONFIG_SETTING_NAME)
+	              ? AF_INET
+	              : AF_INET6;
 
 	strcpy (key_name, is_route ? "route" : "address");
 	key_name_idx = key_name + strlen (key_name);
@@ -1693,7 +1773,9 @@ write_ip_values (GKeyFile *file,
 
 			addr = nm_ip_address_get_address (address);
 			plen = nm_ip_address_get_prefix (address);
-			gw = i == 0 ? gateway : NULL;
+			gw =   (i == 0)
+			     ? gateway
+			     : NULL;
 		}
 
 		g_string_set_size (output, 0);
@@ -1706,18 +1788,19 @@ write_ip_values (GKeyFile *file,
 			 * The current version supports reading of the above form.
 			 */
 			if (!gw) {
-				if (family == AF_INET)
+				if (addr_family == AF_INET)
 					gw = "0.0.0.0";
 				else
 					gw = "::";
 			}
 
 			g_string_append_printf (output, ",%s", gw);
-			if (is_route && metric != -1)
+			if (   is_route
+			    && metric != -1)
 				g_string_append_printf (output, ",%lu", (unsigned long) metric);
 		}
 
-		sprintf (key_name_idx, "%d", i + 1);
+		sprintf (key_name_idx, "%u", i + 1);
 		nm_keyfile_plugin_kf_set_string (file, setting_name, key_name, output->str);
 
 		if (is_route) {
@@ -1732,7 +1815,6 @@ write_ip_values (GKeyFile *file,
 			}
 		}
 	}
-	g_string_free (output, TRUE);
 }
 
 static void
@@ -1843,7 +1925,8 @@ write_hash_of_string (GKeyFile *file,
 	guint i, l;
 
 	/* Write VPN secrets out to a different group to keep them separate */
-	if (NM_IS_SETTING_VPN (setting) && !strcmp (key, NM_SETTING_VPN_SECRETS)) {
+	if (   NM_IS_SETTING_VPN (setting)
+	    && nm_streq (key, NM_SETTING_VPN_SECRETS)) {
 		group_name = NM_KEYFILE_GROUP_VPN_SECRETS;
 		vpn_secrets = TRUE;
 	}
@@ -1852,8 +1935,8 @@ write_hash_of_string (GKeyFile *file,
 
 	keys = nm_utils_strdict_get_keys (hash, TRUE, &l);
 	for (i = 0; i < l; i++) {
+		gs_free char *to_free = NULL;
 		const char *property, *data;
-		gboolean write_item = TRUE;
 
 		property = keys[i];
 
@@ -1864,19 +1947,16 @@ write_hash_of_string (GKeyFile *file,
 		if (vpn_secrets) {
 			NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
 
-			nm_setting_get_secret_flags (setting, property, &secret_flags, NULL);
-			if (secret_flags != NM_SETTING_SECRET_FLAG_NONE)
-				write_item = FALSE;
+			if (!nm_setting_get_secret_flags (setting, property, &secret_flags, NULL))
+				nm_assert_not_reached ();
+			if (!_secret_flags_persist_secret (secret_flags))
+				continue;
 		}
 
-		if (write_item) {
-			gs_free char *to_free = NULL;
-
-			data = g_hash_table_lookup (hash, property);
-			nm_keyfile_plugin_kf_set_string (file, group_name,
-			                                 nm_keyfile_key_encode (property, &to_free),
-			                                 data);
-		}
+		data = g_hash_table_lookup (hash, property);
+		nm_keyfile_plugin_kf_set_string (file, group_name,
+		                                 nm_keyfile_key_encode (property, &to_free),
+		                                 data);
 	}
 }
 
@@ -1976,9 +2056,10 @@ cert_writer_default (NMConnection *connection,
 
 	scheme = cert_data->vtable->scheme_func (cert_data->setting);
 	if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
-		const char *path;
-		char *path_free = NULL, *tmp;
+		gs_free char *path_free = NULL;
 		gs_free char *base_dir = NULL;
+		gs_free char *tmp = NULL;
+		const char *path;
 
 		path = cert_data->vtable->path_func (cert_data->setting);
 		g_assert (path);
@@ -1988,7 +2069,8 @@ cert_writer_default (NMConnection *connection,
 		 * context. */
 		if (path[0] && path[0] != '/') {
 			base_dir = g_get_current_dir ();
-			path = path_free = g_strconcat (base_dir, "/", path, NULL);
+			path_free = g_strconcat (base_dir, "/", path, NULL);
+			path = path_free;
 		} else
 			base_dir = g_path_get_dirname (path);
 
@@ -1998,20 +2080,21 @@ cert_writer_default (NMConnection *connection,
 		tmp = nm_keyfile_detect_unqualified_path_scheme (base_dir, path, -1, FALSE, NULL);
 		if (tmp)
 			g_clear_pointer (&tmp, g_free);
-		else
-			path = tmp = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL);
+		else {
+			tmp = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL);
+			path = tmp;
+		}
 
 		/* Path contains at least a '/', hence it cannot be recognized as the old
 		 * binary format consisting of a list of integers. */
 
 		nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key, path);
-		g_free (tmp);
-		g_free (path_free);
 	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
 		GBytes *blob;
 		const guint8 *blob_data;
 		gsize blob_len;
-		char *blob_base64, *val;
+		gs_free char *blob_base64 = NULL;
+		gs_free char *val = NULL;
 
 		blob = cert_data->vtable->blob_func (cert_data->setting);
 		g_assert (blob);
@@ -2021,8 +2104,6 @@ cert_writer_default (NMConnection *connection,
 		val = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB, blob_base64, NULL);
 
 		nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key, val);
-		g_free (val);
-		g_free (blob_base64);
 	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11) {
 		nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key,
 		                                 cert_data->vtable->uri_func (cert_data->setting));
@@ -2048,7 +2129,7 @@ cert_writer (KeyfileWriterInfo *info,
 	NMKeyfileWriteTypeDataCert type_data = { 0 };
 
 	for (i = 0; nm_setting_8021x_scheme_vtable[i].setting_key; i++) {
-		if (g_strcmp0 (nm_setting_8021x_scheme_vtable[i].setting_key, key) == 0) {
+		if (nm_streq0 (nm_setting_8021x_scheme_vtable[i].setting_key, key)) {
 			objtype = &nm_setting_8021x_scheme_vtable[i];
 			break;
 		}
@@ -2506,26 +2587,28 @@ _parse_info_find (NMSetting *setting,
 /*****************************************************************************/
 
 static void
-read_one_setting_value (NMSetting *setting,
-                        const char *key,
-                        const GValue *value,
-                        GParamFlags flags,
-                        gpointer user_data)
+read_one_setting_value (KeyfileReaderInfo *info,
+                        NMSetting *setting,
+                        const NMSettInfoProperty *property_info)
 {
-	KeyfileReaderInfo *info = user_data;
 	GKeyFile *keyfile = info->keyfile;
-	const char *setting_name;
-	int errsv;
-	GType type;
 	gs_free_error GError *err = NULL;
 	const ParseInfoProperty *pip;
+	gs_free char *tmp_str = NULL;
+	const char *setting_name;
+	const char *key;
+	GType type;
+	guint64 u64;
+	gint64 i64;
 
-	if (info->error)
-		return;
+	nm_assert (!info->error);
+	nm_assert (property_info->param_spec);
 
-	if (!(flags & G_PARAM_WRITABLE))
+	if ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) != G_PARAM_WRITABLE)
 		return;
 
+	key = property_info->param_spec->name;
+
 	pip = _parse_info_find (setting, key, &setting_name);
 
 	nm_assert (setting_name);
@@ -2544,7 +2627,7 @@ read_one_setting_value (NMSetting *setting,
 	 */
 	if (   (!pip || !pip->parser_no_check_key)
 	    && !nm_keyfile_plugin_kf_has_key (keyfile, setting_name, key, &err)) {
-		/* Key doesn't exist or an error ocurred, thus nothing to do. */
+		/* Key doesn't exist or an error occurred, thus nothing to do. */
 		if (err) {
 			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
 			                  _("error loading setting value: %s"),
@@ -2559,69 +2642,78 @@ read_one_setting_value (NMSetting *setting,
 		return;
 	}
 
-	type = G_VALUE_TYPE (value);
+	type = G_PARAM_SPEC_VALUE_TYPE (property_info->param_spec);
 
 	if (type == G_TYPE_STRING) {
 		gs_free char *str_val = NULL;
 
-		str_val = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, NULL);
-		g_object_set (setting, key, str_val, NULL);
+		str_val = nm_keyfile_plugin_kf_get_string (keyfile, setting_name, key, &err);
+		if (!err)
+			nm_g_object_set_property_string_take (G_OBJECT (setting), key, g_steal_pointer (&str_val), &err);
 	} 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))
-				return;
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, &err);
+		if (!err) {
+			u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64);
+			if (   u64 == G_MAXUINT64
+			    && errno != 0) {
+				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
+				                     _("value cannot be interpreted as integer"));
+			} else
+				nm_g_object_set_property_uint (G_OBJECT (setting), key, u64, &err);
 		}
-		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);
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, &err);
+		if (!err) {
+			i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MININT64);
+			if (   i64 == G_MININT64
+			    && errno != 0) {
+				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
+				                     _("value cannot be interpreted as integer"));
+			} else
+				nm_g_object_set_property_int (G_OBJECT (setting), key, i64, &err);
+		}
 	} 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);
+		bool_val = nm_keyfile_plugin_kf_get_boolean (keyfile, setting_name, key, &err);
+		if (!err)
+			nm_g_object_set_property_boolean (G_OBJECT (setting), key, bool_val, &err);
 	} 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))
-				return;
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, &err);
+		if (!err) {
+			/* As documented by glib, G_TYPE_CHAR is really a (signed!) gint8. */
+			i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT8, G_MAXINT8, G_MININT64);
+			if (   i64 == G_MININT64
+			    && errno != 0) {
+				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
+				                     _("value cannot be interpreted as integer"));
+			} else
+				nm_g_object_set_property_char (G_OBJECT (setting), key, i64, &err);
 		}
-
-		g_object_set (setting, key, int_val, NULL);
 	} else if (type == G_TYPE_UINT64) {
-		gs_free char *tmp_str = NULL;
-		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_object_set (setting, key, uint_val, NULL);
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, &err);
+		if (!err) {
+			u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT64, G_MAXUINT64);
+			if (   u64 == G_MAXUINT64
+			    && errno != 0) {
+				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
+				                     _("value cannot be interpreted as integer"));
+			} else
+				nm_g_object_set_property_uint64 (G_OBJECT (setting), key, u64, &err);
+		}
 	} 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))
-				return;
-		} else
-			g_object_set (setting, key, int_val, NULL);
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, &err);
+		if (!err) {
+			i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT64, G_MAXINT64, G_MAXINT64);
+			if (   i64 == G_MAXINT64
+			    && errno != 0) {
+				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
+				                     _("value cannot be interpreted as integer"));
+			} else
+				nm_g_object_set_property_int64 (G_OBJECT (setting), key, i64, &err);
+		}
 	} else if (type == G_TYPE_BYTES) {
-		int *tmp;
+		gs_free int *tmp = NULL;
 		GByteArray *array;
 		GBytes *bytes;
 		gsize length;
@@ -2632,7 +2724,7 @@ read_one_setting_value (NMSetting *setting,
 
 		array = g_byte_array_sized_new (length);
 		for (i = 0; i < length; i++) {
-			int val = tmp[i];
+			const int val = tmp[i];
 			unsigned char v = (unsigned char) (val & 0xFF);
 
 			if (val < 0 || val > 255) {
@@ -2641,7 +2733,6 @@ read_one_setting_value (NMSetting *setting,
 				                     _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"),
 				                     val)) {
 					g_byte_array_unref (array);
-					g_free (tmp);
 					return;
 				}
 				already_warned = TRUE;
@@ -2652,54 +2743,61 @@ read_one_setting_value (NMSetting *setting,
 		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) {
-		char **sa;
+		gs_strfreev char **sa = NULL;
 		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);
+	} else if (G_TYPE_IS_FLAGS (type)) {
+		tmp_str = nm_keyfile_plugin_kf_get_value (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))
-					return;
-			}
+			u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64);
+			if (   u64 == G_MAXUINT64
+			    && errno != 0) {
+				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
+				                     _("value cannot be interpreted as integer"));
+			} else
+				nm_g_object_set_property_flags (G_OBJECT (setting), key, type, u64, &err);
+		}
+	} else if (G_TYPE_IS_ENUM (type)) {
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_name, key, &err);
+		if (!err) {
+			i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MAXINT64);
+			if (   i64 == G_MAXINT64
+			    && errno != 0) {
+				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
+				                     _("value cannot be interpreted as integer"));
+			} else
+				nm_g_object_set_property_enum (G_OBJECT (setting), key, type, i64, &err);
 		}
-	} else if (G_VALUE_HOLDS_ENUM (value)) {
-		int int_val;
+	} else
+		g_return_if_reached ();
 
-		int_val = nm_keyfile_plugin_kf_get_integer (keyfile, setting_name, key, &err);
-		if (!err)
-			g_object_set (setting, key, (int) int_val, NULL);
-	} else {
-		if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-		                 _("unhandled setting property type '%s'"),
-		                 G_VALUE_TYPE_NAME (value)))
-			return;
+	if (err) {
+		if (   err->domain == G_KEY_FILE_ERROR
+		    && NM_IN_SET (err->code, G_KEY_FILE_ERROR_GROUP_NOT_FOUND,
+		                             G_KEY_FILE_ERROR_KEY_NOT_FOUND)) {
+			/* ignore such errors. The key is not present. */
+		} else {
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid setting: %s"), err->message);
+		}
 	}
 }
 
-static NMSetting *
-read_setting (KeyfileReaderInfo *info)
+static void
+_read_setting (KeyfileReaderInfo *info)
 {
 	const NMSettInfoSetting *sett_info;
 	gs_unref_object NMSetting *setting = NULL;
 	const char *alias;
 	GType type;
+	guint i;
 
 	alias = nm_keyfile_plugin_get_setting_name_for_alias (info->group);
 	if (!alias)
@@ -2709,34 +2807,36 @@ read_setting (KeyfileReaderInfo *info)
 	if (!type) {
 		handle_warn (info, NULL, NM_KEYFILE_WARN_SEVERITY_WARN,
 		             _("invalid setting name '%s'"), info->group);
-		return NULL;
+		return;
 	}
 
 	setting = g_object_new (type, NULL);
 
 	info->setting = setting;
 
-	sett_info = _nm_sett_info_setting_get (NM_SETTING_GET_CLASS (setting));
+	sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
 
 	if (sett_info->detail.gendata_info) {
 		gs_free char **keys = NULL;
-		gsize i, n_keys;
+		gsize k, n_keys;
 
 		keys = g_key_file_get_keys (info->keyfile, info->group, &n_keys, NULL);
+		if (!keys)
+			n_keys = 0;
 		if (n_keys > 0) {
 			GHashTable *h = _nm_setting_gendata_hash (setting, TRUE);
 
 			nm_utils_strv_sort (keys, n_keys);
-			for (i = 0; i < n_keys; i++) {
-				gs_free char *key = keys[i];
+			for (k = 0; k < n_keys; k++) {
+				gs_free char *key = keys[k];
 				gs_free_error GError *local = NULL;
 				const GVariantType *variant_type;
 				GVariant *variant;
 
-				/* a GKeyfile can return duplicate keys, there is just no API to make sense
+				/* a GKeyFile can return duplicate keys, there is just no API to make sense
 				 * of them. Skip them. */
-				if (   i + 1 < n_keys
-				    && nm_streq (key, keys[i + 1]))
+				if (   k + 1 < n_keys
+				    && nm_streq (key, keys[k + 1]))
 					continue;
 
 				/* currently, the API is very simple. The setting class just returns
@@ -2781,34 +2881,175 @@ read_setting (KeyfileReaderInfo *info)
 				                     g_steal_pointer (&key),
 				                     g_variant_take_ref (variant));
 			}
-			for (; i < n_keys; i++)
-				g_free (keys[i]);
+			for (; k < n_keys; k++)
+				g_free (keys[k]);
 		}
-	} else
-		nm_setting_enumerate_values (setting, read_one_setting_value, info);
+	}
 
+	for (i = 0; i < sett_info->property_infos_len; i++) {
+		const NMSettInfoProperty *property_info = &sett_info->property_infos[i];
+
+		if (property_info->param_spec) {
+			read_one_setting_value (info, setting, property_info);
+			if (info->error)
+				goto out;
+		}
+	}
+
+out:
 	info->setting = NULL;
+	if (!info->error)
+		nm_connection_add_setting (info->connection, g_steal_pointer (&setting));
+}
+
+static void
+_read_setting_wireguard_peer (KeyfileReaderInfo *info)
+{
+	gs_unref_object NMSettingWireGuard *s_wg_new = NULL;
+	nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
+	gs_free_error GError *error = NULL;
+	NMSettingWireGuard *s_wg;
+	gs_free char *str = NULL;
+	const char *cstr = NULL;
+	const char *key;
+	gint64 i64;
+	gs_strfreev char **sa = NULL;
+	gsize n_sa;
+
+	peer = nm_wireguard_peer_new ();
+
+	nm_assert (g_str_has_prefix (info->group, NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER));
+	cstr = &info->group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)];
+	if (   !nm_utils_base64secret_normalize (cstr, NM_WIREGUARD_PUBLIC_KEY_LEN, &str)
+	    || !nm_streq0 (str, cstr)) {
+		/* the group name must be identical to the normalized(!) key, so that it
+		 * is uniquely identified. */
+		handle_warn (info, NULL, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("invalid peer public key in section '%s'"),
+		             info->group);
+		return;
+	}
+	nm_wireguard_peer_set_public_key (peer, cstr, TRUE);
+	nm_clear_g_free (&str);
+
+	key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY;
+	str = nm_keyfile_plugin_kf_get_string (info->keyfile, info->group, key, NULL);
+	if (str) {
+		if (!nm_wireguard_peer_set_preshared_key (peer, str, FALSE)) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("key '%s.%s' is not not a valid 256 bit key in base64 encoding"),
+			                  info->group, key))
+				return;
+		}
+		nm_clear_g_free (&str);
+	}
+
+	key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS;
+	i64 = nm_keyfile_plugin_kf_get_int64 (info->keyfile, info->group, key, 0, 0, NM_SETTING_SECRET_FLAG_ALL, -1, NULL);
+	if (errno != ENODATA) {
+		if (   i64 == -1
+		    || !_nm_setting_secret_flags_valid (i64)) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("key '%s.%s' is not not a valid secret flag"),
+			                  info->group, key))
+				return;
+		} else
+			nm_wireguard_peer_set_preshared_key_flags (peer, i64);
+	}
+
+	key = NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE;
+	i64 = nm_keyfile_plugin_kf_get_int64 (info->keyfile, info->group, key, 0, 0, G_MAXUINT32, -1, NULL);
+	if (errno != ENODATA) {
+		if (i64 == -1) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("key '%s.%s' is not not a integer in range 0 to 2^32"),
+			                  info->group, key))
+				return;
+		} else
+			nm_wireguard_peer_set_persistent_keepalive (peer, i64);
+	}
+
+	key = NM_WIREGUARD_PEER_ATTR_ENDPOINT;
+	str = nm_keyfile_plugin_kf_get_string (info->keyfile, info->group, key, NULL);
+	if (str && str[0]) {
+		if (!nm_wireguard_peer_set_endpoint (peer, str, FALSE)) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("key '%s.%s' is not not a valid endpoint"),
+			                  info->group, key))
+				return;
+		}
+	}
+	nm_clear_g_free (&str);
+
+	key = NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS;
+	sa = nm_keyfile_plugin_kf_get_string_list (info->keyfile, info->group, key, &n_sa, NULL);
+	if (n_sa > 0) {
+		gboolean has_error = FALSE;
+		gsize i;
+
+		for (i = 0; i < n_sa; i++) {
+			if (!nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC, sa[i], NULL, NULL, NULL)) {
+				has_error = TRUE;
+				continue;
+			}
+			nm_wireguard_peer_append_allowed_ip (peer, sa[i], TRUE);
+		}
+		if (has_error) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("key '%s.%s' has invalid allowed-ips"),
+			                  info->group, key))
+				return;
+		}
+	}
+	nm_clear_pointer (&sa, g_strfreev);
 
 	if (info->error)
-		return NULL;
-	return g_steal_pointer (&setting);
+		return;
+
+	if (!nm_wireguard_peer_is_valid (peer, TRUE, TRUE, &error)) {
+		if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		                  _("peer '%s' is invalid: %s"),
+		                  info->group, error->message))
+			return;
+		return;
+	}
+
+	s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (info->connection, NM_TYPE_SETTING_WIREGUARD));
+	if (!s_wg) {
+		s_wg_new = NM_SETTING_WIREGUARD (nm_setting_wireguard_new ());
+		s_wg = s_wg_new;
+	}
+
+	nm_setting_wireguard_append_peer (s_wg, peer);
+
+	if (s_wg_new) {
+		nm_connection_add_setting (info->connection,
+		                           NM_SETTING (g_steal_pointer (&s_wg_new)));
+	}
 }
 
 static void
-read_vpn_secrets (KeyfileReaderInfo *info, NMSettingVpn *s_vpn)
+_read_setting_vpn_secrets (KeyfileReaderInfo *info)
 {
 	gs_strfreev char **keys = NULL;
-	char **iter;
+	gsize i, n_keys;
+	NMSettingVpn *s_vpn;
+
+	s_vpn = nm_connection_get_setting_vpn (info->connection);
+	if (!s_vpn) {
+		/* if we don't also have a [vpn] section (which must be parsed earlier),
+		 * we don't do anything. */
+		nm_assert (!g_key_file_has_group (info->keyfile, "vpn"));
+		return;
+	}
 
-	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, NULL, NULL);
-	for (iter = keys; *iter; iter++) {
-		char *secret;
+	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, &n_keys, NULL);
+	for (i = 0; i < n_keys; i++) {
+		gs_free char *secret = NULL;
 
-		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);
-		}
+		secret = nm_keyfile_plugin_kf_get_string (info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, keys[i], NULL);
+		if (secret)
+			nm_setting_vpn_add_secret (s_vpn, keys[i], secret);
 	}
 }
 
@@ -2876,12 +3117,11 @@ nm_keyfile_read (GKeyFile *keyfile,
 {
 	gs_unref_object NMConnection *connection = NULL;
 	NMSettingConnection *s_con;
-	NMSetting *setting;
-	char **groups;
-	gsize length;
+	gs_strfreev char **groups = NULL;
+	gsize n_groups;
 	gsize i;
 	gboolean vpn_secrets = FALSE;
-	KeyfileReaderInfo info = { 0 };
+	KeyfileReaderInfo info;
 
 	g_return_val_if_fail (keyfile, NULL);
 	g_return_val_if_fail (!error || !*error, NULL);
@@ -2889,31 +3129,35 @@ nm_keyfile_read (GKeyFile *keyfile,
 
 	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;
+	info = (KeyfileReaderInfo) {
+		.connection = connection,
+		.keyfile    = keyfile,
+		.base_dir   = base_dir,
+		.handler    = handler,
+		.user_data  = user_data,
+	};
+
+	groups = g_key_file_get_groups (keyfile, &n_groups);
+	if (!groups)
+		n_groups = 0;
 
-	groups = g_key_file_get_groups (keyfile, &length);
-	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;
-		}
+	for (i = 0; i < n_groups; i++) {
 
 		info.group = groups[i];
-		setting = read_setting (&info);
+
+		if (nm_streq (groups[i], NM_KEYFILE_GROUP_VPN_SECRETS)) {
+			/* Only read out secrets when needed */
+			vpn_secrets = TRUE;
+		} else if (NM_STR_HAS_PREFIX (groups[i], NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER))
+			_read_setting_wireguard_peer (&info);
+		else
+			_read_setting (&info);
+
 		info.group = NULL;
-		if (info.error) {
-			g_propagate_error (error, info.error);
-			return NULL;
-		}
-		if (setting)
-			nm_connection_add_setting (connection, setting);
+
+		if (info.error)
+			goto out_with_info_error;
 	}
-	g_strfreev (groups);
 
 	s_con = nm_connection_get_setting_connection (connection);
 	if (!s_con) {
@@ -2926,55 +3170,51 @@ nm_keyfile_read (GKeyFile *keyfile,
 	 */
 	if (   !nm_setting_connection_get_interface_name (s_con)
 	    && nm_setting_connection_get_connection_type (s_con)) {
-		char *interface_name;
+		gs_free char *interface_name = NULL;
 
 		interface_name = g_key_file_get_string (keyfile,
 		                                        nm_setting_connection_get_connection_type (s_con),
 		                                        "interface-name",
 		                                        NULL);
-		if (interface_name) {
+		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) {
-				g_propagate_error (error, info.error);
-				return NULL;
-			}
-		}
+		info.group = NM_KEYFILE_GROUP_VPN_SECRETS;
+		_read_setting_vpn_secrets (&info);
+		info.group = NULL;;
+		if (info.error)
+			goto out_with_info_error;
 	}
 
 	return g_steal_pointer (&connection);
+
+out_with_info_error:
+	g_propagate_error (error, info.error);
+	return NULL;
 }
 
 /*****************************************************************************/
 
 static void
-write_setting_value (NMSetting *setting,
-                     const char *key,
-                     const GValue *value,
-                     GParamFlags flag,
-                     gpointer user_data)
+write_setting_value (KeyfileWriterInfo *info,
+                     NMSetting *setting,
+                     const NMSettInfoProperty *property_info)
 {
-	KeyfileWriterInfo *info = user_data;
+	const ParseInfoProperty *pip;
 	const char *setting_name;
+	const char *key;
+	char numstr[64];
+	GValue value;
 	GType type;
-	const ParseInfoProperty *pip;
-	GParamSpec *pspec;
 
-	if (info->error)
+	nm_assert (!info->error);
+
+	if (!property_info->param_spec)
 		return;
 
-	pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), key);
-	nm_assert (pspec);
+	key = property_info->param_spec->name;
 
 	pip = _parse_info_find (setting, key, &setting_name);
 
@@ -3002,58 +3242,65 @@ write_setting_value (NMSetting *setting,
 	 * the secret flags there are in a third-level hash in the 'secrets'
 	 * property.
 	 */
-	if (   (pspec->flags & NM_SETTING_PARAM_SECRET)
+	if (   (property_info->param_spec->flags & NM_SETTING_PARAM_SECRET)
 	    && !NM_IS_SETTING_VPN (setting)) {
 		NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
 
 		if (!nm_setting_get_secret_flags (setting, key, &secret_flags, NULL))
-			g_assert_not_reached ();
-		if (secret_flags != NM_SETTING_SECRET_FLAG_NONE)
+			g_return_if_reached ();
+		if (!_secret_flags_persist_secret (secret_flags))
 			return;
 	}
 
+	value = (GValue) { 0 };
+
+	g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (property_info->param_spec));
+	g_object_get_property (G_OBJECT (setting), property_info->param_spec->name, &value);
+
 	if (   (!pip || !pip->writer_persist_default)
-	    && g_param_value_defaults (pspec, (GValue *) value)) {
+	    && g_param_value_defaults (property_info->param_spec, &value)) {
 		nm_assert (!g_key_file_has_key (info->keyfile, setting_name, key, NULL));
-		return;
+		goto out_unset_value;
 	}
 
 	if (pip && pip->writer) {
-		pip->writer (info, setting, key, value);
-		return;
+		pip->writer (info, setting, key, &value);
+		goto out_unset_value;
 	}
 
-	type = G_VALUE_TYPE (value);
+	type = G_VALUE_TYPE (&value);
 	if (type == G_TYPE_STRING) {
 		const char *str;
 
-		str = g_value_get_string (value);
+		str = g_value_get_string (&value);
 		if (str)
 			nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, str);
-	} else if (type == G_TYPE_UINT)
-		nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, (int) g_value_get_uint (value));
-	else if (type == G_TYPE_INT)
-		nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, g_value_get_int (value));
-	else if (type == G_TYPE_UINT64) {
-		char numstr[30];
-
-		nm_sprintf_buf (numstr, "%" G_GUINT64_FORMAT, g_value_get_uint64 (value));
+	} else if (type == G_TYPE_UINT) {
+		nm_sprintf_buf (numstr, "%u", g_value_get_uint (&value));
+		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr);
+	} else if (type == G_TYPE_INT) {
+		nm_sprintf_buf (numstr, "%d", g_value_get_int (&value));
+		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr);
+	} else if (type == G_TYPE_UINT64) {
+		nm_sprintf_buf (numstr, "%" G_GUINT64_FORMAT, g_value_get_uint64 (&value));
 		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr);
 	} else if (type == G_TYPE_INT64) {
-		char numstr[30];
-
-		nm_sprintf_buf (numstr, "%" G_GINT64_FORMAT, g_value_get_int64 (value));
+		nm_sprintf_buf (numstr, "%" G_GINT64_FORMAT, g_value_get_int64 (&value));
 		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr);
 	} else if (type == G_TYPE_BOOLEAN) {
-		nm_keyfile_plugin_kf_set_boolean (info->keyfile, setting_name, key, g_value_get_boolean (value));
+		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key,
+		                                  g_value_get_boolean (&value)
+		                                ? "true"
+		                                : "false");
 	} else if (type == G_TYPE_CHAR) {
-		nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, (int) g_value_get_schar (value));
+		nm_sprintf_buf (numstr, "%d", (int) g_value_get_schar (&value));
+		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr);
 	} else if (type == G_TYPE_BYTES) {
 		GBytes *bytes;
 		const guint8 *data;
 		gsize len = 0;
 
-		bytes = g_value_get_boxed (value);
+		bytes = g_value_get_boxed (&value);
 		data = bytes ? g_bytes_get_data (bytes, &len) : NULL;
 
 		if (data != NULL && len > 0)
@@ -3061,19 +3308,109 @@ write_setting_value (NMSetting *setting,
 	} else if (type == G_TYPE_STRV) {
 		char **array;
 
-		array = (char **) g_value_get_boxed (value);
+		array = (char **) g_value_get_boxed (&value);
 		nm_keyfile_plugin_kf_set_string_list (info->keyfile, setting_name, key, (const char **const) array, g_strv_length (array));
 	} else if (type == G_TYPE_HASH_TABLE) {
-		write_hash_of_string (info->keyfile, setting, key, value);
+		write_hash_of_string (info->keyfile, setting, key, &value);
 	} else if (type == G_TYPE_ARRAY) {
-		write_array_of_uint (info->keyfile, setting, key, value);
-	} else if (G_VALUE_HOLDS_FLAGS (value)) {
-		/* Flags are guint but GKeyFile has no uint reader, just uint64 */
-		nm_keyfile_plugin_kf_set_uint64 (info->keyfile, setting_name, key, (guint64) g_value_get_flags (value));
-	} else if (G_VALUE_HOLDS_ENUM (value))
-		nm_keyfile_plugin_kf_set_integer (info->keyfile, setting_name, key, (int) g_value_get_enum (value));
-	else
-		g_warn_if_reached ();
+		write_array_of_uint (info->keyfile, setting, key, &value);
+	} else if (G_VALUE_HOLDS_FLAGS (&value)) {
+		nm_sprintf_buf (numstr, "%u", g_value_get_flags (&value));
+		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr);
+	} else if (G_VALUE_HOLDS_ENUM (&value)) {
+		nm_sprintf_buf (numstr, "%d", g_value_get_enum (&value));
+		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_name, key, numstr);
+	} else
+		g_return_if_reached ();
+
+out_unset_value:
+	g_value_unset (&value);
+}
+
+static void
+_write_setting_wireguard (NMSetting *setting, KeyfileWriterInfo *info)
+{
+	NMSettingWireGuard *s_wg;
+	guint i_peer, n_peers;
+
+	s_wg = NM_SETTING_WIREGUARD (setting);
+
+	n_peers = nm_setting_wireguard_get_peers_len (s_wg);
+	for (i_peer = 0; i_peer < n_peers; i_peer++) {
+		NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i_peer);
+		const char *public_key;
+		char group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER) + 200];
+		NMSettingSecretFlags secret_flags;
+		gboolean any_key = FALSE;
+		guint i_aip, n_aip;
+		const char *cstr;
+		guint32 u32;
+
+		public_key = nm_wireguard_peer_get_public_key (peer);
+		if (   !public_key
+		    || !public_key[0]
+		    || !NM_STRCHAR_ALL (public_key, ch, nm_sd_utils_unbase64char (ch, TRUE) >= 0)) {
+			/* invalid peer. Skip it */
+			continue;
+		}
+
+		if (g_snprintf (group,
+		                sizeof (group),
+		                "%s%s",
+		                NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER,
+		                nm_wireguard_peer_get_public_key (peer)) >= sizeof (group)) {
+			/* Too long. Not a valid public key. Skip the peer. */
+			continue;
+		}
+
+		cstr = nm_wireguard_peer_get_endpoint (peer);
+		if (cstr) {
+			g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, cstr);
+			any_key = TRUE;
+		}
+
+		secret_flags = nm_wireguard_peer_get_preshared_key_flags (peer);
+		if (_secret_flags_persist_secret (secret_flags)) {
+			cstr = nm_wireguard_peer_get_preshared_key (peer);
+			if (cstr) {
+				g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, cstr);
+				any_key = TRUE;
+			}
+		}
+
+		/* usually, we don't persist the secret-flags 0 (because they are the default).
+		 * For WireGuard peers, the default secret-flags for preshared-key are 4 (not-required).
+		 * So, in this case behave differently: a missing preshared-key-flag setting means
+		 * "not-required". */
+		if (secret_flags != NM_SETTING_SECRET_FLAG_NOT_REQUIRED) {
+			g_key_file_set_int64 (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, secret_flags);
+			any_key = TRUE;
+		}
+
+		u32 = nm_wireguard_peer_get_persistent_keepalive (peer);
+		if (u32) {
+			g_key_file_set_uint64 (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, u32);
+			any_key = TRUE;
+		}
+
+		n_aip = nm_wireguard_peer_get_allowed_ips_len (peer);
+		if (n_aip > 0) {
+			gs_free const char **strv = NULL;
+
+			strv = g_new (const char *, ((gsize) n_aip) + 1);
+			for (i_aip = 0; i_aip < n_aip; i_aip++)
+				strv[i_aip] = nm_wireguard_peer_get_allowed_ip (peer, i_aip, NULL);
+			strv[n_aip] = NULL;
+			g_key_file_set_string_list (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS,
+			                            strv, n_aip);
+			any_key = TRUE;
+		}
+
+		if (!any_key) {
+			/* we cannot omit all keys. At an empty endpoint. */
+			g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, "");
+		}
+	}
 }
 
 GKeyFile *
@@ -3082,9 +3419,10 @@ nm_keyfile_write (NMConnection *connection,
                   void *user_data,
                   GError **error)
 {
-	KeyfileWriterInfo info = { 0 };
+	gs_unref_keyfile GKeyFile *keyfile = NULL;
+	KeyfileWriterInfo info;
 	gs_free NMSetting **settings = NULL;
-	guint i, length = 0;
+	guint i, j, n_settings = 0;
 
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
 	g_return_val_if_fail (!error || !*error, NULL);
@@ -3092,18 +3430,22 @@ nm_keyfile_write (NMConnection *connection,
 	if (!nm_connection_verify (connection, error))
 		return NULL;
 
-	info.connection = connection;
-	info.keyfile = g_key_file_new ();
-	info.error = NULL;
-	info.handler = handler;
-	info.user_data = user_data;
+	keyfile = g_key_file_new ();
 
-	settings = nm_connection_get_settings (connection, &length);
-	for (i = 0; i < length; i++) {
+	info = (KeyfileWriterInfo) {
+		.connection = connection,
+		.keyfile    = keyfile,
+		.error      = NULL,
+		.handler    = handler,
+		.user_data  = user_data,
+	};
+
+	settings = nm_connection_get_settings (connection, &n_settings);
+	for (i = 0; i < n_settings; i++) {
 		const NMSettInfoSetting *sett_info;
 		NMSetting *setting = settings[i];
 
-		sett_info = _nm_sett_info_setting_get (NM_SETTING_GET_CLASS (setting));
+		sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
 
 		if (sett_info->detail.gendata_info) {
 			guint k, n_keys;
@@ -3138,18 +3480,175 @@ nm_keyfile_write (NMConnection *connection,
 					}
 				}
 			}
-		} else
-			nm_setting_enumerate_values (setting, write_setting_value, &info);
+		}
 
-		if (info.error)
-			break;
+		for (j = 0; j < sett_info->property_infos_len; j++) {
+			const NMSettInfoProperty *property_info = _nm_sett_info_property_info_get_sorted (sett_info, j);
+
+			write_setting_value (&info, setting, property_info);
+			if (info.error)
+				goto out_with_info_error;
+		}
+
+		if (NM_IS_SETTING_WIREGUARD (setting)) {
+			_write_setting_wireguard (setting, &info);
+			if (info.error)
+				goto out_with_info_error;
+		}
+
+		nm_assert (!info.error);
 	}
 
-	if (info.error) {
-		g_propagate_error (error, info.error);
-		g_key_file_unref (info.keyfile);
-		return NULL;
+	nm_assert (!info.error);
+
+	return g_steal_pointer (&keyfile);
+
+out_with_info_error:
+	g_propagate_error (error, info.error);
+	return NULL;
+}
+
+/*****************************************************************************/
+
+static const char temp_letters[] =
+"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
+/*
+ * Check '.[a-zA-Z0-9]{6}' file suffix used for temporary files by g_file_set_contents() (mkstemp()).
+ */
+static gboolean
+check_mkstemp_suffix (const char *path)
+{
+	const char *ptr;
+
+	nm_assert (path);
+
+	/* Matches *.[a-zA-Z0-9]{6} suffix of mkstemp()'s temporary files */
+	ptr = strrchr (path, '.');
+	if (   ptr
+	    && strspn (&ptr[1], temp_letters) == 6
+	    && ptr[7] == '\0')
+		return TRUE;
+	return FALSE;
+}
+
+static gboolean
+_check_suffix_impl (const char *base, const char *tag, gsize tag_len)
+{
+	gsize len;
+
+	nm_assert (base);
+	nm_assert (tag);
+	nm_assert (strlen (tag) == tag_len);
+
+	len = strlen (base);
+	if (   len > tag_len
+	    && !g_ascii_strcasecmp (base + len - tag_len, tag))
+		return TRUE;
+	return FALSE;
+}
+#define check_suffix(base, tag) _check_suffix_impl ((base), ""tag"", NM_STRLEN (tag))
+
+#define SWP_TAG ".swp"
+#define SWPX_TAG ".swpx"
+#define PEM_TAG ".pem"
+#define DER_TAG ".der"
+
+gboolean
+nm_keyfile_utils_ignore_filename (const char *filename, gboolean require_extension)
+{
+	const char *base;
+	gsize l;
+
+	/* ignore_filename() must mirror nm_keyfile_utils_create_filename() */
+
+	g_return_val_if_fail (filename, TRUE);
+
+	base = strrchr (filename, '/');
+	if (base)
+		base++;
+	else
+		base = filename;
+
+	if (!base[0]) {
+		/* this check above with strrchr() also rejects "/some/path/with/trailing/slash/",
+		 * but that is fine, because such a path would name a directory, and we are not
+		 * interested in directories. */
+		return TRUE;
+	}
+
+	if (base[0] == '.') {
+		/* don't allow hidden files */
+		return TRUE;
+	}
+
+	l = strlen (base);
+
+	if (require_extension) {
+		if (   l <= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)
+		    || !g_str_has_suffix (base, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION))
+			return TRUE;
+		return FALSE;
 	}
 
-	return info.keyfile;
+	/* Ignore backup files */
+	if (base[l - 1] == '~')
+		return TRUE;
+
+	/* Ignore temporary files */
+	if (check_mkstemp_suffix (base))
+		return TRUE;
+
+	/* Ignore 802.1x certificates and keys */
+	if (   check_suffix (base, PEM_TAG)
+	    || check_suffix (base, DER_TAG))
+		return TRUE;
+
+	return FALSE;
+}
+
+char *
+nm_keyfile_utils_create_filename (const char *name,
+                                  gboolean with_extension)
+{
+	GString *str;
+	const char *f = name;
+	/* keyfile used to escape with '*', do not change that behavior.
+	 *
+	 * But for newly added escapings, use '_' instead.
+	 * Also, @with_extension is new-style. */
+	const char ESCAPE_CHAR = with_extension ? '_' : '*';
+	const char ESCAPE_CHAR2 = '_';
+
+	g_return_val_if_fail (name && name[0], NULL);
+
+	str = g_string_sized_new (60);
+
+	/* Convert '/' to ESCAPE_CHAR */
+	for (f = name; f[0]; f++) {
+		if (f[0] == '/')
+			g_string_append_c (str, ESCAPE_CHAR);
+		else
+			g_string_append_c (str, f[0]);
+	}
+
+	/* nm_keyfile_utils_create_filename() must avoid anything that ignore_filename() would reject.
+	 * We can escape here more aggressivly then what we would read back. */
+	if (str->str[0] == '.')
+		str->str[0] = ESCAPE_CHAR2;
+	if (str->str[str->len - 1] == '~')
+		str->str[str->len - 1] = ESCAPE_CHAR2;
+	if (   check_mkstemp_suffix (str->str)
+	    || check_suffix (str->str, PEM_TAG)
+	    || check_suffix (str->str, DER_TAG))
+		g_string_append_c (str, ESCAPE_CHAR2);
+
+	if (with_extension)
+		g_string_append (str, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION);
+
+	/* nm_keyfile_utils_create_filename() must mirror ignore_filename() */
+	nm_assert (!strchr (str->str, '/'));
+	nm_assert (!nm_keyfile_utils_ignore_filename (str->str, with_extension));
+
+	return g_string_free (str, FALSE);;
 }