summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2019-09-06 09:25:17 +0200
committerMichael Biebl <biebl@debian.org>2019-09-06 09:25:17 +0200
commit555dfa331e295a3a687a7cc6870e1349ffed8f26 (patch)
treeaebc4e1ffa38b05780f0c1fe39bf78ca63c979c6 /src
parent5c79b900b59a5ad162ecc836e424468207ff031e (diff)
New upstream version 1.20.2 upstream/1.20.2
Diffstat (limited to 'src')
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.c4
-rw-r--r--src/devices/nm-acd-manager.c2
-rw-r--r--src/devices/nm-device.c7
-rw-r--r--src/dhcp/nm-dhcp-nettools.c13
-rw-r--r--src/nm-audit-manager.h1
-rw-r--r--src/nm-core-utils.h4
-rw-r--r--src/nm-ip4-config.c4
-rw-r--r--src/nm-manager.c29
-rw-r--r--src/settings/nm-agent-manager.c2
-rw-r--r--src/settings/nm-settings-connection.c4
-rw-r--r--src/settings/nm-settings.c48
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-plugin.c2
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.c71
-rw-r--r--src/supplicant/nm-supplicant-interface.c35
-rw-r--r--src/tests/test-core-with-expect.c5
-rw-r--r--src/tests/test-ip4-config.c42
16 files changed, 175 insertions, 98 deletions
diff --git a/src/devices/bluetooth/nm-bluez5-manager.c b/src/devices/bluetooth/nm-bluez5-manager.c
index 7bcd04e5..9c5e8644 100644
--- a/src/devices/bluetooth/nm-bluez5-manager.c
+++ b/src/devices/bluetooth/nm-bluez5-manager.c
@@ -404,6 +404,7 @@ get_managed_objects_cb (GDBusProxy *proxy,
                         GAsyncResult *res,
                         NMBluez5Manager *self)
 {
+	gs_unref_variant GVariant *variant0 = NULL;
 	GVariant *variant, *ifaces;
 	GVariantIter i;
 	GError *error = NULL;
@@ -422,7 +423,8 @@ get_managed_objects_cb (GDBusProxy *proxy,
 		g_clear_error (&error);
 		return;
 	}
-	g_variant_iter_init (&i, g_variant_get_child_value (variant, 0));
+	variant0 = g_variant_get_child_value (variant, 0);
+	g_variant_iter_init (&i, variant0);
 	while ((g_variant_iter_next (&i, "{&o*}", &path, &ifaces))) {
 		object_manager_interfaces_added (proxy, path, ifaces, self);
 		g_variant_unref (ifaces);
diff --git a/src/devices/nm-acd-manager.c b/src/devices/nm-acd-manager.c
index 16d3df8a..aca7ffd2 100644
--- a/src/devices/nm-acd-manager.c
+++ b/src/devices/nm-acd-manager.c
@@ -174,7 +174,6 @@ acd_event (GIOChannel *source, GIOCondition condition, gpointer data)
 	AddressInfo *info;
 	gboolean emit_probe_terminated = FALSE;
 	char address_str[INET_ADDRSTRLEN];
-	gs_free char *hwaddr_str = NULL;
 	int r;
 
 	if (n_acd_dispatch (self->acd))
@@ -182,6 +181,7 @@ acd_event (GIOChannel *source, GIOCondition condition, gpointer data)
 
 	while (   !n_acd_pop_event (self->acd, &event)
 	       && event) {
+		gs_free char *hwaddr_str = NULL;
 		gboolean check_probing_done = FALSE;
 
 		switch (event->event) {
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index f07c4f09..823cf48a 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -4151,11 +4151,12 @@ nm_device_create_and_realize (NMDevice *self,
 	nm_auto_nmpobj const NMPObject *plink_keep_alive = NULL;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	const NMPlatformLink *plink;
+	gboolean nm_owned;
 
 	/* Must be set before device is realized */
 	plink = nm_platform_link_get_by_ifname (nm_device_get_platform (self), priv->iface);
-	priv->nm_owned = !plink || !link_type_compatible (self, plink->type, NULL, NULL);
-	_LOGD (LOGD_DEVICE, "create (is %snm-owned)", priv->nm_owned ? "" : "not ");
+	nm_owned = !plink || !link_type_compatible (self, plink->type, NULL, NULL);
+	_LOGD (LOGD_DEVICE, "create (is %snm-owned)", nm_owned ? "" : "not ");
 
 	plink = NULL;
 	/* Create any resources the device needs */
@@ -4168,6 +4169,8 @@ nm_device_create_and_realize (NMDevice *self,
 		}
 	}
 
+	priv->nm_owned = nm_owned;
+
 	realize_start_setup (self,
 	                     plink,
 	                     FALSE, /* assume_state_guess_assume */
diff --git a/src/dhcp/nm-dhcp-nettools.c b/src/dhcp/nm-dhcp-nettools.c
index 3aa92180..9a0c6906 100644
--- a/src/dhcp/nm-dhcp-nettools.c
+++ b/src/dhcp/nm-dhcp-nettools.c
@@ -1208,18 +1208,6 @@ ip4_start (NMDhcpClient *client,
 	return TRUE;
 }
 
-static gboolean
-ip6_start (NMDhcpClient *client,
-           const char *dhcp_anycast_addr,
-           const struct in6_addr *ll_addr,
-           NMSettingIP6ConfigPrivacy privacy,
-           guint needed_prefixes,
-           GError **error)
-{
-	nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "nettools plugin does not support IPv6");
-	return FALSE;
-}
-
 static void
 stop (NMDhcpClient *client,
       gboolean release)
@@ -1265,7 +1253,6 @@ nm_dhcp_nettools_class_init (NMDhcpNettoolsClass *class)
 	object_class->dispose = dispose;
 
 	client_class->ip4_start = ip4_start;
-	client_class->ip6_start = ip6_start;
 	client_class->accept = _accept;
 	client_class->decline = decline;
 	client_class->stop = stop;
diff --git a/src/nm-audit-manager.h b/src/nm-audit-manager.h
index b8678227..b4ad5db9 100644
--- a/src/nm-audit-manager.h
+++ b/src/nm-audit-manager.h
@@ -49,6 +49,7 @@ typedef struct _NMAuditManagerClass NMAuditManagerClass;
 #define NM_AUDIT_OP_NET_CONTROL             "networking-control"
 #define NM_AUDIT_OP_RADIO_CONTROL           "radio-control"
 #define NM_AUDIT_OP_STATISTICS              "statistics"
+#define NM_AUDIT_OP_HOSTNAME_SAVE           "hostname-save"
 
 #define NM_AUDIT_OP_DEVICE_AUTOCONNECT      "device-autoconnect"
 #define NM_AUDIT_OP_DEVICE_DISCONNECT       "device-disconnect"
diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h
index 855a5d08..a0efb3b8 100644
--- a/src/nm-core-utils.h
+++ b/src/nm-core-utils.h
@@ -496,4 +496,8 @@ const char *nm_utils_parse_dns_domain (const char *domain, gboolean *is_routing)
 
 #define NM_VPN_ROUTE_METRIC_DEFAULT     50
 
+#define NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED   "Unable to authenticate the request"
+#define NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN    "Unable to determine UID of the request"
+#define NM_UTILS_ERROR_MSG_INSUFF_PRIV       "Insufficient privileges"
+
 #endif /* __NM_CORE_UTILS_H__ */
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index 36e75bb2..6dd08434 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -190,7 +190,9 @@ _nm_ip_config_add_obj (NMDedupMultiIndex *multi_idx,
 	if (!nm_dedup_multi_index_add_full (multi_idx,
 	                                    &idx_type->parent,
 	                                    obj_new,
-	                                    NM_DEDUP_MULTI_IDX_MODE_APPEND,
+	                                      append_force
+	                                    ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE
+	                                    : NM_DEDUP_MULTI_IDX_MODE_APPEND,
 	                                    NULL,
 	                                    entry_old ?: NM_DEDUP_MULTI_ENTRY_MISSING,
 	                                    NULL,
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 64cdb9ae..78f3722c 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -1223,7 +1223,7 @@ impl_manager_reload (NMDBusObject *obj,
 		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_MANAGER_ERROR,
 		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                                               "Unable to authenticate request");
+		                                               NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
 		return;
 	}
 
@@ -2032,7 +2032,6 @@ system_create_virtual_device (NMManager *self, NMConnection *connection)
 			         "couldn't create the device: %s",
 			         error->message);
 			g_error_free (error);
-			remove_device (self, device, FALSE);
 			return NULL;
 		}
 
@@ -2435,7 +2434,7 @@ device_auth_request_cb (NMDevice *device,
 	if (!subject) {
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                             "Failed to get request UID.");
+		                             NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
 		goto done;
 	}
 
@@ -2453,7 +2452,7 @@ device_auth_request_cb (NMDevice *device,
 	if (!chain) {
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                             "Unable to authenticate request.");
+		                             NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
 		goto done;
 	}
 
@@ -5129,7 +5128,7 @@ validate_activation_request (NMManager *self,
 		g_set_error_literal (error,
 		                     NM_MANAGER_ERROR,
 		                     NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                     "Failed to get request UID.");
+		                     NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
 		return NULL;
 	}
 
@@ -5778,7 +5777,7 @@ impl_manager_deactivate_connection (NMDBusObject *obj,
 	if (!subject) {
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                             "Failed to get request UID.");
+		                             NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
 		goto done;
 	}
 
@@ -5794,7 +5793,7 @@ impl_manager_deactivate_connection (NMDBusObject *obj,
 	if (!chain) {
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                             "Unable to authenticate request.");
+		                             NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
 		goto done;
 	}
 
@@ -6176,7 +6175,7 @@ impl_manager_enable (NMDBusObject *obj,
 	if (!chain) {
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                             "Unable to authenticate request.");
+		                             NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
 		goto done;
 	}
 
@@ -6261,7 +6260,7 @@ impl_manager_get_permissions (NMDBusObject *obj,
 		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_MANAGER_ERROR,
 		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                                               "Unable to authenticate request.");
+		                                               NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
 		return;
 	}
 
@@ -6478,7 +6477,7 @@ impl_manager_check_connectivity (NMDBusObject *obj,
 		g_dbus_method_invocation_return_error_literal(invocation,
 		                                              NM_MANAGER_ERROR,
 		                                              NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                                              "Unable to authenticate request.");
+		                                              NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
 		return;
 	}
 
@@ -6920,7 +6919,7 @@ nm_manager_dbus_set_property_handle (NMDBusObject *obj,
 
 	subject = nm_auth_subject_new_unix_process_from_context (invocation);
 	if (!subject) {
-		error_message = "Could not determine request UID";
+		error_message = NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN;
 		goto err;
 	}
 
@@ -7052,7 +7051,7 @@ impl_manager_checkpoint_create (NMDBusObject *obj,
 		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_MANAGER_ERROR,
 		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                                               "Unable to authenticate request.");
+		                                               NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
 		return;
 	}
 
@@ -7085,7 +7084,7 @@ impl_manager_checkpoint_destroy (NMDBusObject *obj,
 		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_MANAGER_ERROR,
 		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                                               "Unable to authenticate request.");
+		                                               NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
 		return;
 	}
 
@@ -7116,7 +7115,7 @@ impl_manager_checkpoint_rollback (NMDBusObject *obj,
 		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_MANAGER_ERROR,
 		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                                               "Unable to authenticate request.");
+		                                               NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
 		return;
 	}
 
@@ -7148,7 +7147,7 @@ impl_manager_checkpoint_adjust_rollback_timeout (NMDBusObject *obj,
 		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_MANAGER_ERROR,
 		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                                               "Unable to authenticate request.");
+		                                               NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
 		return;
 	}
 
