summary refs log tree commit diff
path: root/src/settings/nm-agent-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/settings/nm-agent-manager.c')
-rw-r--r--src/settings/nm-agent-manager.c1388
1 files changed, 690 insertions, 698 deletions
diff --git a/src/settings/nm-agent-manager.c b/src/settings/nm-agent-manager.c
index 8d9252a5..ee1c6202 100644
--- a/src/settings/nm-agent-manager.c
+++ b/src/settings/nm-agent-manager.c
@@ -23,34 +23,85 @@
 #include <string.h>
 #include <pwd.h>
 
-#include <glib.h>
-#include <dbus/dbus-glib.h>
-#include <dbus/dbus-glib-lowlevel.h>
-
+#include "nm-default.h"
 #include "nm-dbus-interface.h"
-#include "nm-logging.h"
 #include "nm-agent-manager.h"
 #include "nm-secret-agent.h"
 #include "nm-auth-utils.h"
-#include "nm-dbus-glib-types.h"
-#include "nm-auth-utils.h"
 #include "nm-setting-vpn.h"
-#include "nm-setting-connection.h"
 #include "nm-enum-types.h"
 #include "nm-auth-manager.h"
-#include "nm-dbus-manager.h"
+#include "nm-bus-manager.h"
 #include "nm-session-monitor.h"
 #include "nm-simple-connection.h"
 #include "NetworkManagerUtils.h"
+#include "nm-core-internal.h"
+
+#include "nmdbus-agent-manager.h"
+
+NM_DEFINE_SINGLETON_INSTANCE (NMAgentManager);
+
+#define _NMLOG_PREFIX_NAME    "agent-manager"
+#define _NMLOG_DOMAIN         LOGD_AGENTS
+#define _NMLOG(level, agent, ...) \
+    G_STMT_START { \
+        if (nm_logging_enabled ((level), (_NMLOG_DOMAIN))) { \
+            char __prefix1[32]; \
+            char __prefix2[128]; \
+            NMSecretAgent *__agent = (agent); \
+            \
+            if (!(self)) \
+                g_snprintf (__prefix1, sizeof (__prefix1), "%s%s", ""_NMLOG_PREFIX_NAME"", "[]"); \
+            else if ((self) != singleton_instance) \
+                g_snprintf (__prefix1, sizeof (__prefix1), "%s[%p]", ""_NMLOG_PREFIX_NAME"", (self)); \
+            else \
+                g_strlcpy (__prefix1, _NMLOG_PREFIX_NAME, sizeof (__prefix1)); \
+            if (__agent) { \
+                g_snprintf (__prefix2, sizeof (__prefix2), \
+                            ": req[%p, %s]", \
+                            __agent, \
+                            nm_secret_agent_get_description (__agent)); \
+            } else \
+                __prefix2[0] = '\0'; \
+            _nm_log ((level), (_NMLOG_DOMAIN), 0, \
+                     "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+                     __prefix1, __prefix2 _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
+#define LOG_REQ_FMT          "[%p/%s%s%s%s%s%s]"
+#define LOG_REQ_ARG(req) \
+	(req), \
+	NM_PRINT_FMT_QUOTE_STRING ((req)->detail), \
+	NM_PRINT_FMT_QUOTED (((req)->request_type == REQUEST_TYPE_CON_GET) && (req)->con.get.setting_name, \
+	                     "/\"", (req)->con.get.setting_name, "\"", \
+	                     ((req)->request_type == REQUEST_TYPE_CON_GET ? "/(none)" : _request_type_to_string ((req)->request_type, FALSE)))
+
+typedef enum {
+	REQUEST_TYPE_INVALID,
+	REQUEST_TYPE_CON_GET,
+	REQUEST_TYPE_CON_SAVE,
+	REQUEST_TYPE_CON_DEL,
+} RequestType;
+
+static const char *
+_request_type_to_string (RequestType request_type, gboolean verbose)
+{
+	switch (request_type) {
+	case REQUEST_TYPE_CON_GET:  return verbose ? "getting"  : "get";
+	case REQUEST_TYPE_CON_SAVE: return verbose ? "saving"   : "sav";
+	case REQUEST_TYPE_CON_DEL:  return verbose ? "deleting" : "del";
+	default: return "??";
+	}
+}
 
-G_DEFINE_TYPE (NMAgentManager, nm_agent_manager, G_TYPE_OBJECT)
+G_DEFINE_TYPE (NMAgentManager, nm_agent_manager, NM_TYPE_EXPORTED_OBJECT)
 
 #define NM_AGENT_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
                                          NM_TYPE_AGENT_MANAGER, \
                                          NMAgentManagerPrivate))
 
 typedef struct {
-	NMDBusManager *dbus_mgr;
 	NMAuthManager *auth_mgr;
 
 	/* Auth chains for checking agent permissions */
@@ -72,7 +123,7 @@ enum {
 static guint signals[LAST_SIGNAL] = { 0 };
 
 
-typedef struct _Request Request;
+typedef struct _NMAgentManagerCallId Request;
 
 static void request_add_agent (Request *req, NMSecretAgent *agent);
 
@@ -80,19 +131,11 @@ static void request_remove_agent (Request *req, NMSecretAgent *agent, GSList **p
 
 static void request_next_agent (Request *req);
 
-static void impl_agent_manager_register (NMAgentManager *self,
-                                         const char *identifier,
-                                         DBusGMethodInvocation *context);
-
-static void impl_agent_manager_register_with_capabilities (NMAgentManager *self,
-                                                           const char *identifier,
-                                                           NMSecretAgentCapabilities capabilities,
-                                                           DBusGMethodInvocation *context);
+static void _con_get_request_start (Request *req);
+static void _con_save_request_start (Request *req);
+static void _con_del_request_start (Request *req);
 
-static void impl_agent_manager_unregister (NMAgentManager *self,
-                                           DBusGMethodInvocation *context);
-
-#include "nm-agent-manager-glue.h"
+static gboolean _con_get_try_complete_early (Request *req);
 
 /*************************************************************/
 
@@ -112,12 +155,11 @@ remove_agent (NMAgentManager *self, const char *owner)
 	if (!agent)
 		return FALSE;
 
-	nm_log_dbg (LOGD_AGENTS, "(%s) agent unregistered or disappeared",
-	            nm_secret_agent_get_description (agent));
+	_LOGD (agent, "agent unregistered or disappeared");
 
 	/* Remove this agent from any in-progress secrets requests */
 	g_hash_table_iter_init (&iter, priv->requests);
-	while (g_hash_table_iter_next (&iter, NULL, &data))
+	while (g_hash_table_iter_next (&iter, &data, NULL))
 		request_remove_agent ((Request *) data, agent, &pending_reqs);
 
 	/* We cannot call request_next_agent() from from within hash iterating loop,
@@ -132,6 +174,17 @@ remove_agent (NMAgentManager *self, const char *owner)
 	return TRUE;
 }
 
+/* Call this *after* calling request_next_agent() */
+static void
+maybe_remove_agent_on_error (NMSecretAgent *agent,
+                             GError *error)
+{
+	if (   g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED)
+	    || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_DISCONNECTED)
+	    || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER))
+		remove_agent (nm_agent_manager_get (), nm_secret_agent_get_dbus_owner (agent));
+}
+
 /*************************************************************/
 
 static gboolean
@@ -192,7 +245,7 @@ validate_identifier (const char *identifier, GError **error)
 static void
 agent_register_permissions_done (NMAuthChain *chain,
                                  GError *error,
-                                 DBusGMethodInvocation *context,
+                                 GDBusMethodInvocation *context,
                                  gpointer user_data)
 {
 	NMAgentManager *self = NM_AGENT_MANAGER (user_data);
@@ -213,8 +266,7 @@ agent_register_permissions_done (NMAuthChain *chain,
 		                     NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
 		                     "Failed to request agent permissions: (%d) %s",
 		                     error->code, error->message);
-		dbus_g_method_return_error (context, local);
-		g_error_free (local);
+		g_dbus_method_invocation_take_error (context, local);
 	} else {
 		agent = nm_auth_chain_steal_data (chain, "agent");
 		g_assert (agent);
@@ -229,16 +281,15 @@ agent_register_permissions_done (NMAuthChain *chain,
 
 		sender = nm_secret_agent_get_dbus_owner (agent);
 		g_hash_table_insert (priv->agents, g_strdup (sender), agent);
-		nm_log_dbg (LOGD_AGENTS, "(%s) agent registered",
-		            nm_secret_agent_get_description (agent));
-		dbus_g_method_return (context);
+		_LOGD (agent, "agent registered");
+		g_dbus_method_invocation_return_value (context, NULL);
 
 		/* Signal an agent was registered */
 		g_signal_emit (self, signals[AGENT_REGISTERED], 0, agent);
 
 		/* Add this agent to any in-progress secrets requests */
 		g_hash_table_iter_init (&iter, priv->requests);
-		while (g_hash_table_iter_next (&iter, NULL, (gpointer) &req))
+		while (g_hash_table_iter_next (&iter, (gpointer) &req, NULL))
 			request_add_agent (req, agent);
 	}
 
@@ -264,15 +315,23 @@ find_agent_by_identifier_and_uid (NMAgentManager *self,
 }
 
 static void
+agent_disconnected_cb (NMSecretAgent *agent, gpointer user_data)
+{
+	/* The agent quit, so remove it and let interested clients know */
+	remove_agent (NM_AGENT_MANAGER (user_data),
+	              nm_secret_agent_get_dbus_owner (agent));
+}
+
+static void
 impl_agent_manager_register_with_capabilities (NMAgentManager *self,
+                                               GDBusMethodInvocation *context,
                                                const char *identifier,
-                                               NMSecretAgentCapabilities capabilities,
-                                               DBusGMethodInvocation *context)
+                                               guint32 capabilities)
 {
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
 	NMAuthSubject *subject;
 	gulong sender_uid = G_MAXULONG;
-	GError *error = NULL, *local = NULL;
+	GError *error = NULL;
 	NMSecretAgent *agent;
 	NMAuthChain *chain;
 
@@ -285,17 +344,6 @@ impl_agent_manager_register_with_capabilities (NMAgentManager *self,
 	}
 	sender_uid = nm_auth_subject_get_unix_process_uid (subject);
 
-	if (   0 != sender_uid
-	    && !nm_session_monitor_uid_has_session (nm_session_monitor_get (),
-	                                            sender_uid,
-	                                            NULL,
-	                                            &local)) {
-		error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
-		                             NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
-		                             local && local->message ? local->message : "Session not found");
-		goto done;
-	}
-
 	/* Validate the identifier */
 	if (!validate_identifier (identifier, &error))
 		goto done;
@@ -316,9 +364,10 @@ impl_agent_manager_register_with_capabilities (NMAgentManager *self,
 		                             "Failed to initialize the agent");
 		goto done;
 	}
