summary refs log tree commit diff
path: root/src/settings/nm-settings-connection.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2014-07-06 02:16:10 +0200
committerMichael Biebl <biebl@debian.org>2014-07-06 02:16:10 +0200
commit33491bc4279481db8ae47213e34a6d695a0e8830 (patch)
tree097d2b0fdff3fae6885381ae5e57a182cd8cbbba /src/settings/nm-settings-connection.c
parent59c3714a494c3b3765657c0551ad82842d98a7d2 (diff)
Imported Upstream version 0.9.10.0 upstream/0.9.10.0
Diffstat (limited to 'src/settings/nm-settings-connection.c')
-rw-r--r--src/settings/nm-settings-connection.c793
1 files changed, 552 insertions, 241 deletions
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index 7bd35cf8..d20dbc98 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -16,7 +16,7 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
  * (C) Copyright 2008 Novell, Inc.
- * (C) Copyright 2008 - 2012 Red Hat, Inc.
+ * (C) Copyright 2008 - 2013 Red Hat, Inc.
  */
 
 #include "config.h"
@@ -38,9 +38,9 @@
 #include "nm-dbus-glib-types.h"
 #include "nm-logging.h"
 #include "nm-manager-auth.h"
-#include "nm-marshal.h"
 #include "nm-agent-manager.h"
 #include "NetworkManagerUtils.h"
+#include "nm-properties-changed-signal.h"
 
 #define SETTINGS_TIMESTAMPS_FILE  NMSTATEDIR "/timestamps"
 #define SETTINGS_SEEN_BSSIDS_FILE NMSTATEDIR "/seen-bssids"
@@ -52,6 +52,13 @@ static void impl_settings_connection_update (NMSettingsConnection *connection,
                                              GHashTable *new_settings,
                                              DBusGMethodInvocation *context);
 
+static void impl_settings_connection_update_unsaved (NMSettingsConnection *connection,
+                                                     GHashTable *new_settings,
+                                                     DBusGMethodInvocation *context);
+
+static void impl_settings_connection_save (NMSettingsConnection *connection,
+                                           DBusGMethodInvocation *context);
+
 static void impl_settings_connection_delete (NMSettingsConnection *connection,
                                              DBusGMethodInvocation *context);
 
@@ -70,12 +77,13 @@ G_DEFINE_TYPE (NMSettingsConnection, nm_settings_connection, NM_TYPE_CONNECTION)
 enum {
 	PROP_0 = 0,
 	PROP_VISIBLE,
+	PROP_UNSAVED,
 };
 
 enum {
 	UPDATED,
 	REMOVED,
-	UNREGISTER,
+	UPDATED_BY_USER,
 	LAST_SIGNAL
 };
 static guint signals[LAST_SIGNAL] = { 0 };