diff --git a/src/settings/nm-agent-manager.c b/src/settings/nm-agent-manager.c
index 2f9827d5..db0e021c 100644
--- a/src/settings/nm-agent-manager.c
+++ b/src/settings/nm-agent-manager.c
@@ -392,7 +392,7 @@ agent_manager_register_with_capabilities (NMAgentManager *self,
 	if (!subject) {
 		error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
 		                             NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
-		                             "Unable to determine request sender and UID.");
+		                             NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
 		goto done;
 	}
 	sender_uid = nm_auth_subject_get_unix_process_uid (subject);
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index 25a27e68..ed0cb8e8 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -1235,7 +1235,7 @@ pk_auth_cb (NMAuthManager *auth_manager,
 	} else if (nm_auth_call_result_eval (is_authorized, is_challenge, auth_error) != NM_AUTH_CALL_RESULT_YES) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                             "Insufficient privileges");
+		                             NM_UTILS_ERROR_MSG_INSUFF_PRIV);
 	}
 
 	auth_data->callback (self,
@@ -1268,7 +1268,7 @@ _new_auth_subject (GDBusMethodInvocation *context, GError **error)
 		g_set_error_literal (error,
 		                     NM_SETTINGS_ERROR,
 		                     NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                     "Unable to determine UID of request.");
+		                     NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
 	}
 
 	return subject;
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index 42a7bca0..4bdcb522 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -2421,7 +2421,7 @@ pk_add_cb (NMAuthChain *chain,
 	if (result != NM_AUTH_CALL_RESULT_YES) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                             "Insufficient privileges.");