+	g_signal_connect (agent, NM_SECRET_AGENT_DISCONNECTED,
+	                  G_CALLBACK (agent_disconnected_cb), self);
 
-	nm_log_dbg (LOGD_AGENTS, "(%s) requesting permissions",
-	            nm_secret_agent_get_description (agent));
+	_LOGD (agent, "requesting permissions");
 
 	/* Kick off permissions requests for this agent */
 	chain = nm_auth_chain_new_subject (subject, context, agent_register_permissions_done, self);
@@ -337,33 +386,30 @@ impl_agent_manager_register_with_capabilities (NMAgentManager *self,
 
 done:
 	if (error)
-		dbus_g_method_return_error (context, error);
-	g_clear_error (&error);
-	g_clear_error (&local);
+		g_dbus_method_invocation_take_error (context, error);
 	g_clear_object (&subject);
 }
 
 static void
 impl_agent_manager_register (NMAgentManager *self,
-                             const char *identifier,
-                             DBusGMethodInvocation *context)
+                             GDBusMethodInvocation *context,
+                             const char *identifier)
 {
-	impl_agent_manager_register_with_capabilities (self, identifier, 0, context);
+	impl_agent_manager_register_with_capabilities (self, context, identifier, 0);
 }
 
 static void
 impl_agent_manager_unregister (NMAgentManager *self,
-                               DBusGMethodInvocation *context)
+                               GDBusMethodInvocation *context)
 {
-	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
 	GError *error = NULL;
 	char *sender = NULL;
 
-	if (!nm_dbus_manager_get_caller_info (priv->dbus_mgr,
-	                                      context,
-	                                      &sender,
-	                                      NULL,
-	                                      NULL)) {
+	if (!nm_bus_manager_get_caller_info (nm_bus_manager_get (),
+	                                     context,
+	                                     &sender,
+	                                     NULL,
+	                                     NULL)) {
 		error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
 		                             NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
 		                             "Unable to determine request sender.");
@@ -378,137 +424,188 @@ impl_agent_manager_unregister (NMAgentManager *self,
 		goto done;
 	}
 
-	dbus_g_method_return (context);
+	g_dbus_method_invocation_return_value (context, NULL);
 
 done:
 	if (error)
-		dbus_g_method_return_error (context, error);
-	g_clear_error (&error);
+		g_dbus_method_invocation_take_error (context, error);
 	g_free (sender);
 }
 
 /*************************************************************/
 
-typedef void (*RequestCompleteFunc) (Request *req,
-                                     GHashTable *secrets,
-                                     const char *agent_dbus_owner,
-                                     const char *agent_username,
-                                     GError *error,
-                                     gpointer user_data);
-typedef gboolean (*RequestAddAgentFunc) (Request *req, NMSecretAgent *agent);
-typedef void (*RequestNextFunc) (Request *req);
-typedef void (*RequestCancelFunc) (Request *req);
-
-/* Basic secrets request structure */
-struct _Request {
-	guint32 reqid;
+struct _NMAgentManagerCallId {
+	NMAgentManager *self;
+
+	RequestType request_type;
+
 	char *detail;
-	char *verb;
 
 	NMAuthSubject *subject;
 
 	/* Current agent being asked for secrets */
 	NMSecretAgent *current;
-	gconstpointer current_call_id;
+	NMSecretAgentCallId current_call_id;
 
 	/* Stores the sorted list of NMSecretAgents which will be asked for secrets */
 	GSList *pending;
 
-	/* Stores the list of NMSecretAgent hashes that we've already
-	 * asked for secrets, so that we don't ask the same agent twice
-	 * if it quits and re-registers during this secrets request.
-	 */
-	GSList *asked;
+	guint idle_id;
 
-	NMAuthChain *chain;
+	union {
+		struct {
+			char *path;
+			NMConnection *connection;
 
-	guint32 idle_id;
+			NMAuthChain *chain;
 
-	RequestAddAgentFunc add_agent_callback;
-	RequestCancelFunc cancel_callback;
-	RequestNextFunc next_callback;
-	RequestCompleteFunc complete_callback;
-	gpointer complete_callback_data;
-	gboolean completed;
+			/* Whether the agent currently being asked for secrets
+			 * has the system.modify privilege.
+			 */
+			gboolean current_has_modify;
 
-	GDestroyNotify free_func;
-};
+			union {
+				struct {
+					NMSecretAgentGetSecretsFlags flags;
+					char *setting_name;
+					char **hints;
 
-static guint32 next_req_id = 1;
+					GVariant *existing_secrets;
+
+					NMAgentSecretsResultFunc callback;
+					gpointer callback_data;
+				} get;
+			};
+		} con;
+	};
+};
 
 static Request *
-request_new (gsize struct_size,
+request_new (NMAgentManager *self,
+             RequestType request_type,
              const char *detail,
-             const char *verb,
-             NMAuthSubject *subject,
-             RequestCompleteFunc complete_callback,
-             gpointer complete_callback_data,
-             RequestAddAgentFunc add_agent_callback,
-             RequestNextFunc next_callback,
-             RequestCancelFunc cancel_callback,
-             GDestroyNotify free_func)
+             NMAuthSubject *subject)
 {
 	Request *req;
 
-	req = g_malloc0 (struct_size);
-	req->reqid = next_req_id++;
+	req = g_slice_new0 (Request);
+	req->self = g_object_ref (self);
+	req->request_type = request_type;
 	req->detail = g_strdup (detail);
-	req->verb = g_strdup (verb);
 	req->subject = g_object_ref (subject);
-	req->complete_callback = complete_callback;
-	req->complete_callback_data = complete_callback_data;
-	req->add_agent_callback = add_agent_callback,
-	req->next_callback = next_callback;
-	req->cancel_callback = cancel_callback;
-	req->free_func = free_func;
 	return req;
 }
 
 static void
 request_free (Request *req)
 {
-	if (req->free_func)
-		req->free_func ((gpointer) req);
+	switch (req->request_type) {
+	case REQUEST_TYPE_CON_GET:
+	case REQUEST_TYPE_CON_SAVE:
+	case REQUEST_TYPE_CON_DEL:
+		g_object_unref (req->con.connection);
+		g_free (req->con.path);
+		if (req->con.chain)
+			nm_auth_chain_unref (req->con.chain);
+		if (req->request_type == REQUEST_TYPE_CON_GET) {
+			g_free (req->con.get.setting_name);
+			g_strfreev (req->con.get.hints);
+			if (req->con.get.existing_secrets)
+				g_variant_unref (req->con.get.existing_secrets);
+		}
+		break;
+	default:
+		g_assert_not_reached ();
+	}
 
 	if (req->idle_id)
 		g_source_remove (req->idle_id);
 
-	if (!req->completed && req->cancel_callback)
-		req->cancel_callback (req);
-
-	if (req->chain)
-		nm_auth_chain_unref (req->chain);
+	if (req->current && req->current_call_id) {
+		/* cancel-secrets invokes the done-callback synchronously -- in which case
+		 * the handler just return.
+		 * Hence, we can proceed to free @req... */
+		nm_secret_agent_cancel_secrets (req->current, req->current_call_id);
+	}
 
 	g_object_unref (req->subject);
 
 	g_free (req->detail);
-	g_free (req->verb);
 	g_slist_free_full (req->pending, g_object_unref);
-	g_slist_free (req->asked);
+
+	g_object_unref (req->self);
+
+	if (req->current)
+		g_object_unref (req->current);
+
 	memset (req, 0, sizeof (Request));
-	g_free (req);
+	g_slice_free (Request, req);
 }
 
 static void
-req_complete_success (Request *req,
-                      GHashTable *secrets,
+req_complete_release (Request *req,
+                      GVariant *secrets,
                       const char *agent_dbus_owner,
-                      const char *agent_uname)
+                      const char *agent_username,
+                     GError *error)
+{
+	NMAgentManager *self = req->self;
+
+	switch (req->request_type) {
+	case REQUEST_TYPE_CON_GET:
+		req->con.get.callback (self,
+		                       req,
+		                       agent_dbus_owner,
+		                       agent_username,
+		                       req->con.current_has_modify,
+		                       req->con.get.setting_name,
+		                       req->con.get.flags,
+		                       error ? NULL : secrets,
+		                       error,
+		                       req->con.get.callback_data);
+
+		break;
+	case REQUEST_TYPE_CON_SAVE:
+	case REQUEST_TYPE_CON_DEL:
+		break;
+	default:
+		g_return_if_reached ();
+	}
+
+	request_free (req);
+}
+
+static void
+req_complete_cancel (Request *req, gboolean is_disposing)
+{
+	gs_free_error GError *error = NULL;
+
+	nm_assert (req && req->self);
+	nm_assert (!g_hash_table_contains (NM_AGENT_MANAGER_GET_PRIVATE (req->self)->requests, req));
+
+	nm_utils_error_set_cancelled (&error, is_disposing, "NMAgentManager");
+	req_complete_release (req, NULL, NULL, NULL, error);
+}
+
+static void
+req_complete (Request *req,
+              GVariant *secrets,
+              const char *agent_dbus_owner,
+              const char *agent_username,
+              GError *error)
 {
-	req->completed = TRUE;
-	req->complete_callback (req,
-	                        secrets,
-	                        agent_dbus_owner,
-	                        agent_uname,
-	                        NULL,
-	                        req->complete_callback_data);
+	NMAgentManager *self = req->self;
+	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
+
+	if (!g_hash_table_remove (priv->requests, req))
+		g_return_if_reached ();
+	req_complete_release (req, secrets, agent_dbus_owner, agent_username, error);
 }
 
 static void
 req_complete_error (Request *req, GError *error)
 {
-	req->completed = TRUE;
-	req->complete_callback (req, NULL, NULL, NULL, error, req->complete_callback_data);
+	req_complete (req, NULL, NULL, NULL, error);
 }
 
 static gint
@@ -535,12 +632,8 @@ agent_compare_func (gconstpointer aa, gconstpointer bb, gpointer user_data)
 	}
 
 	/* Prefer agents in active sessions */