@@ -83,11 +91,22 @@ static guint signals[LAST_SIGNAL] = { 0 };
 typedef struct {
 	gboolean disposed;
 
-	NMDBusManager *dbus_mgr;
 	NMAgentManager *agent_mgr;
 	NMSessionMonitor *session_monitor;
 	guint session_changed_id;
 
+	/* TRUE if the connection has not yet been saved to disk,
+	 * or if it contains changes that have not been saved to disk.
+	 */
+	gboolean unsaved;
+
+	/* TRUE if the connection was generated by NetworkManager and has
+	 * not been saved or modified by the user.
+	 */
+	gboolean nm_generated;
+
+	guint updated_idle_id;
+
 	GSList *pending_auths; /* List of pending authentication requests */
 	gboolean visible; /* Is this connection is visible by some session? */
 	GSList *reqs;  /* in-progress secrets requests */
@@ -110,6 +129,11 @@ typedef struct {
 	guint64 timestamp;   /* Up-to-date timestamp of connection use */
 	gboolean timestamp_set;
 	GHashTable *seen_bssids; /* Up-to-date BSSIDs that's been seen for the connection */
+
+	int autoconnect_retries;
+	gint32 autoconnect_retry_time;
+	NMDeviceStateReason autoconnect_blocked_reason;
+
 } NMSettingsConnectionPrivate;
 
 /**************************************************************/
@@ -205,7 +229,6 @@ set_visible (NMSettingsConnection *self, gboolean new_visible)
 gboolean
 nm_settings_connection_is_visible (NMSettingsConnection *self)
 {
-	g_return_val_if_fail (self != NULL, FALSE);
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
 
 	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->visible;
@@ -218,7 +241,6 @@ nm_settings_connection_recheck_visibility (NMSettingsConnection *self)
 	NMSettingConnection *s_con;
 	guint32 num, i;
 
-	g_return_if_fail (self != NULL);
 	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (self));
 
 	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
@@ -269,7 +291,6 @@ nm_settings_connection_check_permission (NMSettingsConnection *self,
 	guint32 num, i;
 	const char *puser;
 
-	g_return_val_if_fail (self != NULL, FALSE);
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
 
 	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
@@ -371,28 +392,80 @@ secrets_cleared_cb (NMSettingsConnection *self)
 	priv->agent_secrets = NULL;
 }
 
-/* Update the settings of this connection to match that of 'new', taking care to
- * make a private copy of secrets.
+static gboolean
+emit_updated (NMSettingsConnection *self)
+{
+	NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->updated_idle_id = 0;
+	g_signal_emit (self, signals[UPDATED], 0);
+	return FALSE;
+}
+
+static void
+set_unsaved (NMSettingsConnection *self, gboolean now_unsaved)
+{
+	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+
+	if (priv->unsaved != now_unsaved) {
+		priv->unsaved = now_unsaved;
+		if (!priv->unsaved)
+			priv->nm_generated = FALSE;
+
+		g_object_notify (G_OBJECT (self), NM_SETTINGS_CONNECTION_UNSAVED);
+	}
+}
+
+static void
+changed_cb (NMSettingsConnection *self, gpointer user_data)
+{
+	gboolean update_unsaved = !!user_data;
+
+	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+
+	if (update_unsaved)
+		set_unsaved (self, TRUE);
+	if (priv->updated_idle_id == 0)
+		priv->updated_idle_id = g_idle_add ((GSourceFunc) emit_updated, self);
+}
+
+/* Update the settings of this connection to match that of 'new_connection',
+ * taking care to make a private copy of secrets.
  */
 gboolean
 nm_settings_connection_replace_settings (NMSettingsConnection *self,
-                                         NMConnection *new,
+                                         NMConnection *new_connection,
+                                         gboolean update_unsaved,
                                          GError **error)
 {
 	NMSettingsConnectionPrivate *priv;
-	GHashTable *new_settings, *hash = NULL;
+	GHashTable *hash = NULL;
 	gboolean success = FALSE;
 
-	g_return_val_if_fail (self != NULL, FALSE);
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
-	g_return_val_if_fail (new != NULL, FALSE);
-	g_return_val_if_fail (NM_IS_CONNECTION (new), FALSE);
+	g_return_val_if_fail (NM_IS_CONNECTION (new_connection), FALSE);
 
 	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
 
-	new_settings = nm_connection_to_hash (new, NM_SETTING_HASH_FLAG_ALL);
-	g_assert (new_settings);
-	if (nm_connection_replace_settings (NM_CONNECTION (self), new_settings, error)) {
+	nm_utils_normalize_connection (new_connection, TRUE);
+	if (!nm_connection_verify (new_connection, error))
+		return FALSE;
+
+	/* Do nothing if there's nothing to update */
+	if (nm_connection_compare (NM_CONNECTION (self),
+	                           new_connection,
+	                           NM_SETTING_COMPARE_FLAG_EXACT)) {
+		return TRUE;
+	}
+
+	/* Disconnect the changed signal to ensure we don't set Unsaved when
+	 * it's not required.
+	 */
+	g_signal_handlers_block_by_func (self, G_CALLBACK (changed_cb), GUINT_TO_POINTER (TRUE));
+
+	if (nm_connection_replace_settings_from_connection (NM_CONNECTION (self),
+	                                                    new_connection,
+	                                                    error)) {
+		priv->nm_generated = FALSE;
+
 		/* Cache the just-updated system secrets in case something calls
 		 * nm_connection_clear_secrets() and clears them.
 		 */
@@ -405,14 +478,23 @@ nm_settings_connection_replace_settings (NMSettingsConnection *self,
 		if (priv->agent_secrets) {
 			hash = nm_connection_to_hash (priv->agent_secrets, NM_SETTING_HASH_FLAG_ONLY_SECRETS);
 			if (hash) {
-				success = nm_connection_update_secrets (NM_CONNECTION (self), NULL, hash, error);
+				(void) nm_connection_update_secrets (NM_CONNECTION (self), NULL, hash, NULL);
 				g_hash_table_destroy (hash);
 			}
 		}
 
 		nm_settings_connection_recheck_visibility (self);
+
+		/* Manually emit changed signal since we disconnected the handler, but
+		 * only update Unsaved if the caller wanted us to.
+		 */
+		changed_cb (self, GUINT_TO_POINTER (update_unsaved));
+
+		g_signal_emit (self, signals[UPDATED_BY_USER], 0);
 	}
-	g_hash_table_destroy (new_settings);
+
+	g_signal_handlers_unblock_by_func (self, G_CALLBACK (changed_cb), GUINT_TO_POINTER (TRUE));
+
 	return success;
 }
 
@@ -423,61 +505,63 @@ ignore_cb (NMSettingsConnection *connection,
 {
 }
 
-/* Replaces the settings in this connection with those in 'new'. If any changes
- * are made, commits them to permanent storage and to any other subsystems
- * watching this connection. Before returning, 'callback' is run with the given
- * 'user_data' along with any errors encountered.
+/* Replaces the settings in this connection with those in 'new_connection'. If
+ * any changes are made, commits them to permanent storage and to any other
+ * subsystems watching this connection. Before returning, 'callback' is run
+ * with the given 'user_data' along with any errors encountered.
  */
 void
 nm_settings_connection_replace_and_commit (NMSettingsConnection *self,
-                                           NMConnection *new,
+                                           NMConnection *new_connection,
                                            NMSettingsConnectionCommitFunc callback,
                                            gpointer user_data)
 {
 	GError *error = NULL;
 
-	g_return_if_fail (self != NULL);
 	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (self));
-	g_return_if_fail (new != NULL);
-	g_return_if_fail (NM_IS_CONNECTION (new));
+	g_return_if_fail (NM_IS_CONNECTION (new_connection));
 
-	if (!callback)
-		callback = ignore_cb;
-
-	/* Do nothing if there's nothing to update */
-	if (nm_connection_compare (NM_CONNECTION (self),
-	                           NM_CONNECTION (new),
-	                           NM_SETTING_COMPARE_FLAG_EXACT)) {
-		callback (self, NULL, user_data);
-		return;
-	}
-
-	if (nm_settings_connection_replace_settings (self, new, &error)) {
+	if (nm_settings_connection_replace_settings (self, new_connection, TRUE, &error)) {
 		nm_settings_connection_commit_changes (self, callback, user_data);
 	} else {
-		callback (self, error, user_data);
+		if (callback)
+			callback (self, error, user_data);
 		g_clear_error (&error);
 	}
 }
 
+static void
+commit_changes (NMSettingsConnection *self,
+                NMSettingsConnectionCommitFunc callback,
+                gpointer user_data)
+{
+	/* Subclasses only call this function if the save was successful, so at
+	 * this point the connection is synced to disk and no longer unsaved.
+	 */
+	set_unsaved (self, FALSE);
+
+	g_object_ref (self);
+	callback (self, NULL, user_data);
+	g_object_unref (self);
+}
+
 void
 nm_settings_connection_commit_changes (NMSettingsConnection *connection,
                                        NMSettingsConnectionCommitFunc callback,
                                        gpointer user_data)
 {
-	g_return_if_fail (connection != NULL);
 	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (connection));
-	g_return_if_fail (callback != NULL);
 
 	if (NM_SETTINGS_CONNECTION_GET_CLASS (connection)->commit_changes) {
 		NM_SETTINGS_CONNECTION_GET_CLASS (connection)->commit_changes (connection,
-		                                                               callback,
+		                                                               callback ? callback : ignore_cb,
 		                                                               user_data);
 	} else {
 		GError *error = g_error_new (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_INTERNAL_ERROR,
 		                             "%s: %s:%d commit_changes() unimplemented", __func__, __FILE__, __LINE__);
-		callback (connection, error, user_data);
+		if (callback)
+			callback (connection, error, user_data);
 		g_error_free (error);
 	}
 }
@@ -487,35 +571,23 @@ nm_settings_connection_delete (NMSettingsConnection *connection,
                                NMSettingsConnectionDeleteFunc callback,
                                gpointer user_data)
 {
-	g_return_if_fail (connection != NULL);
 	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (connection));
-	g_return_if_fail (callback != NULL);
 
 	if (NM_SETTINGS_CONNECTION_GET_CLASS (connection)->delete) {
 		NM_SETTINGS_CONNECTION_GET_CLASS (connection)->delete (connection,
-		                                                       callback,
+		                                                       callback ? callback : ignore_cb,
 		                                                       user_data);
 	} else {
 		GError *error = g_error_new (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_INTERNAL_ERROR,
 		                             "%s: %s:%d delete() unimplemented", __func__, __FILE__, __LINE__);
-		callback (connection, error, user_data);
+		if (callback)
+			callback (connection, error, user_data);
 		g_error_free (error);
 	}
 }
 
 static void
-commit_changes (NMSettingsConnection *connection,
-                NMSettingsConnectionCommitFunc callback,
-                gpointer user_data)
-{
-	g_object_ref (connection);
-	g_signal_emit (connection, signals[UPDATED], 0);
-	callback (connection, NULL, user_data);
-	g_object_unref (connection);
-}
-
-static void
 remove_entry_from_db (NMSettingsConnection *connection, const char* db_name)
 {
 	GKeyFile *key_file;
@@ -565,7 +637,7 @@ do_delete (NMSettingsConnection *connection,
 	/* Tell agents to remove secrets for this connection */
 	for_agents = nm_connection_duplicate (NM_CONNECTION (connection));
 	nm_connection_clear_secrets (for_agents);
-	nm_agent_manager_delete_secrets (priv->agent_mgr, for_agents, FALSE, 0);
+	nm_agent_manager_delete_secrets (priv->agent_mgr, for_agents);
 	g_object_unref (for_agents);
 
 	/* Remove timestamp from timestamps database file */
@@ -574,7 +646,10 @@ do_delete (NMSettingsConnection *connection,
 	/* Remove connection from seen-bssids database file */
 	remove_entry_from_db (connection, "seen-bssids");
 
+	nm_settings_connection_signal_remove (connection);
+
 	callback (connection, NULL, user_data);
+
 	g_object_unref (connection);
 }
 
@@ -792,13 +867,11 @@ agent_secrets_done_cb (NMAgentManager *manager,
 /**
  * nm_settings_connection_get_secrets:
  * @connection: the #NMSettingsConnection
- * @filter_by_uid: if TRUE, only request secrets from agents registered by the
- * same UID as @uid.
- * @uid: when @filter_by_uid is TRUE, only request secrets from agents belonging
- * to this UID
+ * @subject: the #NMAuthSubject originating the request
  * @setting_name: the setting to return secrets for
  * @flags: flags to modify the secrets request
- * @hint: the name of a key in @setting_name for which a secret may be required
+ * @hints: key names in @setting_name for which secrets may be required, or some
+ *   other information about the request
  * @callback: the function to call with returned secrets
  * @callback_data: user data to pass to @callback
  *
@@ -809,11 +882,10 @@ agent_secrets_done_cb (NMAgentManager *manager,
  **/
 guint32 
 nm_settings_connection_get_secrets (NMSettingsConnection *self,
-                                    gboolean filter_by_uid,
-                                    gulong uid,
+                                    NMAuthSubject *subject,
                                     const char *setting_name,
                                     NMSettingsGetSecretsFlags flags,
-                                    const char *hint,
+                                    const char **hints,
                                     NMSettingsConnectionSecretsFunc callback,
                                     gpointer callback_data,
                                     GError **error)
@@ -821,6 +893,7 @@ nm_settings_connection_get_secrets (NMSettingsConnection *self,
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
 	GHashTable *existing_secrets;
 	guint32 call_id = 0;
+	char *joined_hints = NULL;
 
 	/* Use priv->secrets to work around the fact that nm_connection_clear_secrets()
 	 * will clear secrets on this object's settings.
@@ -843,12 +916,11 @@ nm_settings_connection_get_secrets (NMSettingsConnection *self,
 	existing_secrets = nm_connection_to_hash (priv->system_secrets, NM_SETTING_HASH_FLAG_ONLY_SECRETS);
 	call_id = nm_agent_manager_get_secrets (priv->agent_mgr,
 	                                        NM_CONNECTION (self),
-	                                        filter_by_uid,
-	                                        uid,
+	                                        subject,
 	                                        existing_secrets,
 	                                        setting_name,
 	                                        flags,
-	                                        hint,
+	                                        hints,
 	                                        agent_secrets_done_cb,
 	                                        self,
 	                                        callback,
@@ -856,12 +928,17 @@ nm_settings_connection_get_secrets (NMSettingsConnection *self,
 	if (existing_secrets)
 		g_hash_table_unref (existing_secrets);
 
-	nm_log_dbg (LOGD_SETTINGS, "(%s/%s:%u) secrets requested flags 0x%X hint '%s'",
-	            nm_connection_get_uuid (NM_CONNECTION (self)),
-	            setting_name,
-	            call_id,
-	            flags,
-	            hint);
+	if (nm_logging_enabled (LOGL_DEBUG, LOGD_SETTINGS)) {
+		if (hints)
+			joined_hints = g_strjoinv (",", (char **) hints);
+		nm_log_dbg (LOGD_SETTINGS, "(%s/%s:%u) secrets requested flags 0x%X hints '%s'",
+		            nm_connection_get_uuid (NM_CONNECTION (self)),
+		            setting_name,
+		            call_id,
+		            flags,
+		            joined_hints ? joined_hints : "(none)");
+		g_free (joined_hints);
+	}
 
 	return call_id;
 }
@@ -884,7 +961,7 @@ nm_settings_connection_cancel_secrets (NMSettingsConnection *self,
 
 typedef void (*AuthCallback) (NMSettingsConnection *connection, 
                               DBusGMethodInvocation *context,
-                              gulong sender_uid,
+                              NMAuthSubject *subject,
                               GError *error,
                               gpointer data);
 
@@ -901,115 +978,114 @@ pk_auth_cb (NMAuthChain *chain,
 	const char *perm;
 	AuthCallback callback;
 	gpointer callback_data;
-	gulong sender_uid;
+	NMAuthSubject *subject;
 
 	priv->pending_auths = g_slist_remove (priv->pending_auths, chain);
 
+	perm = nm_auth_chain_get_data (chain, "perm");
+	g_assert (perm);
+	result = nm_auth_chain_get_result (chain, perm);
+
 	/* If our NMSettingsConnection is already gone, do nothing */
 	if (chain_error) {
 		error = g_error_new (NM_SETTINGS_ERROR,
 		                     NM_SETTINGS_ERROR_GENERAL,
 		                     "Error checking authorization: %s",
 		                     chain_error->message ? chain_error->message : "(unknown)");
-	} else {
-		perm = nm_auth_chain_get_data (chain, "perm");
-		g_assert (perm);
-		result = nm_auth_chain_get_result (chain, perm);
-
-		/* Caller didn't successfully authenticate */
-		if (result != NM_AUTH_CALL_RESULT_YES) {
-			error = g_error_new_literal (NM_SETTINGS_ERROR,
-			                             NM_SETTINGS_ERROR_NOT_PRIVILEGED,
-			                             "Insufficient privileges.");
-		}
+	} else if (result != NM_AUTH_CALL_RESULT_YES) {
+		error = g_error_new_literal (NM_SETTINGS_ERROR,
+		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
+		                             "Insufficient privileges.");
 	}
 
 	callback = nm_auth_chain_get_data (chain, "callback");
 	callback_data = nm_auth_chain_get_data (chain, "callback-data");
-	sender_uid = nm_auth_chain_get_data_ulong (chain, "sender-uid");
-	callback (self, context, sender_uid, error, callback_data);
+	subject = nm_auth_chain_get_data (chain, "subject");
+	callback (self, context, subject, error, callback_data);
 
 	g_clear_error (&error);
 	nm_auth_chain_unref (chain);
 }
 
-static gboolean
-check_user_in_acl (NMConnection *connection,
-                   DBusGMethodInvocation *context,
-                   NMDBusManager *dbus_mgr,
-                   NMSessionMonitor *session_monitor,
-                   gulong *out_sender_uid,
-                   GError **error)
-{
-	gulong sender_uid = G_MAXULONG;
-	char *error_desc = NULL;
-
-	g_return_val_if_fail (connection != NULL, FALSE);
-	g_return_val_if_fail (context != NULL, FALSE);
-	g_return_val_if_fail (session_monitor != NULL, FALSE);
-
-	/* Get the caller's UID */
-	if (!nm_auth_get_caller_uid (context, dbus_mgr, &sender_uid, &error_desc)) {
-		g_set_error_literal (error,
-		                     NM_SETTINGS_ERROR,
-		                     NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                     error_desc);
-		g_free (error_desc);
-		return FALSE;
-	}
+/**
+ * _new_auth_subject:
+ * @context: the D-Bus method invocation context
+ * @error: on failure, a #GError
+ *
+ * Creates an NMAuthSubject for the caller.
+ *
+ * Returns: the #NMAuthSubject on success, or %NULL on failure and sets @error
+ */
+static NMAuthSubject *
+_new_auth_subject (DBusGMethodInvocation *context, GError **error)
+{
+	NMAuthSubject *subject;
 
-	/* Make sure the UID can view this connection */
-	if (!nm_auth_uid_in_acl (connection, session_monitor, sender_uid, &error_desc)) {
+	subject = nm_auth_subject_new_from_context (context);
+	if (!subject) {
 		g_set_error_literal (error,
 		                     NM_SETTINGS_ERROR,
 		                     NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                     error_desc);
-		g_free (error_desc);
-		return FALSE;
+		                     "Unable to determine UID of request.");
 	}
 
-	if (out_sender_uid)
-		*out_sender_uid = sender_uid;
-	return TRUE;
+	return subject;
 }
 
 static void
 auth_start (NMSettingsConnection *self,
             DBusGMethodInvocation *context,
+            NMAuthSubject *subject,
             const char *check_permission,
             AuthCallback callback,
             gpointer callback_data)
 {
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
 	NMAuthChain *chain;
-	gulong sender_uid = G_MAXULONG;
 	GError *error = NULL;
+	char *error_desc = NULL;
 
-	if (!check_user_in_acl (NM_CONNECTION (self),
-	                        context,
-	                        priv->dbus_mgr,
-	                        priv->session_monitor,
-	                        &sender_uid,
-	                        &error)) {
-		callback (self, context, G_MAXULONG, error, callback_data);
+	g_return_if_fail (context != NULL);
+	g_return_if_fail (NM_IS_AUTH_SUBJECT (subject));
+
+	/* Ensure the caller can view this connection */
+	if (!nm_auth_uid_in_acl (NM_CONNECTION (self),
+	                         priv->session_monitor,
+	                         nm_auth_subject_get_uid (subject),
+	                         &error_desc)) {
+		error = g_error_new_literal (NM_SETTINGS_ERROR,
+		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
+		                             error_desc);
+		g_free (error_desc);
+
+		callback (self, context, subject, error, callback_data);
 		g_clear_error (&error);
 		return;
 	}
 
-	if (check_permission) {
-		chain = nm_auth_chain_new (context, NULL, pk_auth_cb, self);
-		g_assert (chain);
-		nm_auth_chain_set_data (chain, "perm", (gpointer) check_permission, NULL);
-		nm_auth_chain_set_data (chain, "callback", callback, NULL);
-		nm_auth_chain_set_data (chain, "callback-data", callback_data, NULL);
-		nm_auth_chain_set_data_ulong (chain, "sender-uid", sender_uid);
-
-		nm_auth_chain_add_call (chain, check_permission, TRUE);
-		priv->pending_auths = g_slist_append (priv->pending_auths, chain);
-	} else {
+	if (!check_permission) {
 		/* Don't need polkit auth, automatic success */
-		callback (self, context, sender_uid, NULL, callback_data);
+		callback (self, context, subject, NULL, callback_data);
+		return;
+	}
+
+	chain = nm_auth_chain_new_subject (subject, context, pk_auth_cb, self);
+	if (!chain) {
+		g_set_error_literal (&error,
+		                     NM_SETTINGS_ERROR,
+		                     NM_SETTINGS_ERROR_PERMISSION_DENIED,
+		                     "Unable to authenticate the request.");
+		callback (self, context, subject, error, callback_data);
+		g_clear_error (&error);
+		return;
 	}
+
+	priv->pending_auths = g_slist_append (priv->pending_auths, chain);
+	nm_auth_chain_set_data (chain, "perm", (gpointer) check_permission, NULL);
+	nm_auth_chain_set_data (chain, "callback", callback, NULL);
+	nm_auth_chain_set_data (chain, "callback-data", callback_data, NULL);
+	nm_auth_chain_set_data (chain, "subject", g_object_ref (subject), g_object_unref);
+	nm_auth_chain_add_call (chain, check_permission, TRUE);
 }
 
 /**** DBus method handlers ************************************/
@@ -1019,7 +1095,6 @@ check_writable (NMConnection *connection, GError **error)
 {
 	NMSettingConnection *s_con;
 
-	g_return_val_if_fail (connection != NULL, FALSE);
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
 
 	s_con = nm_connection_get_setting_connection (connection);
@@ -1049,7 +1124,7 @@ check_writable (NMConnection *connection, GError **error)
 static void
 get_settings_auth_cb (NMSettingsConnection *self, 
                       DBusGMethodInvocation *context,
-                      gulong sender_uid,
+                      NMAuthSubject *subject,
                       GError *error,
                       gpointer data)
 {
@@ -1086,7 +1161,19 @@ get_settings_auth_cb (NMSettingsConnection *self,
 		s_wifi = nm_connection_get_setting_wireless (NM_CONNECTION (dupl_con));
 		if (bssid_list && s_wifi) {
 			g_object_set (s_wifi, NM_SETTING_WIRELESS_SEEN_BSSIDS, bssid_list, NULL);
-			nm_utils_slist_free (bssid_list, g_free);
+			g_slist_free_full (bssid_list, g_free);
+		}
+
+		/* 802-11-wireless.security property is deprecated. But we set it here so that
+		 * we don't disturb old clients that might expect it being properly set for
+		 * secured Wi-Fi connections.
+		 */
+		if (nm_connection_get_setting_wireless_security (NM_CONNECTION (dupl_con))) {
+			s_wifi = nm_connection_get_setting_wireless (NM_CONNECTION (dupl_con));
+			g_assert (s_wifi);
+			g_object_set (s_wifi,
+			              NM_SETTING_WIRELESS_SEC, NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+			              NULL);
 		}
 
 		/* Secrets should *never* be returned by the GetSettings method, they
@@ -1105,16 +1192,45 @@ static void
 impl_settings_connection_get_settings (NMSettingsConnection *self,
                                        DBusGMethodInvocation *context)
 {
-	auth_start (self, context, NULL, get_settings_auth_cb, NULL);
+	NMAuthSubject *subject;
+	GError *error = NULL;
+
+	subject = _new_auth_subject (context, &error);
+	if (subject) {
+		auth_start (self, context, subject, NULL, get_settings_auth_cb, NULL);
+		g_object_unref (subject);
+	} else {
+		dbus_g_method_return_error (context, error);
+		g_error_free (error);
+	}
 }
 
 typedef struct {
 	DBusGMethodInvocation *context;
 	NMAgentManager *agent_mgr;
-	gulong sender_uid;
+	NMAuthSubject *subject;
+	NMConnection *new_settings;
+	gboolean save_to_disk;
 } UpdateInfo;
 
 static void
+update_complete (NMSettingsConnection *self,
+                 UpdateInfo *info,
+                 GError *error)
+{
+	if (error)
+		dbus_g_method_return_error (info->context, error);
+	else
+		dbus_g_method_return (info->context);
+
+	g_clear_object (&info->subject);
+	g_clear_object (&info->agent_mgr);
+	g_clear_object (&info->new_settings);
+	memset (info, 0, sizeof (*info));
+	g_free (info);
+}
+
+static void
 con_update_cb (NMSettingsConnection *self,
                GError *error,
                gpointer user_data)
@@ -1122,9 +1238,7 @@ con_update_cb (NMSettingsConnection *self,
 	UpdateInfo *info = user_data;
 	NMConnection *for_agent;
 
-	if (error)
-		dbus_g_method_return_error (info->context, error);
-	else {
+	if (!error) {
 		/* Dupe the connection so we can clear out non-agent-owned secrets,
 		 * as agent-owned secrets are the only ones we send back be saved.
 		 * Only send secrets to agents of the same UID that called update too.
@@ -1133,54 +1247,52 @@ con_update_cb (NMSettingsConnection *self,
 		nm_connection_clear_secrets_with_flags (for_agent,
 		                                        secrets_filter_cb,
 		                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
-		nm_agent_manager_save_secrets (info->agent_mgr, for_agent, TRUE, info->sender_uid);
+		nm_agent_manager_save_secrets (info->agent_mgr, for_agent, info->subject);
 		g_object_unref (for_agent);
-
-		dbus_g_method_return (info->context);
 	}
 
-	g_object_unref (info->agent_mgr);
-	memset (info, 0, sizeof (*info));
-	g_free (info);
+	update_complete (self, info, error);
 }
 
 static void
 update_auth_cb (NMSettingsConnection *self,
                 DBusGMethodInvocation *context,
-                gulong sender_uid,
+                NMAuthSubject *subject,
                 GError *error,
                 gpointer data)
 {
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-	NMConnection *new_settings = data;
-	UpdateInfo *info;
+	UpdateInfo *info = data;
+	GError *local = NULL;
 
-	if (error)
-		dbus_g_method_return_error (context, error);
-	else {
-		info = g_malloc0 (sizeof (*info));
-		info->context = context;
-		info->agent_mgr = g_object_ref (priv->agent_mgr);
-		info->sender_uid = sender_uid;
-
-		/* Cache the new secrets from the agent, as stuff like inotify-triggered
-		 * changes to connection's backing config files will blow them away if
-		 * they're in the main connection.
-		 */
-		update_agent_secrets_cache (self, new_settings);
+	if (error) {
+		update_complete (self, info, error);
+		return;
+	}
 
-		/* Update and commit our settings. */
+	/* Cache the new secrets from the agent, as stuff like inotify-triggered
+	 * changes to connection's backing config files will blow them away if
+	 * they're in the main connection.
+	 */
+	update_agent_secrets_cache (self, info->new_settings);
+
+	if (info->save_to_disk) {
 		nm_settings_connection_replace_and_commit (self,
-			                                       new_settings,
-			                                       con_update_cb,
-			                                       info);
+		                                           info->new_settings,
+		                                           con_update_cb,
+		                                           info);
+	} else {
+		/* Do nothing if there's nothing to update */
+		if (!nm_connection_compare (NM_CONNECTION (self), info->new_settings, NM_SETTING_COMPARE_FLAG_EXACT)) {
+			if (!nm_settings_connection_replace_settings (self, info->new_settings, TRUE, &local))
+				g_assert (local);
+		}
+		con_update_cb (self, local, info);
+		g_clear_error (&local);
 	}
-
-	g_object_unref (new_settings);
 }
 
 static const char *
-get_modify_permission_update (NMConnection *old, NMConnection *new)
+get_update_modify_permission (NMConnection *old, NMConnection *new)
 {
 	NMSettingConnection *s_con;
 	guint32 orig_num = 0, new_num = 0;
@@ -1206,54 +1318,103 @@ get_modify_permission_update (NMConnection *old, NMConnection *new)
 }
 
 static void
-impl_settings_connection_update (NMSettingsConnection *self,
-                                 GHashTable *new_settings,
-                                 DBusGMethodInvocation *context)
+impl_settings_connection_update_helper (NMSettingsConnection *self,
+                                        GHashTable *new_settings,
+                                        DBusGMethodInvocation *context,
+                                        gboolean save_to_disk)
 {
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-	NMConnection *tmp;
+	NMAuthSubject *subject = NULL;
+	NMConnection *tmp = NULL;
 	GError *error = NULL;
+	UpdateInfo *info;
+	const char *permission;
+	char *error_desc = NULL;
+
+	g_assert (new_settings != NULL || save_to_disk == TRUE);
 
 	/* If the connection is read-only, that has to be changed at the source of
 	 * the problem (ex a system settings plugin that can't write connections out)
 	 * instead of over D-Bus.
 	 */
-	if (!check_writable (NM_CONNECTION (self), &error)) {
-		dbus_g_method_return_error (context, error);
-		g_error_free (error);
-		return;
-	}
+	if (!check_writable (NM_CONNECTION (self), &error))
+		goto error;
 
 	/* Check if the settings are valid first */
-	tmp = nm_connection_new_from_hash (new_settings, &error);
-	if (!tmp) {
-		g_assert (error);
-		dbus_g_method_return_error (context, error);
-		g_error_free (error);
-		return;
+	if (new_settings) {
+		tmp = nm_connection_new_from_hash (new_settings, &error);
+		if (!tmp) {
+			g_assert (error);
+			goto error;
+		}
 	}
 
+	subject = _new_auth_subject (context, &error);
+	if (!subject)
+		goto error;
+
 	/* And that the new connection settings will be visible to the user
 	 * that's sending the update request.  You can't make a connection
 	 * invisible to yourself.
 	 */
-	if (!check_user_in_acl (tmp,
-	                        context,
-	                        priv->dbus_mgr,
-	                        priv->session_monitor,
-	                        NULL,
-	                        &error)) {
-		dbus_g_method_return_error (context, error);
-		g_clear_error (&error);
-		g_object_unref (tmp);
-		return;
+	if (!nm_auth_uid_in_acl (tmp ? tmp : NM_CONNECTION (self),
+	                         priv->session_monitor,
+	                         nm_auth_subject_get_uid (subject),
+	                         &error_desc)) {
+		error = g_error_new_literal (NM_SETTINGS_ERROR,
+		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
+		                             error_desc);
+		g_free (error_desc);
+		goto error;
 	}
 
-	auth_start (self,
-	            context,
-	            get_modify_permission_update (NM_CONNECTION (self), tmp),
-	            update_auth_cb,
-	            tmp);
+	info = g_malloc0 (sizeof (*info));
+	info->context = context;
+	info->agent_mgr = g_object_ref (priv->agent_mgr);
+	info->subject = subject;
+	info->save_to_disk = save_to_disk;
+	info->new_settings = tmp;
+
+	permission = get_update_modify_permission (NM_CONNECTION (self),
+	                                           tmp ? tmp : NM_CONNECTION (self));
+	auth_start (self, context, subject, permission, update_auth_cb, info);
+	return;
+
+error:
+	g_clear_object (&tmp);
+	g_clear_object (&subject);
+
+	dbus_g_method_return_error (context, error);
+	g_clear_error (&error);
+}
+
+static void
+impl_settings_connection_update (NMSettingsConnection *self,
+                                 GHashTable *new_settings,
+                                 DBusGMethodInvocation *context)
+{
+	g_assert (new_settings);
+	impl_settings_connection_update_helper (self, new_settings, context, TRUE);
+}
+
+static void
+impl_settings_connection_update_unsaved (NMSettingsConnection *self,
+                                         GHashTable *new_settings,
+                                         DBusGMethodInvocation *context)
+{
+	g_assert (new_settings);
+	impl_settings_connection_update_helper (self, new_settings, context, FALSE);
+}
+
+static void
+impl_settings_connection_save (NMSettingsConnection *self,
+                               DBusGMethodInvocation *context)
+{
+	/* Do nothing if the connection is already synced with disk */
+	if (NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->unsaved == TRUE)
+		impl_settings_connection_update_helper (self, NULL, context, TRUE);
+	else
+		dbus_g_method_return (context);
 }
 
 static void
@@ -1272,7 +1433,7 @@ con_delete_cb (NMSettingsConnection *connection,
 static void
 delete_auth_cb (NMSettingsConnection *self, 
                 DBusGMethodInvocation *context,
-                gulong sender_uid,
+                NMAuthSubject *subject,
                 GError *error,
                 gpointer data)
 {
@@ -1305,6 +1466,7 @@ static void
 impl_settings_connection_delete (NMSettingsConnection *self,
                                  DBusGMethodInvocation *context)
 {
+	NMAuthSubject *subject;
 	GError *error = NULL;
 	
 	if (!check_writable (NM_CONNECTION (self), &error)) {
@@ -1313,7 +1475,14 @@ impl_settings_connection_delete (NMSettingsConnection *self,
 		return;
 	}
 
-	auth_start (self, context, get_modify_permission_basic (self), delete_auth_cb, NULL);
+	subject = _new_auth_subject (context, &error);
+	if (subject) {
+		auth_start (self, context, subject, get_modify_permission_basic (self), delete_auth_cb, NULL);
+		g_object_unref (subject);
+	} else {
+		dbus_g_method_return_error (context, error);
+		g_error_free (error);
+	}
 }
 
 /**************************************************************/
@@ -1351,7 +1520,7 @@ dbus_get_agent_secrets_cb (NMSettingsConnection *self,
 static void
 dbus_secrets_auth_cb (NMSettingsConnection *self, 
                       DBusGMethodInvocation *context,
-                      gulong sender_uid,
+                      NMAuthSubject *subject,
                       GError *error,
                       gpointer user_data)
 {
@@ -1362,8 +1531,7 @@ dbus_secrets_auth_cb (NMSettingsConnection *self,
 
 	if (!error) {
 		call_id = nm_settings_connection_get_secrets (self,
-			                                          TRUE,
-			                                          sender_uid,
+			                                          subject,
 			                                          setting_name,
 			                                          NM_SETTINGS_GET_SECRETS_FLAG_USER_REQUESTED,
 			                                          NULL,
@@ -1389,11 +1557,22 @@ impl_settings_connection_get_secrets (NMSettingsConnection *self,
                                       const gchar *setting_name,
                                       DBusGMethodInvocation *context)
 {
-	auth_start (self,
-	            context,
-	            get_modify_permission_basic (self),
-	            dbus_secrets_auth_cb,
-	            g_strdup (setting_name));
+	NMAuthSubject *subject;
+	GError *error = NULL;
+
+	subject = _new_auth_subject (context, &error);
+	if (subject) {
+		auth_start (self,
+		            context,
+		            subject,
+		            get_modify_permission_basic (self),
+		            dbus_secrets_auth_cb,
+		            g_strdup (setting_name));
+		g_object_unref (subject);
+	} else {
+		dbus_g_method_return_error (context, error);
+		g_error_free (error);
+	}
 }
 
 /**************************************************************/
@@ -1407,9 +1586,17 @@ nm_settings_connection_signal_remove (NMSettingsConnection *self)
 	/* And unregistered last to ensure the removed signal goes out before
 	 * we take the connection off the bus.
 	 */
-	g_signal_emit_by_name (self, "unregister");
+	nm_dbus_manager_unregister_object (nm_dbus_manager_get (), G_OBJECT (self));
+}
+
+gboolean
+nm_settings_connection_get_unsaved (NMSettingsConnection *self)
+{
+	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->unsaved;
 }
 
+/**************************************************************/
+
 /**
  * nm_settings_connection_get_timestamp:
  * @connection: the #NMSettingsConnection
@@ -1573,7 +1760,6 @@ nm_settings_connection_get_seen_bssids (NMSettingsConnection *connection)
 	char *bssid_str;
 	GSList *bssid_list = NULL;
 
-	g_return_val_if_fail (connection != NULL, 0);
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (connection), NULL);
 
 	g_hash_table_iter_init (&iter, priv->seen_bssids);
@@ -1594,7 +1780,6 @@ gboolean
 nm_settings_connection_has_seen_bssid (NMSettingsConnection *connection,
                                        const struct ether_addr *bssid)
 {
-	g_return_val_if_fail (connection != NULL, FALSE);
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (connection), FALSE);
 	g_return_val_if_fail (bssid != NULL, FALSE);
 
@@ -1630,7 +1815,6 @@ nm_settings_connection_add_seen_bssid (NMSettingsConnection *connection,
 
 	/* Add the new BSSID; let the hash take ownership of the allocated BSSID string */
 	bssid_str = nm_utils_hwaddr_ntoa (seen_bssid, ARPHRD_ETHER);
-	g_return_if_fail (bssid_str != NULL);
 	g_hash_table_insert (priv->seen_bssids, mac_dup (seen_bssid), bssid_str);
 
 	/* Build up a list of all the BSSIDs in string form */
@@ -1729,6 +1913,111 @@ nm_settings_connection_read_and_fill_seen_bssids (NMSettingsConnection *connecti
 	}
 }
 
+#define AUTOCONNECT_RETRIES_DEFAULT 4
+#define AUTOCONNECT_RESET_RETRIES_TIMER 300
+
+int
+nm_settings_connection_get_autoconnect_retries (NMSettingsConnection *connection)
+{
+	return NM_SETTINGS_CONNECTION_GET_PRIVATE (connection)->autoconnect_retries;
+}
+
+void
+nm_settings_connection_set_autoconnect_retries (NMSettingsConnection *connection,
+                                                int retries)
+{
+	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (connection);
+
+	priv->autoconnect_retries = retries;
+	if (retries)
+		priv->autoconnect_retry_time = 0;
+	else
+		priv->autoconnect_retry_time = nm_utils_get_monotonic_timestamp_s () + AUTOCONNECT_RESET_RETRIES_TIMER;
+}
+
+void
+nm_settings_connection_reset_autoconnect_retries (NMSettingsConnection *connection)
+{
+	nm_settings_connection_set_autoconnect_retries (connection, AUTOCONNECT_RETRIES_DEFAULT);
+}
+
+gint32
+nm_settings_connection_get_autoconnect_retry_time (NMSettingsConnection *connection)
+{
+	return NM_SETTINGS_CONNECTION_GET_PRIVATE (connection)->autoconnect_retry_time;
+}
+
+NMDeviceStateReason
+nm_settings_connection_get_autoconnect_blocked_reason (NMSettingsConnection *connection)
+{
+	return NM_SETTINGS_CONNECTION_GET_PRIVATE (connection)->autoconnect_blocked_reason;
+}
+
+void
+nm_settings_connection_set_autoconnect_blocked_reason (NMSettingsConnection *connection,
+                                                       NMDeviceStateReason reason)
+{
+	NM_SETTINGS_CONNECTION_GET_PRIVATE (connection)->autoconnect_blocked_reason = reason;
+}
+
+gboolean
+nm_settings_connection_can_autoconnect (NMSettingsConnection *connection)
+{
+	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (connection);
+	NMSettingConnection *s_con;
+	const char *permission;
+
+	if (   !priv->visible
+	    || priv->autoconnect_retries == 0
+	    || priv->autoconnect_blocked_reason != NM_DEVICE_STATE_REASON_NONE)
+		return FALSE;
+
+	s_con = nm_connection_get_setting_connection (NM_CONNECTION (connection));
+	if (!nm_setting_connection_get_autoconnect (s_con))
+		return FALSE;
+
+	permission = nm_utils_get_shared_wifi_permission (NM_CONNECTION (connection));
+	if (permission) {
+		if (nm_settings_connection_check_permission (connection, permission) == FALSE)
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+/**
+ * nm_settings_connection_get_nm_generated:
+ * @connection: an #NMSettingsConnection
+ *
+ * Gets the "nm-generated" flag on @connection.
+ *
+ * A connection is "nm-generated" if it was generated by
+ * nm_device_generate_connection() and then assumed by #NMManager, and
+ * it has not been modified or saved by the user since then. In other
+ * words, an "nm-generated" connection reflects state that is entirely
+ * external to NetworkManager.
+ */
+gboolean
+nm_settings_connection_get_nm_generated (NMSettingsConnection *connection)
+{
+	return NM_SETTINGS_CONNECTION_GET_PRIVATE (connection)->nm_generated;
+}
+
+/**
+ * nm_settings_connection_set_nm_generated:
+ * @connection: an #NMSettingsConnection
+ *
+ * Sets the "nm-generated" flag on @connection; see
+ * nm_settings_connection_get_nm_generated().
+ */
+void
+nm_settings_connection_set_nm_generated (NMSettingsConnection *connection)
+{
+	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (connection);
+
+	priv->nm_generated = TRUE;
+}
+
 /**************************************************************/
 
 static void
@@ -1736,8 +2025,6 @@ nm_settings_connection_init (NMSettingsConnection *self)
 {
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
 
-	priv->dbus_mgr = nm_dbus_manager_get ();
-
 	priv->visible = FALSE;
 
 	priv->session_monitor = nm_session_monitor_get ();
@@ -1750,7 +2037,11 @@ nm_settings_connection_init (NMSettingsConnection *self)
 
 	priv->seen_bssids = g_hash_table_new_full (mac_hash, mac_equal, g_free, g_free);
 
-	g_signal_connect (self, "secrets-cleared", G_CALLBACK (secrets_cleared_cb), NULL);
+	priv->autoconnect_retries = AUTOCONNECT_RETRIES_DEFAULT;
+	priv->autoconnect_blocked_reason = NM_DEVICE_STATE_REASON_NONE;
+
+	g_signal_connect (self, NM_CONNECTION_SECRETS_CLEARED, G_CALLBACK (secrets_cleared_cb), NULL);
+	g_signal_connect (self, NM_CONNECTION_CHANGED, G_CALLBACK (changed_cb), GUINT_TO_POINTER (TRUE));
 }
 
 static void
@@ -1764,6 +2055,11 @@ dispose (GObject *object)
 		goto out;
 	priv->disposed = TRUE;
 
+	if (priv->updated_idle_id) {
+		g_source_remove (priv->updated_idle_id);
+		priv->updated_idle_id = 0;
+	}
+
 	if (priv->system_secrets)
 		g_object_unref (priv->system_secrets);
 	if (priv->agent_secrets)
@@ -1786,9 +2082,7 @@ dispose (GObject *object)
 
 	if (priv->session_changed_id)
 		g_signal_handler_disconnect (priv->session_monitor, priv->session_changed_id);
-	g_object_unref (priv->session_monitor);
 	g_object_unref (priv->agent_mgr);
-	g_object_unref (priv->dbus_mgr);
 
 out:
 	G_OBJECT_CLASS (nm_settings_connection_parent_class)->dispose (object);
@@ -1798,9 +2092,14 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
+	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (object);
+
 	switch (prop_id) {
 	case PROP_VISIBLE:
-		g_value_set_boolean (value, NM_SETTINGS_CONNECTION_GET_PRIVATE (object)->visible);
+		g_value_set_boolean (value, priv->visible);
+		break;
+	case PROP_UNSAVED:
+		g_value_set_boolean (value, priv->unsaved);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -1840,7 +2139,19 @@ nm_settings_connection_class_init (NMSettingsConnectionClass *class)
 		                       FALSE,
 		                       G_PARAM_READABLE));
 
+	g_object_class_install_property
+		(object_class, PROP_UNSAVED,
+		 g_param_spec_boolean (NM_SETTINGS_CONNECTION_UNSAVED,
+		                       "Unsaved",
+		                       "TRUE when the connection has not yet been saved "
+		                       "to permanent storage (eg disk) or when it "
+		                       "has been changed but not yet saved.",
+		                       FALSE,
+		                       G_PARAM_READABLE));
+
 	/* Signals */
+
+	/* Emitted when the connection is changed for any reason */
 	signals[UPDATED] = 
 		g_signal_new (NM_SETTINGS_CONNECTION_UPDATED,
 		              G_TYPE_FROM_CLASS (class),
@@ -1850,18 +2161,17 @@ nm_settings_connection_class_init (NMSettingsConnectionClass *class)
 		              g_cclosure_marshal_VOID__VOID,
 		              G_TYPE_NONE, 0);
 
-	signals[REMOVED] = 
-		g_signal_new (NM_SETTINGS_CONNECTION_REMOVED,
+	/* Emitted when connection is changed from D-Bus */
+	signals[UPDATED_BY_USER] =
+		g_signal_new (NM_SETTINGS_CONNECTION_UPDATED_BY_USER,
 		              G_TYPE_FROM_CLASS (class),
 		              G_SIGNAL_RUN_FIRST,
-		              0,
-		              NULL, NULL,
+		              0, NULL, NULL,
 		              g_cclosure_marshal_VOID__VOID,
 		              G_TYPE_NONE, 0);
 
-	/* Not exported */
-	signals[UNREGISTER] = 
-		g_signal_new ("unregister",
+	signals[REMOVED] = 
+		g_signal_new (NM_SETTINGS_CONNECTION_REMOVED,
 		              G_TYPE_FROM_CLASS (class),
 		              G_SIGNAL_RUN_FIRST,
 		              0,
@@ -1869,6 +2179,7 @@ nm_settings_connection_class_init (NMSettingsConnectionClass *class)
 		              g_cclosure_marshal_VOID__VOID,
 		              G_TYPE_NONE, 0);
 
-	dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (class),
-	                                 &dbus_glib_nm_settings_connection_object_info);
+	nm_dbus_manager_register_exported_type (nm_dbus_manager_get (),
+	                                        G_TYPE_FROM_CLASS (class),
+	                                        &dbus_glib_nm_settings_connection_object_info);
 }