+		                             NM_UTILS_ERROR_MSG_INSUFF_PRIV);
 	} else {
 		/* Authorized */
 		connection = nm_auth_chain_get_data (chain, "connection");
@@ -2521,7 +2521,7 @@ nm_settings_add_connection_dbus (NMSettings *self,
 	if (!chain) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                             "Unable to authenticate the request.");
+		                             NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
 		goto done;
 	}
 
@@ -2605,7 +2605,7 @@ settings_add_connection_helper (NMSettings *self,
 		g_dbus_method_invocation_return_error_literal (context,
 		                                               NM_SETTINGS_ERROR,
 		                                               NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                                               "Unable to determine UID of request.");
+		                                               NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
 		return;
 	}
 
@@ -2946,7 +2946,7 @@ impl_settings_get_connection_by_uuid (NMDBusObject *obj,
 	if (!subject) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                             "Unable to determine UID of request.");
+		                             NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
 		goto error;
 	}
 
@@ -3288,15 +3288,14 @@ pk_hostname_cb (NMAuthChain *chain,
 	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
 
 	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME);
+	hostname = nm_auth_chain_get_data (chain, "hostname");
 
 	/* If our NMSettingsConnection is already gone, do nothing */
 	if (result != NM_AUTH_CALL_RESULT_YES) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                             "Insufficient privileges.");