-	a_active = nm_session_monitor_uid_active (nm_session_monitor_get (),
-	                                          nm_secret_agent_get_owner_uid (a),
-	                                          NULL);
-	b_active = nm_session_monitor_uid_active (nm_session_monitor_get (),
-	                                          nm_secret_agent_get_owner_uid (b),
-	                                          NULL);
+	a_active = nm_session_monitor_session_exists (nm_session_monitor_get (), nm_secret_agent_get_owner_uid (a), TRUE);
+	b_active = nm_session_monitor_session_exists (nm_session_monitor_get (), nm_secret_agent_get_owner_uid (b), TRUE);
 	if (a_active && !b_active)
 		return -1;
 	else if (a_active == b_active)
@@ -554,14 +647,26 @@ agent_compare_func (gconstpointer aa, gconstpointer bb, gpointer user_data)
 static void
 request_add_agent (Request *req, NMSecretAgent *agent)
 {
+	NMAgentManager *self;
+
 	g_return_if_fail (req != NULL);
 	g_return_if_fail (agent != NULL);
 
-	if (g_slist_find (req->asked, GUINT_TO_POINTER (nm_secret_agent_get_hash (agent))))
-		return;
+	self = req->self;
 
-	if (req->add_agent_callback && !req->add_agent_callback (req, agent))
-		return;
+	if (req->request_type == REQUEST_TYPE_CON_GET) {
+		NMAuthSubject *subject = nm_secret_agent_get_subject (agent);
+
+		/* Ensure the caller's username exists in the connection's permissions,
+		 * or that the permissions is empty (ie, visible by everyone).
+		 */
+		if (!nm_auth_is_subject_in_acl (req->con.connection, subject, NULL)) {
+			_LOGD (agent, "agent ignored for secrets request "LOG_REQ_FMT" (not in ACL)",
+			       LOG_REQ_ARG (req));
+			/* Connection not visible to this agent's user */
+			return;
+		}
+	}
 
 	/* If the request should filter agents by UID, do that now */
 	if (nm_auth_subject_is_unix_process (req->subject)) {
@@ -570,18 +675,16 @@ request_add_agent (Request *req, NMSecretAgent *agent)
 		agent_uid = nm_secret_agent_get_owner_uid (agent);
 		subject_uid = nm_auth_subject_get_unix_process_uid (req->subject);
 		if (agent_uid != subject_uid) {
-			nm_log_dbg (LOGD_AGENTS, "(%s) agent ignored for secrets request %p/%s "
-			            "(uid %ld not required %ld)",
-			            nm_secret_agent_get_description (agent),
-			            req, req->detail,
-			            (long)agent_uid, (long)subject_uid);
+			_LOGD (agent, "agent ignored for secrets request "LOG_REQ_FMT" "
+			       "(uid %ld not required %ld)",
+			       LOG_REQ_ARG (req),
+			       (long) agent_uid, (long) subject_uid);
 			return;
 		}
 	}
 
-	nm_log_dbg (LOGD_AGENTS, "(%s) agent allowed for secrets request %p/%s",
-	            nm_secret_agent_get_description (agent),
-	            req, req->detail);
+	_LOGD (agent, "agent allowed for secrets request "LOG_REQ_FMT,
+	       LOG_REQ_ARG (req));
 
 	/* Add this agent to the list, sorted appropriately */
 	req->pending = g_slist_insert_sorted_with_data (req->pending,
@@ -605,25 +708,41 @@ request_add_agents (NMAgentManager *self, Request *req)
 static void
 request_next_agent (Request *req)
 {
+	NMAgentManager *self;
 	GError *error = NULL;
 
-	req->current_call_id = NULL;
-	if (req->current)
-		g_object_unref (req->current);
+	self = req->self;
+
+	if (req->current) {
+		if (req->current_call_id)
+			nm_secret_agent_cancel_secrets (req->current, req->current_call_id);
+		g_clear_object (&req->current);
+	}
+	g_warn_if_fail (!req->current_call_id);
 
 	if (req->pending) {
 		/* Send the request to the next agent */
 		req->current = req->pending->data;
 		req->pending = g_slist_remove (req->pending, req->current);
 
-		nm_log_dbg (LOGD_AGENTS, "(%s) agent %s secrets for request %p/%s",
-		            nm_secret_agent_get_description (req->current),
-		            req->verb, req, req->detail);
-
-		req->next_callback (req);
+		_LOGD (req->current, "agent %s secrets for request "LOG_REQ_FMT,
+		       _request_type_to_string (req->request_type, TRUE),
+		       LOG_REQ_ARG (req));
+
+		switch (req->request_type) {
+		case REQUEST_TYPE_CON_GET:
+			_con_get_request_start (req);
+			break;
+		case REQUEST_TYPE_CON_SAVE:
+			_con_save_request_start (req);
+			break;
+		case REQUEST_TYPE_CON_DEL:
+			_con_del_request_start (req);
+			break;
+		default:
+			g_assert_not_reached ();
+		}
 	} else {
-		req->current = NULL;
-
 		/* No more secret agents are available to fulfill this secrets request */
 		error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
 		                             NM_AGENT_MANAGER_ERROR_NO_SECRETS,
@@ -636,25 +755,41 @@ request_next_agent (Request *req)
 static void
 request_remove_agent (Request *req, NMSecretAgent *agent, GSList **pending_reqs)
 {
+	NMAgentManager *self;
+
 	g_return_if_fail (req != NULL);
 	g_return_if_fail (agent != NULL);
 
-	req->pending = g_slist_remove (req->pending, agent);
+	self = req->self;
 
 	if (agent == req->current) {
-		nm_log_dbg (LOGD_AGENTS, "(%s) current agent removed from secrets request %p/%s",
-		            nm_secret_agent_get_description (agent), req, req->detail);
-
-		if (req->chain) {
-			/* This cancels the pending authorization requests. */
-			nm_auth_chain_unref (req->chain);
-			req->chain = NULL;
+		nm_assert (!g_slist_find (req->pending, agent));
+
+		_LOGD (agent, "current agent removed from secrets request "LOG_REQ_FMT,
+		       LOG_REQ_ARG (req));
+
+		switch (req->request_type) {
+		case REQUEST_TYPE_CON_GET:
+		case REQUEST_TYPE_CON_SAVE:
+		case REQUEST_TYPE_CON_DEL:
+			if (req->con.chain) {
+				/* This cancels the pending authorization requests. */
+				nm_auth_chain_unref (req->con.chain);
+				req->con.chain = NULL;
+			}
+			break;
+		default:
+			g_assert_not_reached ();
 		}
 
 		*pending_reqs = g_slist_prepend (*pending_reqs, req);
-	} else {
-		nm_log_dbg (LOGD_AGENTS, "(%s) agent removed from secrets request %p/%s",
-		            nm_secret_agent_get_description (agent), req, req->detail);
+	} else if (g_slist_find (req->pending, agent)) {
+		req->pending = g_slist_remove (req->pending, agent);
+
+		_LOGD (agent, "agent removed from secrets request "LOG_REQ_FMT,
+		       LOG_REQ_ARG (req));
+
+		g_object_unref (agent);
 	}
 }
 
@@ -664,190 +799,87 @@ request_start (gpointer user_data)
 	Request *req = user_data;
 
 	req->idle_id = 0;
-	request_next_agent (req);
-	return FALSE;
-}
-
-/*************************************************************/
-
-/* Request subclass for connection secrets */
-typedef struct {
-	Request parent;
-
-	NMSecretAgentGetSecretsFlags flags;
-	NMConnection *connection;
-	char *setting_name;
-	char **hints;
-
-	GHashTable *existing_secrets;
 
-	NMAgentSecretsResultFunc callback;
-	gpointer callback_data;
-	gpointer other_data2;
-	gpointer other_data3;
-
-	/* Whether the agent currently being asked for secrets
-	 * has the system.modify privilege.
-	 */
-	gboolean current_has_modify;
-} ConnectionRequest;
-
-static void
-connection_request_free (gpointer data)
-{
-	ConnectionRequest *req = data;
-
-	g_object_unref (req->connection);
-	g_free (req->setting_name);
-	g_strfreev (req->hints);
-	if (req->existing_secrets)
-		g_hash_table_unref (req->existing_secrets);
-}
-
-static gboolean
-connection_request_add_agent (Request *parent, NMSecretAgent *agent)
-{
-	ConnectionRequest *req = (ConnectionRequest *) parent;
-	NMAuthSubject *subject = nm_secret_agent_get_subject(agent);
-
-	/* Ensure the caller's username exists in the connection's permissions,
-	 * or that the permissions is empty (ie, visible by everyone).
-	 */
-	if (!nm_auth_is_subject_in_acl (req->connection, nm_session_monitor_get (), subject, NULL)) {
-		nm_log_dbg (LOGD_AGENTS, "(%s) agent ignored for secrets request %p/%s (not in ACL)",
-		            nm_secret_agent_get_description (agent),
-		            parent, parent->detail);
-		/* Connection not visible to this agent's user */
-		return FALSE;
+	switch (req->request_type) {
+	case REQUEST_TYPE_CON_GET:
+		if (_con_get_try_complete_early (req))
+			goto out;
+		break;
+	default:
+		break;
 	}
+	request_next_agent (req);
 
-	return TRUE;
-}
-
-static ConnectionRequest *
-connection_request_new_get (NMConnection *connection,
-                            NMAuthSubject *subject,
-                            GHashTable *existing_secrets,
-                            const char *setting_name,
-                            const char *verb,
-                            NMSecretAgentGetSecretsFlags flags,
-                            const char **hints,
-                            NMAgentSecretsResultFunc callback,
-                            gpointer callback_data,
-                            gpointer other_data2,
-                            gpointer other_data3,
-                            RequestCompleteFunc complete_callback,
-                            gpointer complete_callback_data,
-                            RequestNextFunc next_callback,
-                            RequestCancelFunc cancel_callback)
-{
-	ConnectionRequest *req;
-
-	req = (ConnectionRequest *) request_new (sizeof (ConnectionRequest),
-	                                         nm_connection_get_id (connection),
-	                                         verb,
-	                                         subject,
-	                                         complete_callback,
-	                                         complete_callback_data,
-	                                         connection_request_add_agent,
-	                                         next_callback,
-	                                         cancel_callback,
-	                                         connection_request_free);
-	g_assert (req);
-
-	req->connection = g_object_ref (connection);
-	if (existing_secrets)
-		req->existing_secrets = g_hash_table_ref (existing_secrets);
-	req->setting_name = g_strdup (setting_name);
-	req->hints = g_strdupv ((char **) hints);
-	req->flags = flags;
-	req->callback = callback;
-	req->callback_data = callback_data;
-	req->other_data2 = other_data2;
-	req->other_data3 = other_data3;
-	return req;
+out:
+	return FALSE;
 }
 
-static ConnectionRequest *
-connection_request_new_other (NMConnection *connection,
-                              NMAuthSubject *subject,
-                              const char *verb,
-                              RequestCompleteFunc complete_callback,
-                              gpointer complete_callback_data,
-                              RequestNextFunc next_callback)
-{
-	ConnectionRequest *req;
-
-	req = (ConnectionRequest *) request_new (sizeof (ConnectionRequest),
-	                                         nm_connection_get_id (connection),
-	                                         verb,
-	                                         subject,
-	                                         complete_callback,
-	                                         complete_callback_data,
-	                                         NULL,
-	                                         next_callback,
-	                                         NULL,
-	                                         connection_request_free);
-	g_assert (req);
-	req->connection = g_object_ref (connection);
-	return req;
-}
+/*************************************************************/
 
 static void
-get_done_cb (NMSecretAgent *agent,
-             gconstpointer call_id,
-             GHashTable *secrets,
-             GError *error,
-             gpointer user_data)
+_con_get_request_done (NMSecretAgent *agent,
+                       NMSecretAgentCallId call_id,
+                       GVariant *secrets,
+                       GError *error,
+                       gpointer user_data)
 {
-	Request *parent = user_data;
-	ConnectionRequest *req = user_data;
-	GHashTable *setting_secrets;
+	NMAgentManager *self;
+	Request *req = user_data;
+	GVariant *setting_secrets;
 	const char *agent_dbus_owner;
 	struct passwd *pw;
 	char *agent_uname = NULL;
 
-	g_return_if_fail (call_id == parent->current_call_id);
+	g_return_if_fail (call_id == req->current_call_id);
+	g_return_if_fail (agent == req->current);
+	g_return_if_fail (req->request_type == REQUEST_TYPE_CON_GET);
+
+	self = req->self;
+
+	req->current_call_id = NULL;
 
 	if (error) {
-		nm_log_dbg (LOGD_AGENTS, "(%s) agent failed secrets request %p/%s/%s: (%d) %s",
-		            nm_secret_agent_get_description (agent),
-		            req, parent->detail, req->setting_name,
-		            error ? error->code : -1,
-		            (error && error->message) ? error->message : "(unknown)");
+		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			_LOGD (agent, "get secrets request cancelled: "LOG_REQ_FMT,
+			       LOG_REQ_ARG (req));
+			return;
+		}
 
-		if (dbus_g_error_has_name (error, NM_DBUS_INTERFACE_SECRET_AGENT ".UserCanceled")) {
+		_LOGD (agent, "agent failed secrets request "LOG_REQ_FMT": %s",
+		       LOG_REQ_ARG (req),
+		       error->message);
+
+		if (g_error_matches (error, NM_SECRET_AGENT_ERROR, NM_SECRET_AGENT_ERROR_USER_CANCELED)) {
 			error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
 			                             NM_AGENT_MANAGER_ERROR_USER_CANCELED,
 			                             "User canceled the secrets request.");
-			req_complete_error (parent, error);
+			req_complete_error (req, error);
 			g_error_free (error);
 		} else {
-			if (parent->current_call_id) {
+			if (req->current_call_id) {
 				/* Tell the failed agent we're no longer interested. */
-				nm_secret_agent_cancel_secrets (parent->current, parent->current_call_id);
+				nm_secret_agent_cancel_secrets (req->current, req->current_call_id);
 			}
 
 			/* Try the next agent */
-			request_next_agent (parent);
+			request_next_agent (req);
+			maybe_remove_agent_on_error (agent, error);
 		}
 		return;
 	}
 
 	/* Ensure the setting we wanted secrets for got returned and has something in it */
-	setting_secrets = g_hash_table_lookup (secrets, req->setting_name);
-	if (!setting_secrets || !g_hash_table_size (setting_secrets)) {
-		nm_log_dbg (LOGD_AGENTS, "(%s) agent returned no secrets for request %p/%s/%s",
-		            nm_secret_agent_get_description (agent),
-		            req, parent->detail, req->setting_name);
+	setting_secrets = g_variant_lookup_value (secrets, req->con.get.setting_name, NM_VARIANT_TYPE_SETTING);
+	if (!setting_secrets || !g_variant_n_children (setting_secrets)) {
+		_LOGD (agent, "agent returned no secrets for request "LOG_REQ_FMT,
+		       LOG_REQ_ARG (req));
 		/* Try the next agent */
-		request_next_agent (parent);
+		request_next_agent (req);
 		return;
 	}
 
-	nm_log_dbg (LOGD_AGENTS, "(%s) agent returned secrets for request %p/%s/%s",
-	            nm_secret_agent_get_description (agent),
-	            req, parent->detail, req->setting_name);
+	_LOGD (agent, "agent returned secrets for request "LOG_REQ_FMT,
+	       LOG_REQ_ARG (req));
 
 	/* Get the agent's username */
 	pw = getpwuid (nm_secret_agent_get_owner_uid (agent));
@@ -858,110 +890,109 @@ get_done_cb (NMSecretAgent *agent,
 	}
 
 	agent_dbus_owner = nm_secret_agent_get_dbus_owner (agent);
-	req_complete_success (parent, secrets, agent_dbus_owner, agent_uname);
+	req_complete (req, secrets, agent_dbus_owner, agent_uname, NULL);
 	g_free (agent_uname);
 }
 
 static void
-set_secrets_not_required (NMConnection *connection, GHashTable *hash)
+set_secrets_not_required (NMConnection *connection, GVariant *dict)
 {
-	GHashTableIter iter, setting_iter;
+	GVariantIter iter, setting_iter;
 	const char *setting_name = NULL;
-	GHashTable *setting_hash = NULL;
+	GVariant *setting_dict = NULL;
 
-	/* Iterate through the settings hashes */
-	g_hash_table_iter_init (&iter, hash);
-	while (g_hash_table_iter_next (&iter,
-	                               (gpointer *) &setting_name,
-	                               (gpointer *) &setting_hash)) {
+	/* Iterate through the settings dicts */
+	g_variant_iter_init (&iter, dict);
+	while (g_variant_iter_next (&iter, "{&s@a{sv}}", &setting_name, &setting_dict)) {
 		const char *key_name = NULL;
 		NMSetting *setting;
-		GValue *val;
+		GVariant *val;
 
 		setting = nm_connection_get_setting_by_name (connection, setting_name);
 		if (setting) {
 			/* Now through each secret in the setting and mark it as not required */
-			g_hash_table_iter_init (&setting_iter, setting_hash);
-			while (g_hash_table_iter_next (&setting_iter, (gpointer *) &key_name, (gpointer *) &val)) {
+			g_variant_iter_init (&setting_iter, setting_dict);
+			while (g_variant_iter_next (&setting_iter, "{&sv}", &key_name, &val)) {
 				/* For each secret, set the flag that it's not required; VPN
 				 * secrets need slightly different treatment here since the
-				 * "secrets" property is actually a hash table of secrets.
+				 * "secrets" property is actually a dictionary of secrets.
 				 */
 				if (   strcmp (setting_name, NM_SETTING_VPN_SETTING_NAME) == 0
-				    && strcmp (key_name, NM_SETTING_VPN_SECRETS) == 0) {
-					GHashTableIter vpn_secret_iter;
-					const char *secret_name;
+				    && strcmp (key_name, NM_SETTING_VPN_SECRETS) == 0
+				    && g_variant_is_of_type (val, G_VARIANT_TYPE ("a{ss}"))) {
+					GVariantIter vpn_secret_iter;
+					const char *secret_name, *secret;
 
-					g_hash_table_iter_init (&vpn_secret_iter, g_value_get_boxed (val));
-					while (g_hash_table_iter_next (&vpn_secret_iter, (gpointer *) &secret_name, NULL))
+					g_variant_iter_init (&vpn_secret_iter, val);
+					while (g_variant_iter_next (&vpn_secret_iter, "{&s&s}", &secret_name, &secret))
 						nm_setting_set_secret_flags (setting, secret_name, NM_SETTING_SECRET_FLAG_NOT_REQUIRED, NULL);
 				} else
 					nm_setting_set_secret_flags (setting, key_name, NM_SETTING_SECRET_FLAG_NOT_REQUIRED, NULL);
+				g_variant_unref (val);
 			}
 		}
 	}
 }
 
 static void
-get_agent_request_secrets (ConnectionRequest *req, gboolean include_system_secrets)
+_con_get_request_start_proceed (Request *req, gboolean include_system_secrets)
 {
-	Request *parent = (Request *) req;
 	NMConnection *tmp;
 
-	tmp = nm_simple_connection_new_clone (req->connection);
+	g_return_if_fail (req->request_type == REQUEST_TYPE_CON_GET);
+
+	tmp = nm_simple_connection_new_clone (req->con.connection);
 	nm_connection_clear_secrets (tmp);
 	if (include_system_secrets) {
-		if (req->existing_secrets) {
-			GVariant *secrets_dict;
-
-			secrets_dict = nm_utils_connection_hash_to_dict (req->existing_secrets);
-			(void) nm_connection_update_secrets (tmp, req->setting_name, secrets_dict, NULL);
-			g_variant_unref (secrets_dict);
-		}
+		if (req->con.get.existing_secrets)
+			(void) nm_connection_update_secrets (tmp, req->con.get.setting_name, req->con.get.existing_secrets, NULL);
 	} else {
 		/* Update secret flags in the temporary connection to indicate that
 		 * the system secrets we're not sending to the agent aren't required,
 		 * so the agent can properly validate UI controls and such.
 		 */
-		if (req->existing_secrets)
-			set_secrets_not_required (tmp, req->existing_secrets);
+		if (req->con.get.existing_secrets)
+			set_secrets_not_required (tmp, req->con.get.existing_secrets);
 	}
 
-	parent->current_call_id = nm_secret_agent_get_secrets (parent->current,
-	                                                       tmp,
-	                                                       req->setting_name,
-	                                                       (const char **) req->hints,
-	                                                       req->flags,
-	                                                       get_done_cb,
-	                                                       req);
-	if (parent->current_call_id == NULL) {
-		/* Shouldn't hit this, but handle it anyway */
-		g_warn_if_fail (parent->current_call_id != NULL);
-		request_next_agent (parent);
+	req->current_call_id = nm_secret_agent_get_secrets (req->current,
+	                                                    req->con.path,
+	                                                    tmp,
+	                                                    req->con.get.setting_name,
+	                                                    (const char **) req->con.get.hints,
+	                                                    req->con.get.flags,
+	                                                    _con_get_request_done,
+	                                                    req);
+	if (!req->current_call_id) {
+		g_warn_if_reached ();
+		request_next_agent (req);
 	}
 
 	g_object_unref (tmp);
 }
 
 static void
-get_agent_modify_auth_cb (NMAuthChain *chain,
-                          GError *error,
-                          DBusGMethodInvocation *context,
-                          gpointer user_data)
+_con_get_request_start_validated (NMAuthChain *chain,
+                                 GError *error,
+                                 GDBusMethodInvocation *context,
+                                 gpointer user_data)
 {
-	Request *parent = user_data;
-	ConnectionRequest *req = user_data;
+	NMAgentManager *self;
+	Request *req = user_data;
 	const char *perm;
 
-	parent->chain = NULL;
+	g_return_if_fail (req->request_type == REQUEST_TYPE_CON_GET);
+
+	self = req->self;
+
+	req->con.chain = NULL;
 
 	if (error) {
-		nm_log_dbg (LOGD_AGENTS, "(%s) agent %p/%s/%s MODIFY check error: (%d) %s",
-		            nm_secret_agent_get_description (parent->current),
-		            req, parent->detail, req->setting_name,
-		            error->code, error->message ? error->message : "(unknown)");
+		_LOGD (req->current, "agent "LOG_REQ_FMT" MODIFY check error: (%d) %s",
+		       LOG_REQ_ARG (req),
+		       error->code, error->message ? error->message : "(unknown)");
 		/* Try the next agent */
-		request_next_agent (parent);
+		request_next_agent (req);
 	} else {
 		/* If the agent obtained the 'modify' permission, we send all system secrets
 		 * to it.  If it didn't, we still ask it for secrets, but we don't send
@@ -970,25 +1001,24 @@ get_agent_modify_auth_cb (NMAuthChain *chain,
 		perm = nm_auth_chain_get_data (chain, "perm");
 		g_assert (perm);
 		if (nm_auth_chain_get_result (chain, perm) == NM_AUTH_CALL_RESULT_YES)
-			req->current_has_modify = TRUE;
+			req->con.current_has_modify = TRUE;
 
-		nm_log_dbg (LOGD_AGENTS, "(%s) agent %p/%s/%s MODIFY check result %s",
-		            nm_secret_agent_get_description (parent->current),
-		            req, parent->detail, req->setting_name,
-		            req->current_has_modify ? "YES" : "NO");
+		_LOGD (req->current, "agent "LOG_REQ_FMT" MODIFY check result %s",
+		       LOG_REQ_ARG (req),
+		       req->con.current_has_modify ? "YES" : "NO");
 
-		get_agent_request_secrets (req, req->current_has_modify);
+		_con_get_request_start_proceed (req, req->con.current_has_modify);
 	}
 
 	nm_auth_chain_unref (chain);
 }
 
 static void
-check_system_secrets_cb (NMSetting *setting,
-                         const char *key,
-                         const GValue *value,
-                         GParamFlags flags,
-                         gpointer user_data)
+has_system_secrets_check (NMSetting *setting,
+                          const char *key,
+                          const GValue *value,
+                          GParamFlags flags,
+                          gpointer user_data)
 {
 	NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
 	gboolean *has_system = user_data;
@@ -1022,20 +1052,22 @@ has_system_secrets (NMConnection *connection)
 {
 	gboolean has_system = FALSE;
 
-	nm_connection_for_each_setting_value (connection, check_system_secrets_cb, &has_system);
+	nm_connection_for_each_setting_value (connection, has_system_secrets_check, &has_system);
 	return has_system;
 }
 
 static void
-get_next_cb (Request *parent)
+_con_get_request_start (Request *req)
 {
-	ConnectionRequest *req = (ConnectionRequest *) parent;
+	NMAgentManager *self;
 	NMSettingConnection *s_con;
 	const char *agent_dbus_owner, *perm;
 
-	req->current_has_modify = FALSE;
+	self = req->self;
+
+	req->con.current_has_modify = FALSE;
 
-	agent_dbus_owner = nm_secret_agent_get_dbus_owner (parent->current);
+	agent_dbus_owner = nm_secret_agent_get_dbus_owner (req->current);
 
 	/* If the request flags allow user interaction, and there are existing
 	 * system secrets (or blank secrets that are supposed to be system-owned),
@@ -1043,168 +1075,144 @@ get_next_cb (Request *parent)
 	 * secrets to the agent.  We shouldn't leak system-owned secrets to
 	 * unprivileged users.
 	 */
-	if (   (req->flags != NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE)
-	    && (req->existing_secrets || has_system_secrets (req->connection))) {
-		nm_log_dbg (LOGD_AGENTS, "(%p/%s/%s) request has system secrets; checking agent %s for MODIFY",
-		            req, parent->detail, req->setting_name, agent_dbus_owner);
+	if (   (req->con.get.flags != NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE)
+	    && (req->con.get.existing_secrets || has_system_secrets (req->con.connection))) {
+		_LOGD (NULL, "("LOG_REQ_FMT") request has system secrets; checking agent %s for MODIFY",
+		       LOG_REQ_ARG (req), agent_dbus_owner);
 
-		parent->chain = nm_auth_chain_new_subject (nm_secret_agent_get_subject (parent->current),
-		                                           NULL,
-		                                           get_agent_modify_auth_cb,
-		                                           req);
-		g_assert (parent->chain);
+		req->con.chain = nm_auth_chain_new_subject (nm_secret_agent_get_subject (req->current),
+		                                            NULL,
+		                                            _con_get_request_start_validated,
+		                                            req);
+		g_assert (req->con.chain);
 
 		/* If the caller is the only user in the connection's permissions, then
 		 * we use the 'modify.own' permission instead of 'modify.system'.  If the
 		 * request affects more than just the caller, require 'modify.system'.
 		 */
-		s_con = nm_connection_get_setting_connection (req->connection);
+		s_con = nm_connection_get_setting_connection (req->con.connection);
 		g_assert (s_con);
 		if (nm_setting_connection_get_num_permissions (s_con) == 1)
 			perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN;
 		else
 			perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM;
-		nm_auth_chain_set_data (parent->chain, "perm", (gpointer) perm, NULL);
+		nm_auth_chain_set_data (req->con.chain, "perm", (gpointer) perm, NULL);
 
-		nm_auth_chain_add_call (parent->chain, perm, TRUE);
+		nm_auth_chain_add_call (req->con.chain, perm, TRUE);
 	} else {
-		nm_log_dbg (LOGD_AGENTS, "(%p/%s/%s) requesting user-owned secrets from agent %s",
-		            req, parent->detail, req->setting_name, agent_dbus_owner);
+		_LOGD (NULL, "("LOG_REQ_FMT") requesting user-owned secrets from agent %s",
+		       LOG_REQ_ARG (req), agent_dbus_owner);
 
-		get_agent_request_secrets (req, FALSE);
+		_con_get_request_start_proceed (req, FALSE);
 	}
 }
 
 static gboolean
-get_start (gpointer user_data)
+_con_get_try_complete_early (Request *req)
 {
-	Request *parent = user_data;
-	ConnectionRequest *req = user_data;
-	GHashTable *setting_secrets = NULL;
+	NMAgentManager *self;
+	gs_unref_variant GVariant *setting_secrets = NULL;
+	gs_unref_object NMConnection *tmp = NULL;
+	GError *error = NULL;
 
-	parent->idle_id = 0;
+	self = req->self;
 
 	/* Check if there are any existing secrets */
-	if (req->existing_secrets)
-		setting_secrets = g_hash_table_lookup (req->existing_secrets, req->setting_name);
-
-	if (setting_secrets && g_hash_table_size (setting_secrets)) {
-		NMConnection *tmp;
-		GError *error = NULL;
-		gboolean new_secrets = (req->flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
-		GVariant *secrets_dict;
-
-		/* The connection already had secrets; check if any more are required.
-		 * If no more are required, we're done.  If secrets are still needed,
-		 * ask a secret agent for more.  This allows admins to provide generic
-		 * secrets but allow additional user-specific ones as well.
-		 */
-		tmp = nm_simple_connection_new_clone (req->connection);
-		g_assert (tmp);
+	if (req->con.get.existing_secrets)
+		setting_secrets = g_variant_lookup_value (req->con.get.existing_secrets, req->con.get.setting_name, NM_VARIANT_TYPE_SETTING);
 
-		secrets_dict = nm_utils_connection_hash_to_dict (req->existing_secrets);
-		if (!nm_connection_update_secrets (tmp, req->setting_name, secrets_dict, &error)) {
-			req_complete_error (parent, error);
-			g_clear_error (&error);
-		} else {
-			/* Do we have everything we need? */
-			if (   (req->flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM)
-			    || ((nm_connection_need_secrets (tmp, NULL) == NULL) && (new_secrets == FALSE))) {
-				nm_log_dbg (LOGD_AGENTS, "(%p/%s/%s) system settings secrets sufficient",
-				            req, parent->detail, req->setting_name);
-
-				/* Got everything, we're done */
-				req_complete_success (parent, req->existing_secrets, NULL, NULL);
-			} else {
-				nm_log_dbg (LOGD_AGENTS, "(%p/%s/%s) system settings secrets insufficient, asking agents",
-				            req, parent->detail, req->setting_name);
-
-				/* We don't, so ask some agents for additional secrets */
-				if (   req->flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_NO_ERRORS
-				    && !parent->pending) {
-					/* The request initiated from GetSecrets() via DBus,
-					 * don't error out if any secrets are missing. */
-					req_complete_success (parent, req->existing_secrets, NULL, NULL);
-				} else
-					request_next_agent (parent);
-			}
-		}
-		g_variant_unref (secrets_dict);
-		g_object_unref (tmp);
-	} else {
-		/* Couldn't get secrets from system settings, so now we ask the
-		 * agents for secrets.  Let the Agent Manager handle which agents
-		 * we'll ask and in which order.
-		 */
-		request_next_agent (parent);
-	}
+	if (!setting_secrets || !g_variant_n_children (setting_secrets))
+		return FALSE;
 
-	return FALSE;
-}
+	/* The connection already had secrets; check if any more are required.
+	 * If no more are required, we're done.  If secrets are still needed,
+	 * ask a secret agent for more.  This allows admins to provide generic
+	 * secrets but allow additional user-specific ones as well.
+	 */
+	tmp = nm_simple_connection_new_clone (req->con.connection);
+	g_assert (tmp);
 
-static void
-get_complete_cb (Request *parent,
-                 GHashTable *secrets,
-                 const char *agent_dbus_owner,
-                 const char *agent_username,
-                 GError *error,
-                 gpointer user_data)
-{
-	NMAgentManager *self = NM_AGENT_MANAGER (user_data);
-	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
-	ConnectionRequest *req = (ConnectionRequest *) parent;
-
-	/* Send secrets back to the requesting object */
-	req->callback (self,
-	               parent->reqid,
-	               agent_dbus_owner,
-	               agent_username,
-	               req->current_has_modify,
-	               req->setting_name,
-	               req->flags,
-	               error ? NULL : secrets,
-	               error,
-	               req->callback_data,
-	               req->other_data2,
-	               req->other_data3);
-
-	g_hash_table_remove (priv->requests, GUINT_TO_POINTER (parent->reqid));
-}
+	if (!nm_connection_update_secrets (tmp, req->con.get.setting_name, req->con.get.existing_secrets, &error)) {
+		req_complete_error (req, error);
+		g_clear_error (&error);
+		return TRUE;
+	}
+	/* Do we have everything we need? */
+	if (   NM_FLAGS_HAS (req->con.get.flags, NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM)
+	    || (   (nm_connection_need_secrets (tmp, NULL) == NULL)
+	        && !NM_FLAGS_HAS(req->con.get.flags, NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW))) {
+		_LOGD (NULL, "("LOG_REQ_FMT") system settings secrets sufficient",
+		       LOG_REQ_ARG (req));
+
+		/* Got everything, we're done */
+		req_complete (req, req->con.get.existing_secrets, NULL, NULL, NULL);
+		return TRUE;
+	}
 
-static void
-get_cancel_cb (Request *parent)
-{
-	ConnectionRequest *req = (ConnectionRequest *) parent;
+	_LOGD (NULL, "("LOG_REQ_FMT") system settings secrets insufficient, asking agents",
+	       LOG_REQ_ARG (req));
+
+	/* We don't, so ask some agents for additional secrets */
+	if (   req->con.get.flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_NO_ERRORS
+	    && !req->pending) {
+		/* The request initiated from GetSecrets() via DBus,
+		 * don't error out if any secrets are missing. */
+		req_complete (req, req->con.get.existing_secrets, NULL, NULL, NULL);
+		return TRUE;
+	}
 
-	req->current_has_modify = FALSE;
-	if (parent->current && parent->current_call_id)
-		nm_secret_agent_cancel_secrets (parent->current, parent->current_call_id);
+	/* Couldn't get secrets from system settings, so now we ask the
+	 * agents for secrets.  Let the Agent Manager handle which agents
+	 * we'll ask and in which order.
+	 */
+	return FALSE;
 }
 
-guint32
+/**
+ * nm_agent_manager_get_secrets:
+ * @self:
+ * @path:
+ * @connection:
+ * @subject:
+ * @existing_secrets:
+ * @flags:
+ * @hints:
+ * @callback:
+ * @callback_data:
+ *
+ * Requests secrets for a connection.
+ *
+ * This function cannot fail. The callback will be invoked
+ * asynchrnously, but it will always be invoked exactly once.
+ * Even for cancellation and disposing of @self. In those latter
+ * cases, the callback is invoked synchrnously during the cancellation/
+ * disposal.
+ *
+ * Returns: a call-id to cancel the call.
+ */
+NMAgentManagerCallId
 nm_agent_manager_get_secrets (NMAgentManager *self,
+                              const char *path,
                               NMConnection *connection,
                               NMAuthSubject *subject,
-                              GHashTable *existing_secrets,
+                              GVariant *existing_secrets,
                               const char *setting_name,
                               NMSecretAgentGetSecretsFlags flags,
                               const char **hints,
                               NMAgentSecretsResultFunc callback,
-                              gpointer callback_data,
-                              gpointer other_data2,
-                              gpointer other_data3)
+                              gpointer callback_data)
 {
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
-	Request *parent;
-	ConnectionRequest *req;
+	Request *req;
 
-	g_return_val_if_fail (self != NULL, 0);
-	g_return_val_if_fail (NM_IS_CONNECTION (connection), 0);
-	g_return_val_if_fail (callback != NULL, 0);
+	g_return_val_if_fail (self != NULL, NULL);
+	g_return_val_if_fail (path && *path, NULL);
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
+	g_return_val_if_fail (callback != NULL, NULL);
 
 	nm_log_dbg (LOGD_SETTINGS,
 	            "Secrets requested for connection %s (%s/%s)",
-	            nm_connection_get_path (connection),
+	            path,
 	            nm_connection_get_id (connection),
 	            setting_name);
 
@@ -1213,224 +1221,221 @@ nm_agent_manager_get_secrets (NMAgentManager *self,
 	 * This in turn depends on nm_connection_to_dbus() and nm_setting_to_hash()
 	 * both returning NULL if they didn't hash anything.
 	 */
+	req = request_new (self,
+	                   REQUEST_TYPE_CON_GET,
+	                   nm_connection_get_id (connection),
+	                   subject);
+
+	req->con.path = g_strdup (path);
+	req->con.connection = g_object_ref (connection);
+	if (existing_secrets)
+		req->con.get.existing_secrets = g_variant_ref (existing_secrets);
+	req->con.get.setting_name = g_strdup (setting_name);
+	req->con.get.hints = g_strdupv ((char **) hints);
+	req->con.get.flags = flags;
+	req->con.get.callback = callback;
+	req->con.get.callback_data = callback_data;
 
-	req = connection_request_new_get (connection,
-	                                  subject,
-	                                  existing_secrets,
-	                                  setting_name,
-	                                  "getting",
-	                                  flags,
-	                                  hints,
-	                                  callback,
-	                                  callback_data,
-	                                  other_data2,
-	                                  other_data3,
-	                                  get_complete_cb,
-	                                  self,
-	                                  get_next_cb,
-	                                  get_cancel_cb);
-	parent = (Request *) req;
-	g_hash_table_insert (priv->requests, GUINT_TO_POINTER (parent->reqid), req);
+	if (!nm_g_hash_table_add (priv->requests, req))
+		g_assert_not_reached ();
 
 	/* Kick off the request */
-	if (!(req->flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM))
-		request_add_agents (self, parent);
-	parent->idle_id = g_idle_add (get_start, req);
-	return parent->reqid;
+	if (!(req->con.get.flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM))
+		request_add_agents (self, req);
+	req->idle_id = g_idle_add (request_start, req);
+	return req;
 }
 
 void
 nm_agent_manager_cancel_secrets (NMAgentManager *self,
-                                 guint32 request_id)
+                                 NMAgentManagerCallId request_id)
 {
 	g_return_if_fail (self != NULL);
-	g_return_if_fail (request_id > 0);
+	g_return_if_fail (request_id);
+	g_return_if_fail (request_id->request_type == REQUEST_TYPE_CON_GET);
 
-	g_hash_table_remove (NM_AGENT_MANAGER_GET_PRIVATE (self)->requests,
-	                     GUINT_TO_POINTER (request_id));
+	if (!g_hash_table_remove (NM_AGENT_MANAGER_GET_PRIVATE (self)->requests,
+	                          request_id))
+		g_return_if_reached ();
+
+	req_complete_cancel (request_id, FALSE);
 }
 
 /*************************************************************/
 
 static void
-save_done_cb (NMSecretAgent *agent,
-              gconstpointer call_id,
-              GHashTable *secrets,
-              GError *error,
-              gpointer user_data)
+_con_save_request_done (NMSecretAgent *agent,
+                        NMSecretAgentCallId call_id,
+                        GVariant *secrets,
+                        GError *error,
+                        gpointer user_data)
 {
-	Request *parent = user_data;
-	ConnectionRequest *req = user_data;
+	NMAgentManager *self;
+	Request *req = user_data;
 	const char *agent_dbus_owner;
 
-	g_return_if_fail (call_id == parent->current_call_id);
+	g_return_if_fail (call_id == req->current_call_id);
+	g_return_if_fail (agent == req->current);
+	g_return_if_fail (req->request_type == REQUEST_TYPE_CON_SAVE);
+
+	self = req->self;
+
+	req->current_call_id = NULL;
 
 	if (error) {
-		nm_log_dbg (LOGD_AGENTS, "(%s) agent failed save secrets request %p/%s: (%d) %s",
-		            nm_secret_agent_get_description (agent),
-		            req, parent->detail,
-		            error ? error->code : -1,
-		            (error && error->message) ? error->message : "(unknown)");
+		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			_LOGD (agent, "save secrets request cancelled: "LOG_REQ_FMT,
+			       LOG_REQ_ARG (req));
+			return;
+		}
+
+		_LOGD (agent, "agent failed save secrets request "LOG_REQ_FMT": %s",
+		       LOG_REQ_ARG (req), error->message);
 		/* Try the next agent */
-		request_next_agent (parent);
+		request_next_agent (req);
+		maybe_remove_agent_on_error (agent, error);
 		return;
 	}
 
-	nm_log_dbg (LOGD_AGENTS, "(%s) agent saved secrets for request %p/%s",
-	            nm_secret_agent_get_description (agent),
-	            req, parent->detail);
+	_LOGD (agent, "agent saved secrets for request "LOG_REQ_FMT,
+	       LOG_REQ_ARG (req));
 
 	agent_dbus_owner = nm_secret_agent_get_dbus_owner (agent);
-	req_complete_success (parent, NULL, NULL, agent_dbus_owner);
+	req_complete (req, NULL, NULL, agent_dbus_owner, NULL);
 }
 
 static void
-save_next_cb (Request *parent)
+_con_save_request_start (Request *req)
 {
-	ConnectionRequest *req = (ConnectionRequest *) parent;
-
-	parent->current_call_id = nm_secret_agent_save_secrets (parent->current,
-	                                                        req->connection,
-	                                                        save_done_cb,
-	                                                        req);
-	if (parent->current_call_id == NULL) {
-		/* Shouldn't hit this, but handle it anyway */
-		g_warn_if_fail (parent->current_call_id != NULL);
-		request_next_agent (parent);
+	req->current_call_id = nm_secret_agent_save_secrets (req->current,
+	                                                     req->con.path,
+	                                                     req->con.connection,
+	                                                     _con_save_request_done,
+	                                                     req);
+	if (!req->current_call_id) {
+		g_warn_if_reached ();
+		request_next_agent (req);
 	}
 }
 
-static void
-save_complete_cb (Request *req,
-                  GHashTable *secrets,
-                  const char *agent_dbus_owner,
-                  const char *agent_username,
-                  GError *error,
-                  gpointer user_data)
-{
-	g_hash_table_remove (NM_AGENT_MANAGER_GET_PRIVATE (user_data)->requests,
-	                     GUINT_TO_POINTER (req->reqid));
-}
-
-guint32
+void
 nm_agent_manager_save_secrets (NMAgentManager *self,
+                               const char *path,
                                NMConnection *connection,
                                NMAuthSubject *subject)
 {
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
-	ConnectionRequest *req;
-	Request *parent;
+	Request *req;
 
-	g_return_val_if_fail (self != NULL, 0);
-	g_return_val_if_fail (NM_IS_CONNECTION (connection), 0);
+	g_return_if_fail (self);
+	g_return_if_fail (path && *path);
+	g_return_if_fail (NM_IS_CONNECTION (connection));
 
 	nm_log_dbg (LOGD_SETTINGS,
 	            "Saving secrets for connection %s (%s)",
-	            nm_connection_get_path (connection),
+	            path,
 	            nm_connection_get_id (connection));
 
-	req = connection_request_new_other (connection,
-	                                    subject,
-	                                    "saving",
-	                                    save_complete_cb,
-	                                    self,
-	                                    save_next_cb);
-	parent = (Request *) req;
-	g_hash_table_insert (priv->requests, GUINT_TO_POINTER (parent->reqid), req);
+	req = request_new (self,
+	                   REQUEST_TYPE_CON_SAVE,
+	                   nm_connection_get_id (connection),
+	                   subject);
+	req->con.path = g_strdup (path);
+	req->con.connection = g_object_ref (connection);
+	if (!nm_g_hash_table_add (priv->requests, req))
+		g_assert_not_reached ();
 
 	/* Kick off the request */
-	request_add_agents (self, parent);
-	parent->idle_id = g_idle_add (request_start, req);
-	return parent->reqid;
+	request_add_agents (self, req);
+	req->idle_id = g_idle_add (request_start, req);
 }
 
 /*************************************************************/
 
 static void
-delete_done_cb (NMSecretAgent *agent,
-              gconstpointer call_id,
-              GHashTable *secrets,
-              GError *error,
-              gpointer user_data)
+_con_del_request_done (NMSecretAgent *agent,
+                       NMSecretAgentCallId call_id,
+                       GVariant *secrets,
+                       GError *error,
+                       gpointer user_data)
 {
+	NMAgentManager *self;
 	Request *req = user_data;
 
 	g_return_if_fail (call_id == req->current_call_id);
+	g_return_if_fail (agent == req->current);
+	g_return_if_fail (req->request_type == REQUEST_TYPE_CON_DEL);
+
+	self = req->self;
+
+	req->current_call_id = NULL;
 
 	if (error) {
-		nm_log_dbg (LOGD_AGENTS, "(%s) agent failed delete secrets request %p/%s: (%d) %s",
-		            nm_secret_agent_get_description (agent), req, req->detail,
-		            error ? error->code : -1,
-		            (error && error->message) ? error->message : "(unknown)");
+		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			_LOGD (agent, "delete secrets request cancelled: "LOG_REQ_FMT,
+			       LOG_REQ_ARG (req));
+			return;
+		}
+
+		_LOGD (agent, "agent failed delete secrets request "LOG_REQ_FMT": %s",
+		       LOG_REQ_ARG (req), error->message);
 	} else {
-		nm_log_dbg (LOGD_AGENTS, "(%s) agent deleted secrets for request %p/%s",
-		            nm_secret_agent_get_description (agent), req, req->detail);
+		_LOGD (agent, "agent deleted secrets for request "LOG_REQ_FMT,
+		       LOG_REQ_ARG (req));
 	}
 
 	/* Tell the next agent to delete secrets */
 	request_next_agent (req);
+	if (error)
+		maybe_remove_agent_on_error (agent, error);
 }
 
 static void
-delete_next_cb (Request *parent)
+_con_del_request_start (Request *req)
 {
-	ConnectionRequest *req = (ConnectionRequest *) parent;
-
-	parent->current_call_id = nm_secret_agent_delete_secrets (parent->current,
-	                                                          req->connection,
-	                                                          delete_done_cb,
-	                                                          req);
-	if (parent->current_call_id == NULL) {
-		/* Shouldn't hit this, but handle it anyway */
-		g_warn_if_fail (parent->current_call_id != NULL);
-		request_next_agent (parent);
+	req->current_call_id = nm_secret_agent_delete_secrets (req->current,
+	                                                       req->con.path,
+	                                                       req->con.connection,
+	                                                       _con_del_request_done,
+	                                                       req);
+	if (!req->current_call_id) {
+		g_warn_if_reached ();
+		request_next_agent (req);
 	}
 }
 
-static void
-delete_complete_cb (Request *req,
-                    GHashTable *secrets,
-                    const char *agent_dbus_owner,
-                    const char *agent_username,
-                    GError *error,
-                    gpointer user_data)
-{
-	g_hash_table_remove (NM_AGENT_MANAGER_GET_PRIVATE (user_data)->requests,
-	                     GUINT_TO_POINTER (req->reqid));
-}
-
-guint32
+void
 nm_agent_manager_delete_secrets (NMAgentManager *self,
+                                 const char *path,
                                  NMConnection *connection)
 {
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
 	NMAuthSubject *subject;
-	ConnectionRequest *req;
-	Request *parent;
+	Request *req;
 
-	g_return_val_if_fail (self != NULL, 0);
-	g_return_val_if_fail (NM_IS_CONNECTION (connection), 0);
+	g_return_if_fail (self != NULL);
+	g_return_if_fail (path && *path);
+	g_return_if_fail (NM_IS_CONNECTION (connection));
 
 	nm_log_dbg (LOGD_SETTINGS,
 	            "Deleting secrets for connection %s (%s)",
-	            nm_connection_get_path (connection),
+	            path,
 	            nm_connection_get_id (connection));
 
 	subject = nm_auth_subject_new_internal ();
-	req = connection_request_new_other (connection,
-	                                    subject,
-	                                    "deleting",
-	                                    delete_complete_cb,
-	                                    self,
-	                                    delete_next_cb);
+	req = request_new (self,
+	                   REQUEST_TYPE_CON_DEL,
+	                   nm_connection_get_id (connection),
+	                   subject);
+	req->con.path = g_strdup (path);
+	req->con.connection = g_object_ref (connection);
 	g_object_unref (subject);
-	parent = (Request *) req;
-	g_hash_table_insert (priv->requests, GUINT_TO_POINTER (parent->reqid), req);
+	if (!nm_g_hash_table_add (priv->requests, req))
+		g_assert_not_reached ();
 
 	/* Kick off the request */
-	request_add_agents (self, parent);
-	parent->idle_id = g_idle_add (request_start, req);
-	return parent->reqid;
+	request_add_agents (self, req);
+	req->idle_id = g_idle_add (request_start, req);
 }
 
 /*************************************************************/
@@ -1480,22 +1485,9 @@ nm_agent_manager_all_agents_have_capability (NMAgentManager *manager,
 /*************************************************************/
 
 static void
-name_owner_changed_cb (NMDBusManager *dbus_mgr,
-                       const char *name,
-                       const char *old_owner,
-                       const char *new_owner,
-                       gpointer user_data)
-{
-	if (old_owner) {
-		/* The agent quit, so remove it and let interested clients know */
-		remove_agent (NM_AGENT_MANAGER (user_data), old_owner);
-	}
-}
-
-static void
 agent_permissions_changed_done (NMAuthChain *chain,
                                 GError *error,
-                                DBusGMethodInvocation *context,
+                                GDBusMethodInvocation *context,
                                 gpointer user_data)
 {
 	NMAgentManager *self = NM_AGENT_MANAGER (user_data);
@@ -1508,12 +1500,10 @@ agent_permissions_changed_done (NMAuthChain *chain,
 	agent = nm_auth_chain_get_data (chain, "agent");
 	g_assert (agent);
 
-	if (error) {
-		nm_log_dbg (LOGD_AGENTS, "(%s) failed to request updated agent permissions",
-		            nm_secret_agent_get_description (agent));
-	} else {
-		nm_log_dbg (LOGD_AGENTS, "(%s) updated agent permissions",
-		            nm_secret_agent_get_description (agent));
+	if (error)
+		_LOGD (agent, "failed to request updated agent permissions");
+	else {
+		_LOGD (agent, "updated agent permissions");
 
 		if (nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED) == NM_AUTH_CALL_RESULT_YES)
 			share_protected = TRUE;
@@ -1566,10 +1556,7 @@ nm_agent_manager_init (NMAgentManager *self)
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
 
 	priv->agents = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
-	priv->requests = g_hash_table_new_full (g_direct_hash,
-	                                        g_direct_equal,
-	                                        NULL,
-	                                        (GDestroyNotify) request_free);
+	priv->requests = g_hash_table_new (g_direct_hash, g_direct_equal);
 }
 
 static void
@@ -1579,20 +1566,16 @@ constructed (GObject *object)
 
 	G_OBJECT_CLASS (nm_agent_manager_parent_class)->constructed (object);
 
-	priv->dbus_mgr = g_object_ref (nm_dbus_manager_get ());
 	priv->auth_mgr = g_object_ref (nm_auth_manager_get ());
 
-	nm_dbus_manager_register_object (priv->dbus_mgr, NM_DBUS_PATH_AGENT_MANAGER, object);
-
-	g_signal_connect (priv->dbus_mgr,
-	                  NM_DBUS_MANAGER_NAME_OWNER_CHANGED,
-	                  G_CALLBACK (name_owner_changed_cb),
-	                  object);
+	nm_exported_object_export (NM_EXPORTED_OBJECT (object));
 
 	g_signal_connect (priv->auth_mgr,
 	                  NM_AUTH_MANAGER_SIGNAL_CHANGED,
 	                  G_CALLBACK (authority_changed_cb),
 	                  object);
+
+	NM_UTILS_KEEP_ALIVE (object, nm_session_monitor_get (), "NMAgentManager-depends-on-NMSessionMonitor");
 }
 
 static void
@@ -1600,6 +1583,21 @@ dispose (GObject *object)
 {
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (object);
 
+	if (priv->requests) {
+		GHashTableIter iter;
+		Request *req;
+
+cancel_more:
+		g_hash_table_iter_init (&iter, priv->requests);
+		if (g_hash_table_iter_next (&iter, (gpointer *) &req, NULL)) {
+			g_hash_table_iter_remove (&iter);
+			req_complete_cancel (req, TRUE);
+			goto cancel_more;
+		}
+		g_hash_table_unref (priv->requests);
+		priv->requests = NULL;
+	}
+
 	g_slist_free_full (priv->chains, (GDestroyNotify) nm_auth_chain_unref);
 	priv->chains = NULL;
 
@@ -1607,10 +1605,6 @@ dispose (GObject *object)
 		g_hash_table_destroy (priv->agents);
 		priv->agents = NULL;
 	}
-	if (priv->requests) {
-		g_hash_table_destroy (priv->requests);
-		priv->requests = NULL;
-	}
 
 	if (priv->auth_mgr) {
 		g_signal_handlers_disconnect_by_func (priv->auth_mgr,
@@ -1618,13 +1612,8 @@ dispose (GObject *object)
 		                                      object);
 		g_clear_object (&priv->auth_mgr);
 	}
-	if (priv->dbus_mgr) {
-		g_signal_handlers_disconnect_by_func (priv->dbus_mgr,
-		                                      G_CALLBACK (name_owner_changed_cb),
-		                                      object);
-		nm_dbus_manager_unregister_object (priv->dbus_mgr, object);
-		g_clear_object (&priv->dbus_mgr);
-	}
+
+	nm_exported_object_unexport (NM_EXPORTED_OBJECT (object));
 
 	G_OBJECT_CLASS (nm_agent_manager_parent_class)->dispose (object);
 }
@@ -1633,9 +1622,12 @@ static void
 nm_agent_manager_class_init (NMAgentManagerClass *agent_manager_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (agent_manager_class);
+	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (agent_manager_class);
 
 	g_type_class_add_private (agent_manager_class, sizeof (NMAgentManagerPrivate));
 
+	exported_object_class->export_path = NM_DBUS_PATH_AGENT_MANAGER;
+
 	/* virtual methods */
 	object_class->constructed = constructed;
 	object_class->dispose = dispose;
@@ -1651,10 +1643,10 @@ nm_agent_manager_class_init (NMAgentManagerClass *agent_manager_class)
 		              G_TYPE_NONE, 1,
 		              G_TYPE_OBJECT);
 
-	dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (agent_manager_class),
-	                                 &dbus_glib_nm_agent_manager_object_info);
-
-	dbus_g_error_domain_register (NM_AGENT_MANAGER_ERROR,
-	                              NM_DBUS_INTERFACE_AGENT_MANAGER,
-	                              NM_TYPE_AGENT_MANAGER_ERROR);
+	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (agent_manager_class),
+	                                        NMDBUS_TYPE_AGENT_MANAGER_SKELETON,
+	                                        "Register", impl_agent_manager_register,
+	                                        "RegisterWithCapabilities", impl_agent_manager_register_with_capabilities,
+	                                        "Unregister", impl_agent_manager_unregister,
+	                                        NULL);
 }