+		                             NM_UTILS_ERROR_MSG_INSUFF_PRIV);
 	} else {
-		hostname = nm_auth_chain_get_data (chain, "hostname");
-
 		if (!nm_hostname_manager_write_hostname (priv->hostname_manager, hostname)) {
 			error = g_error_new_literal (NM_SETTINGS_ERROR,
 			                             NM_SETTINGS_ERROR_FAILED,
@@ -3304,6 +3303,12 @@ pk_hostname_cb (NMAuthChain *chain,
 		}
 	}
 
+	nm_audit_log_control_op (NM_AUDIT_OP_HOSTNAME_SAVE,
+	                         hostname,
+	                         !error,
+	                         nm_auth_chain_get_subject (chain),
+	                         error ? error->message : NULL);
+
 	if (error)
 		g_dbus_method_invocation_take_error (context, error);
 	else
@@ -3323,30 +3328,39 @@ impl_settings_save_hostname (NMDBusObject *obj,
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 	NMAuthChain *chain;
 	const char *hostname;
+	const char *error_reason;
+	int error_code;
 
 	g_variant_get (parameters, "(&s)", &hostname);
 
 	/* Minimal validation of the hostname */
 	if (!nm_hostname_manager_validate_hostname (hostname)) {
-		g_dbus_method_invocation_return_error_literal (invocation,
-		                                               NM_SETTINGS_ERROR,
-		                                               NM_SETTINGS_ERROR_INVALID_HOSTNAME,
-		                                               "The hostname was too long or contained invalid characters.");
-		return;
+		error_code = NM_SETTINGS_ERROR_INVALID_HOSTNAME;
+		error_reason = "The hostname was too long or contained invalid characters";
+		goto err;
 	}
 
 	chain = nm_auth_chain_new_context (invocation, pk_hostname_cb, self);
 	if (!chain) {
-		g_dbus_method_invocation_return_error_literal (invocation,
-		                                               NM_SETTINGS_ERROR,
-		                                               NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                                               "Unable to authenticate the request.");
-		return;
+		error_code = NM_SETTINGS_ERROR_PERMISSION_DENIED;
+		error_reason = NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED;
+		goto err;
 	}
 
 	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
 	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME, TRUE);
 	nm_auth_chain_set_data (chain, "hostname", g_strdup (hostname), g_free);
+	return;
+err:
+	nm_audit_log_control_op (NM_AUDIT_OP_HOSTNAME_SAVE,
+	                         hostname,
+	                         FALSE,
+	                         invocation,
+	                         error_reason);
+	g_dbus_method_invocation_return_error_literal (invocation,
+	                                               NM_SETTINGS_ERROR,
+	                                               error_code,
+	                                               error_reason);
 }
 
 /*****************************************************************************/
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
index 475ecbb6..e663ab8a 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
@@ -89,6 +89,8 @@ static GHashTable *load_eni_ifaces (NMSIfupdownPlugin *self);
 static void
 _storage_data_destroy (StorageData *sd)
 {
+	if (!sd)
+		return;
 	nm_g_object_unref (sd->connection);
 	nm_g_object_unref (sd->storage);
 	g_slice_free (StorageData, sd);
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.c b/src/settings/plugins/keyfile/nms-keyfile-writer.c
index 5fbbb7a1..abd3f1f4 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -126,6 +126,10 @@ cert_writer (NMConnection *connection,
 		new_path = g_strdup_printf ("%s/%s-%s.%s", info->keyfile_dir, nm_connection_get_uuid (connection),
 		                            cert_data->vtable->file_suffix, ext);
 
+		/* FIXME(keyfile-parse-in-memory): writer must not access/write to the file system before
+		 * being sure that the entire profile can be written and all circumstances are good to
+		 * proceed. That means, while writing we must only collect the blogs in-memory, and write
+		 * them all in the end together (or not at all). */
 		success = nm_utils_file_set_contents (new_path, (const char *) blob_data,
 		                                      blob_len, 0600, &local);
 		if (success) {
@@ -193,10 +197,12 @@ _internal_write_connection (NMConnection *connection,
 	gs_free char *path = NULL;
 	const char *id;
 	WriteInfo info = { 0 };
-	GError *local_err = NULL;
+	gs_free_error GError *local_err = NULL;
 	int errsv;
 	gboolean rename;
 	int i_path;
+	gs_unref_object NMConnection *reread = NULL;
+	gboolean reread_same = FALSE;
 
 	g_return_val_if_fail (!out_path || !*out_path, FALSE);
 	g_return_val_if_fail (keyfile_dir && keyfile_dir[0] == '/', FALSE);
@@ -309,12 +315,40 @@ _internal_write_connection (NMConnection *connection,
 		return FALSE;
 	}
 
+	if (   out_reread
+	    || out_reread_same) {
+		gs_free_error GError *reread_error = NULL;
+
+		reread = nms_keyfile_reader_from_keyfile (kf_file, path, NULL, profile_dir, FALSE, &reread_error);
+
+		if (   !reread
+		    || !nm_connection_normalize (reread, NULL, NULL, &reread_error)) {
+			nm_log_err (LOGD_SETTINGS, "BUG: the profile cannot be stored in keyfile format without becoming unusable: %s", reread_error->message);
+			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+			             "keyfile writer produces an invalid connection: %s",
+			             reread_error->message);
+			nm_assert_not_reached ();
+			return FALSE;
+		}
+
+		if (out_reread_same) {
+			reread_same = !!nm_connection_compare (reread, connection, NM_SETTING_COMPARE_FLAG_EXACT);
+
+			nm_assert (reread_same == nm_connection_compare (connection, reread, NM_SETTING_COMPARE_FLAG_EXACT));
+			nm_assert (reread_same == ({
+			                                gs_unref_hashtable GHashTable *_settings = NULL;
+
+			                                (   nm_connection_diff (reread, connection, NM_SETTING_COMPARE_FLAG_EXACT, &_settings)
+			                                 && !_settings);
+			                           }));
+		}
+	}
+
 	nm_utils_file_set_contents (path, kf_content_buf, kf_content_len, 0600, &local_err);
 	if (local_err) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 		             "error writing to file '%s': %s",
 		             path, local_err->message);
-		g_error_free (local_err);
 		return FALSE;
 	}
 
@@ -335,36 +369,8 @@ _internal_write_connection (NMConnection *connection,
 	    && !nm_streq (path, existing_path))
 		unlink (existing_path);
 
-	if (out_reread || out_reread_same) {
-		gs_unref_object NMConnection *reread = NULL;
-		gboolean reread_same = FALSE;
-
-		reread = nms_keyfile_reader_from_keyfile (kf_file, path, NULL, profile_dir, FALSE, NULL);
-
-		nm_assert (NM_IS_CONNECTION (reread));
-
-		if (   reread
-		    && !nm_connection_normalize (reread, NULL, NULL, NULL)) {
-			nm_assert_not_reached ();
-			g_clear_object (&reread);
-		}
-
-		if (reread && out_reread_same) {
-			reread_same = !!nm_connection_compare (reread, connection, NM_SETTING_COMPARE_FLAG_EXACT);
-
-			nm_assert (reread_same == nm_connection_compare (connection, reread, NM_SETTING_COMPARE_FLAG_EXACT));
-			nm_assert (reread_same == ({
-			                                gs_unref_hashtable GHashTable *_settings = NULL;
-
-			                                (   nm_connection_diff (reread, connection, NM_SETTING_COMPARE_FLAG_EXACT, &_settings)
-			                                 && !_settings);
-			                           }));
-		}
-
-		NM_SET_OUT (out_reread, g_steal_pointer (&reread));
-		NM_SET_OUT (out_reread_same, reread_same);
-	}
-
+	NM_SET_OUT (out_reread, g_steal_pointer (&reread));
+	NM_SET_OUT (out_reread_same, reread_same);
 	NM_SET_OUT (out_path, g_steal_pointer (&path));
 
 	return TRUE;
@@ -439,4 +445,3 @@ nms_keyfile_writer_test_connection (NMConnection *connection,
 	                                   out_reread_same,
 	                                   error);
 }
-
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index 079afca3..a54b770c 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -135,7 +135,8 @@ typedef struct {
 	NMSupplicantFeature p2p_support;
 	NMSupplicantFeature mesh_support;
 	NMSupplicantFeature wfd_support;
-	NMSupplicantFeature ft_support;
+	NMSupplicantFeature ft_support_global;
+	NMSupplicantFeature ft_support_per_iface;
 	NMSupplicantFeature sha384_support;
 	guint32        max_scan_ssids;
 	guint32        ready_count;
@@ -609,14 +610,25 @@ static void
 parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	gboolean have_active = FALSE, have_p2p = FALSE, have_ssid = FALSE;
+	gboolean have_active = FALSE;
+	gboolean have_ssid = FALSE;
+	gboolean have_p2p = FALSE;
+	gboolean have_ft = FALSE;
 	gint32 max_scan_ssids = -1;
 	const char **array;
 
 	g_return_if_fail (capabilities && g_variant_is_of_type (capabilities, G_VARIANT_TYPE_VARDICT));
 
-	if (   g_variant_lookup (capabilities, "Modes", "^a&s", &array)
-	    && array) {
+	if (g_variant_lookup (capabilities, "KeyMgmt", "^a&s", &array)) {
+		have_ft = g_strv_contains (array, "wpa-ft-psk");
+		g_free (array);
+	}
+
+	priv->ft_support_per_iface =   have_ft
+	                             ? NM_SUPPLICANT_FEATURE_YES
+	                             : NM_SUPPLICANT_FEATURE_NO;
+
+	if (g_variant_lookup (capabilities, "Modes", "^a&s", &array)) {
 		if (g_strv_contains (array, "p2p"))
 			have_p2p = TRUE;
 		g_free (array);
@@ -627,8 +639,7 @@ parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities)
 		_notify (self, PROP_P2P_AVAILABLE);
 	}
 
-	if (   g_variant_lookup (capabilities, "Scan", "^a&s", &array)
-	    && array) {
+	if (g_variant_lookup (capabilities, "Scan", "^a&s", &array)) {
 		if (g_strv_contains (array, "active"))
 			have_active = TRUE;
 		if (g_strv_contains (array, "ssid"))
@@ -807,7 +818,13 @@ nm_supplicant_interface_get_wfd_support (NMSupplicantInterface *self)
 NMSupplicantFeature
 nm_supplicant_interface_get_ft_support (NMSupplicantInterface *self)
 {
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->ft_support;
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (priv->ft_support_global == NM_SUPPLICANT_FEATURE_NO)
+		return NM_SUPPLICANT_FEATURE_NO;
+	if (priv->ft_support_per_iface != NM_SUPPLICANT_FEATURE_UNKNOWN)
+		return priv->ft_support_per_iface;
+	return priv->ft_support_global;
 }
 
 NMSupplicantFeature
@@ -889,7 +906,7 @@ nm_supplicant_interface_set_ft_support (NMSupplicantInterface *self,
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	priv->ft_support = ft_support;
+	priv->ft_support_global = ft_support;
 }
 
 void
@@ -2801,7 +2818,7 @@ set_property (GObject *object,
 		break;
 	case PROP_FT_SUPPORT:
 		/* construct-only */
-		priv->ft_support = g_value_get_int (value);
+		priv->ft_support_global = g_value_get_int (value);
 		break;
 	case PROP_SHA384_SUPPORT:
 		/* construct-only */
diff --git a/src/tests/test-core-with-expect.c b/src/tests/test-core-with-expect.c
index d38025ec..2d111a41 100644
--- a/src/tests/test-core-with-expect.c
+++ b/src/tests/test-core-with-expect.c
@@ -118,13 +118,13 @@ test_nm_utils_kill_child_async_do (const char *name, pid_t pid, int sig, guint32
 	timeout_id = g_timeout_add_seconds (5, test_nm_utils_kill_child_async_fail_cb, &data);
 
 	data.loop = g_main_loop_new (NULL, FALSE);
-	g_main_run (data.loop);
+	g_main_loop_run (data.loop);
 
 	g_assert (data.called);
 	success = g_source_remove (timeout_id);
 	g_assert (success);
 
-	g_main_destroy (data.loop);
+	g_main_loop_unref (data.loop);
 }
 
 static void
@@ -578,4 +578,3 @@ main (int argc, char **argv)
 
 	return g_test_run ();
 }
-
diff --git a/src/tests/test-ip4-config.c b/src/tests/test-ip4-config.c
index d4d0c1bb..5afd8670 100644
--- a/src/tests/test-ip4-config.c
+++ b/src/tests/test-ip4-config.c
@@ -75,6 +75,47 @@ build_test_config (void)
 }
 
 static void
+test_replace (void)
+{
+	gs_unref_object NMIP4Config *config1 = NULL;
+	gs_unref_object NMIP4Config *config2 = NULL;
+	NMPlatformIP4Address addr;
+	gboolean relevant_changes;
+
+	config1 = nmtst_ip4_config_new (1);
+
+	addr = *nmtst_platform_ip4_address ("172.16.0.1", NULL, 24);
+	addr.timestamp = 10;
+	addr.preferred = 3600;
+	addr.lifetime = 7200;
+	nm_ip4_config_add_address (config1, &addr);
+
+	addr = *nmtst_platform_ip4_address ("172.16.0.2", NULL, 24);
+	addr.timestamp = 10;
+	addr.preferred = 3600;
+	addr.lifetime = 7200;
+	nm_ip4_config_add_address (config1, &addr);
+
+	config2 = nmtst_ip4_config_new (1);
+
+	addr = *nmtst_platform_ip4_address ("192.168.1.1", NULL, 24);
+	addr.timestamp = 40;
+	addr.preferred = 60;
+	addr.lifetime = 120;
+	nm_ip4_config_add_address (config2, &addr);
+
+	addr = *nmtst_platform_ip4_address ("172.16.0.2", NULL, 24);
+	addr.timestamp = 40;
+	addr.preferred = 60;
+	addr.lifetime = 120;
+	nm_ip4_config_add_address (config2, &addr);
+
+	g_assert (nm_ip4_config_replace (config2, config1, &relevant_changes));
+	g_assert (relevant_changes);
+	g_assert (nm_ip4_config_equal (config1, config2));
+}
+
+static void
 test_subtract (void)
 {
 	NMIP4Config *src, *dst;
@@ -339,6 +380,7 @@ main (int argc, char **argv)
 {
 	nmtst_init_with_logging (&argc, &argv, NULL, "DEFAULT");
 
+	g_test_add_func ("/ip4-config/replace", test_replace);
 	g_test_add_func ("/ip4-config/subtract", test_subtract);
 	g_test_add_func ("/ip4-config/compare-with-source", test_compare_with_source);
 	g_test_add_func ("/ip4-config/add-address-with-source", test_add_address_with_source);