about summary refs log tree commit diff
path: root/src/nm-auth-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-auth-manager.c')
-rw-r--r--src/nm-auth-manager.c623
1 files changed, 350 insertions, 273 deletions
diff --git a/src/nm-auth-manager.c b/src/nm-auth-manager.c
index 003d9975..199a2e40 100644
--- a/src/nm-auth-manager.c
+++ b/src/nm-auth-manager.c
@@ -22,6 +22,7 @@
 
 #include "nm-auth-manager.h"
 
+#include "c-list/src/c-list.h"
 #include "nm-errors.h"
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
@@ -30,6 +31,9 @@
 #define POLKIT_OBJECT_PATH                  "/org/freedesktop/PolicyKit1/Authority"
 #define POLKIT_INTERFACE                    "org.freedesktop.PolicyKit1.Authority"
 
+#define CANCELLATION_ID_PREFIX "cancellation-id-"
+#define CANCELLATION_TIMEOUT_MS 5000
+
 /*****************************************************************************/
 
 NM_GOBJECT_PROPERTIES_DEFINE_BASE (
@@ -44,13 +48,14 @@ enum {
 static guint signals[LAST_SIGNAL] = {0};
 
 typedef struct {
-	gboolean polkit_enabled;
-#if WITH_POLKIT
-	guint call_id_counter;
-	GCancellable *new_proxy_cancellable;
-	GSList *queued_calls;
+	CList calls_lst_head;
 	GDBusProxy *proxy;
-#endif
+	GCancellable *new_proxy_cancellable;
+	GCancellable *cancel_cancellable;
+	guint64 call_numid_counter;
+	bool polkit_enabled:1;
+	bool disposing:1;
+	bool shutting_down:1;
 } NMAuthManagerPrivate;
 
 struct _NMAuthManager {
@@ -85,6 +90,22 @@ NM_DEFINE_SINGLETON_REGISTER (NMAuthManager);
         } \
     } G_STMT_END
 
+#define _NMLOG2(level, call_id, ...) \
+    G_STMT_START { \
+        if (nm_logging_enabled ((level), (_NMLOG_DOMAIN))) { \
+            NMAuthManagerCallId *_call_id = (call_id); \
+            char __prefix[30] = _NMLOG_PREFIX_NAME; \
+            \
+            if (_call_id->self != singleton_instance) \
+                g_snprintf (__prefix, sizeof (__prefix), ""_NMLOG_PREFIX_NAME"[%p]", _call_id->self); \
+            _nm_log ((level), (_NMLOG_DOMAIN), 0, NULL, NULL, \
+                     "%s: call[%"G_GUINT64_FORMAT"]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+                     __prefix, \
+                     _call_id->call_numid \
+                     _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
 /*****************************************************************************/
 
 gboolean
@@ -97,251 +118,323 @@ nm_auth_manager_get_polkit_enabled (NMAuthManager *self)
 
 /*****************************************************************************/
 
-#if WITH_POLKIT
-
 typedef enum {
 	POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE                   = 0,
 	POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION = (1<<0),
 } PolkitCheckAuthorizationFlags;
 
-typedef struct {
-	guint call_id;
+typedef enum {
+	IDLE_REASON_AUTHORIZED,
+	IDLE_REASON_NO_DBUS,
+} IdleReason;
+
+struct _NMAuthManagerCallId {
+	CList calls_lst;
 	NMAuthManager *self;
-	GSimpleAsyncResult *simple;
-	gchar *cancellation_id;
 	GVariant *dbus_parameters;
-	GCancellable *cancellable;
-} CheckAuthData;
+	GCancellable *dbus_cancellable;
+	NMAuthManagerCheckAuthorizationCallback callback;
+	gpointer user_data;
+	guint64 call_numid;
+	guint idle_id;
+	IdleReason idle_reason:8;
+};
+
+#define cancellation_id_to_str_a(call_numid) \
+	nm_sprintf_bufa (NM_STRLEN (CANCELLATION_ID_PREFIX) + 20, \
+	                 CANCELLATION_ID_PREFIX"%"G_GUINT64_FORMAT, \
+	                 (call_numid))
 
 static void
-_check_auth_data_free (CheckAuthData *data)
+_call_id_free (NMAuthManagerCallId *call_id)
 {
-	if (data->dbus_parameters)
-		g_variant_unref (data->dbus_parameters);
-	g_object_unref (data->self);
-	g_object_unref (data->simple);
-	g_clear_object (&data->cancellable);
-	g_free (data->cancellation_id);
-	g_free (data);
+	c_list_unlink (&call_id->calls_lst);
+	nm_clear_g_source (&call_id->idle_id);
+	if (call_id->dbus_parameters)
+		g_variant_unref (g_steal_pointer (&call_id->dbus_parameters));
+
+	if (call_id->dbus_cancellable) {
+		/* we have a pending D-Bus call. We keep the call-id instance alive
+		 * for _call_check_authorize_cb() */
+		g_cancellable_cancel (call_id->dbus_cancellable);
+		return;
+	}
+
+	g_object_unref (call_id->self);
+	g_slice_free (NMAuthManagerCallId, call_id);
 }
 
 static void
-_call_check_authorization_complete_with_error (CheckAuthData *data,
-                                               const char *error_message)
+_call_id_invoke_callback (NMAuthManagerCallId *call_id,
+                          gboolean is_authorized,
+                          gboolean is_challenge,
+                          GError *error)
 {
-	NMAuthManager *self = data->self;
-
-	_LOGD ("call[%u]: CheckAuthorization failed due to internal error: %s", data->call_id, error_message);
-	g_simple_async_result_set_error (data->simple,
-	                                 NM_MANAGER_ERROR,
-	                                 NM_MANAGER_ERROR_FAILED,
-	                                 "Authorization check failed: %s",
-	                                 error_message);
-
-	g_simple_async_result_complete_in_idle (data->simple);
-
-	_check_auth_data_free (data);
+	c_list_unlink (&call_id->calls_lst);
+
+	call_id->callback (call_id->self,
+	                   call_id,
+	                   is_authorized,
+	                   is_challenge,
+	                   error,
+	                   call_id->user_data);
+	_call_id_free (call_id);
 }
 
 static void
-cancel_check_authorization_cb (GDBusProxy *proxy,
+cancel_check_authorization_cb (GObject *proxy,
                                GAsyncResult *res,
                                gpointer user_data)
 {
-	NMAuthManager *self = user_data;
-	GVariant *value;
-	GError *error= NULL;
-
-	value = g_dbus_proxy_call_finish (proxy, res, &error);
-	if (value == NULL) {
-		g_dbus_error_strip_remote_error (error);
-		_LOGD ("Error cancelling authorization check: %s", error->message);
-		g_error_free (error);
-	} else
-		g_variant_unref (value);
+	NMAuthManagerCallId *call_id = user_data;
+	gs_unref_variant GVariant *value = NULL;
+	gs_free_error GError *error= NULL;
+
+	value = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		_LOG2T (call_id, "cancel request was cancelled");
+	else if (error)
+		_LOG2T (call_id, "cancel request failed: %s", error->message);
+	else
+		_LOG2T (call_id, "cancel request succeeded");
 
-	g_object_unref (self);
+	_call_id_free (call_id);
 }
 
-typedef struct {
-	gboolean is_authorized;
-	gboolean is_challenge;
-} CheckAuthorizationResult;
-
 static void
-check_authorization_cb (GDBusProxy *proxy,
-                        GAsyncResult *res,
-                        gpointer user_data)
+_call_check_authorize_cb (GObject *proxy,
+                          GAsyncResult *res,
+                          gpointer user_data)
 {
-	CheckAuthData *data = user_data;
-	NMAuthManager *self = data->self;
-	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
-	GVariant *value;
-	GError *error = NULL;
-
-	value = _nm_dbus_proxy_call_finish (proxy, res, G_VARIANT_TYPE ("((bba{ss}))"), &error);
-	if (value == NULL) {
-		if (data->cancellation_id != NULL &&
-		    (   g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)
-		     && !g_dbus_error_is_remote_error (error))) {
-			_LOGD ("call[%u]: CheckAuthorization cancelled", data->call_id);
-			g_dbus_proxy_call (priv->proxy,
-			                   "CancelCheckAuthorization",
-			                   g_variant_new ("(s)", data->cancellation_id),
-			                   G_DBUS_CALL_FLAGS_NONE,
-			                   -1,
-			                   NULL, /* GCancellable */
-			                   (GAsyncReadyCallback) cancel_check_authorization_cb,
-			                   g_object_ref (self));
-		} else
-			_LOGD ("call[%u]: CheckAuthorization failed: %s", data->call_id, error->message);
-		g_dbus_error_strip_remote_error (error);
-		g_simple_async_result_set_error (data->simple,
-		                                 NM_MANAGER_ERROR,
-		                                 NM_MANAGER_ERROR_FAILED,
-		                                 "Authorization check failed: %s",
-		                                 error->message);
-		g_error_free (error);
-	} else {
-		CheckAuthorizationResult *result;
+	NMAuthManagerCallId *call_id = user_data;
+	NMAuthManager *self;
+	NMAuthManagerPrivate *priv;
+	gs_unref_variant GVariant *value = NULL;
+	gs_free_error GError *error = NULL;
+	gboolean is_authorized = FALSE;
+	gboolean is_challenge = FALSE;
 
-		result = g_new0 (CheckAuthorizationResult, 1);
+	/* we need to clear the cancelable, to signal for _call_id_free() that we
+	 * are not in a pending call.
+	 *
+	 * Note how _call_id_free() kept call-id alive, even if the request was
+	 * already cancelled. */
+	g_clear_object (&call_id->dbus_cancellable);
 
-		g_variant_get (value,
-		               "((bb@a{ss}))",
-		               &result->is_authorized,
-		               &result->is_challenge,
-		               NULL);
-		g_variant_unref (value);
+	self = call_id->self;
+	priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
+
+	value = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, G_VARIANT_TYPE ("((bba{ss}))"), &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		/* call_id was cancelled externally, but _call_id_free() kept call_id
+		 * alive (and it has still the reference on @self. */
+
+		if (!priv->cancel_cancellable) {
+			/* we do a forced shutdown. There is no more time for cancelling... */
+			_call_id_free (call_id);
+
+			/* this shouldn't really happen, because:
+			 * _call_check_authorize() only scheduled the D-Bus request at a time when
+			 * cancel_cancellable was still set. It means, somebody called force-shutdown
+			 * after call-id was schedule.
+			 * force-shutdown should only be called after:
+			 *   - cancel all pending requests
+			 *   - give enough time to cancel the request and schedule a D-Bus call
+			 *     to CancelCheckAuthorization (below), before issuing force-shutdown. */
+			g_return_if_reached ();
+		}
 
-		_LOGD ("call[%u]: CheckAuthorization succeeded: (is_authorized=%d, is_challenge=%d)", data->call_id, result->is_authorized, result->is_challenge);
-		g_simple_async_result_set_op_res_gpointer (data->simple, result, g_free);
+		g_dbus_proxy_call (priv->proxy,
+		                   "CancelCheckAuthorization",
+		                   g_variant_new ("(s)",
+		                                  cancellation_id_to_str_a (call_id->call_numid)),
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   CANCELLATION_TIMEOUT_MS,
+		                   priv->cancel_cancellable,
+		                   cancel_check_authorization_cb,
+		                   call_id);
+		return;
 	}
 
-	g_simple_async_result_complete (data->simple);
+	if (!error) {
+		g_variant_get (value,
+		               "((bb@a{ss}))",
+		               &is_authorized,
+		               &is_challenge,
+		               NULL);
+		_LOG2T (call_id, "completed: authorized=%d, challenge=%d",
+		        is_authorized, is_challenge);
+	} else
+		_LOG2T (call_id, "completed: failed: %s", error->message);
 
-	_check_auth_data_free (data);
+	_call_id_invoke_callback (call_id, is_authorized, is_challenge, error);
 }
 
 static void
-_call_check_authorization (CheckAuthData *data)
+_call_check_authorize (NMAuthManagerCallId *call_id)
 {
-	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (data->self);
+	NMAuthManager *self = call_id->self;
+	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
+
+	nm_assert (call_id->dbus_parameters);
+	nm_assert (g_variant_is_floating (call_id->dbus_parameters));
+	nm_assert (!call_id->dbus_cancellable);
+
+	call_id->dbus_cancellable = g_cancellable_new ();
+
+	nm_assert (priv->cancel_cancellable);
 
 	g_dbus_proxy_call (priv->proxy,
 	                   "CheckAuthorization",
-	                   data->dbus_parameters,
+	                   g_steal_pointer (&call_id->dbus_parameters),
 	                   G_DBUS_CALL_FLAGS_NONE,
 	                   G_MAXINT, /* no timeout */
-	                   data->cancellable,
-	                   (GAsyncReadyCallback) check_authorization_cb,
-	                   data);
-	g_clear_object (&data->cancellable);
-	data->dbus_parameters = NULL;
+	                   call_id->dbus_cancellable,
+	                   _call_check_authorize_cb,
+	                   call_id);
 }
 
-void
-nm_auth_manager_polkit_authority_check_authorization (NMAuthManager *self,
-                                                      NMAuthSubject *subject,
-                                                      const char *action_id,
-                                                      gboolean allow_user_interaction,
-                                                      GCancellable *cancellable,
-                                                      GAsyncReadyCallback callback,
-                                                      gpointer user_data)
+static gboolean
+_call_on_idle (gpointer user_data)
+{
+	NMAuthManagerCallId *call_id = user_data;
+	gs_free_error GError *error = NULL;
+	gboolean is_authorized = FALSE;
+	gboolean is_challenge = FALSE;
+	const char *error_msg = NULL;
+
+	call_id->idle_id = 0;
+	if (call_id->idle_reason == IDLE_REASON_AUTHORIZED) {
+		is_authorized = TRUE;
+		_LOG2T (call_id, "completed: authorized=%d, challenge=%d (simulated)",
+		        is_authorized, is_challenge);
+	} else {
+		nm_assert (call_id->idle_reason == IDLE_REASON_NO_DBUS);
+		error_msg = "failure creating GDBusProxy for authorization request";
+		_LOG2T (call_id, "completed: failed due to no D-Bus proxy");
+	}
+
+	if (error_msg)
+		g_set_error_literal (&error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, error_msg);
+	_call_id_invoke_callback (call_id, is_authorized, is_challenge, error);
+	return G_SOURCE_REMOVE;
+}
+
+/*
+ * @callback must never be invoked synchronously.
+ *
+ * @callback is always invoked exactly once, and never synchronously.
+ * You may cancel the invocation with nm_auth_manager_check_authorization_cancel(),
+ * but: you may only do so exactly once, and only before @callback is
+ * invoked. Even if you cancel the request, @callback will still be invoked
+ * (synchronously, during the _cancel() callback).
+ *
+ * The request keeps @self alive (it needs to do so, because when cancelling a
+ * request we might need to do an additional CancelCheckAuthorization call, for
+ * which @self must be live long enough).
+ */
+NMAuthManagerCallId *
+nm_auth_manager_check_authorization (NMAuthManager *self,
+                                     NMAuthSubject *subject,
+                                     const char *action_id,
+                                     gboolean allow_user_interaction,
+                                     NMAuthManagerCheckAuthorizationCallback callback,
+                                     gpointer user_data)
 {
 	NMAuthManagerPrivate *priv;
+	PolkitCheckAuthorizationFlags flags;
 	char subject_buf[64];
 	GVariantBuilder builder;
-	PolkitCheckAuthorizationFlags flags;
 	GVariant *subject_value;
 	GVariant *details_value;
-	CheckAuthData *data;
+	NMAuthManagerCallId *call_id;
 
-	g_return_if_fail (NM_IS_AUTH_MANAGER (self));
-	g_return_if_fail (NM_IS_AUTH_SUBJECT (subject));
-	g_return_if_fail (nm_auth_subject_is_unix_process (subject));
-	g_return_if_fail (action_id != NULL);
-	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+	g_return_val_if_fail (NM_IS_AUTH_MANAGER (self), NULL);
+	g_return_val_if_fail (NM_IN_SET (nm_auth_subject_get_subject_type (subject),
+	                                 NM_AUTH_SUBJECT_TYPE_INTERNAL,
+	                                 NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS),
+	                      NULL);
+	g_return_val_if_fail (action_id, NULL);
 
 	priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
 
-	g_return_if_fail (priv->polkit_enabled);
+	g_return_val_if_fail (!priv->disposing, NULL);
+	g_return_val_if_fail (!priv->shutting_down, NULL);
 
 	flags = allow_user_interaction
 	    ? POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION
 	    : POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE;
 
-	subject_value = nm_auth_subject_unix_process_to_polkit_gvariant (subject);
-	nm_assert (g_variant_is_floating (subject_value));
-
-	/* ((PolkitDetails *)NULL) */
-	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
-	details_value = g_variant_builder_end (&builder);
-
-	data = g_new0 (CheckAuthData, 1);
-	data->call_id = ++priv->call_id_counter;
-	data->self = g_object_ref (self);
-	data->simple = g_simple_async_result_new (G_OBJECT (self),
-	                                          callback,
-	                                          user_data,
-	                                          nm_auth_manager_polkit_authority_check_authorization);
-	if (cancellable != NULL) {
-		data->cancellation_id = g_strdup_printf ("cancellation-id-%u", data->call_id);
-		data->cancellable = g_object_ref (cancellable);
-	}
-
-	data->dbus_parameters = g_variant_new ("(@(sa{sv})s@a{ss}us)",
-	                                       subject_value,
-	                                       action_id,
-	                                       details_value,
-	                                       (guint32) flags,
-	                                       data->cancellation_id != NULL ? data->cancellation_id : "");
-
-	if (priv->new_proxy_cancellable) {
-		_LOGD ("call[%u]: CheckAuthorization(%s), subject=%s (wait for proxy)", data->call_id, action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
-
-		priv->queued_calls = g_slist_prepend (priv->queued_calls, data);
-	} else if (!priv->proxy) {
-		_LOGD ("call[%u]: CheckAuthorization(%s), subject=%s (fails due to invalid DBUS proxy)", data->call_id, action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
-
-		_call_check_authorization_complete_with_error (data, "invalid DBUS proxy");
+	call_id = g_slice_new0 (NMAuthManagerCallId);
+	call_id->self = g_object_ref (self);
+	call_id->callback = callback;
+	call_id->user_data = user_data;
+	call_id->call_numid = ++priv->call_numid_counter;
+	c_list_link_tail (&priv->calls_lst_head, &call_id->calls_lst);
+
+	if (!priv->polkit_enabled) {
+		_LOG2T (call_id, "CheckAuthorization(%s), subject=%s (succeeding due to polkit authorization disabled)", action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
+		call_id->idle_reason = IDLE_REASON_AUTHORIZED;
+		call_id->idle_id = g_idle_add (_call_on_idle, call_id);
+	} else if (nm_auth_subject_is_internal (subject)) {
+		_LOG2T (call_id, "CheckAuthorization(%s), subject=%s (succeeding for internal request)", action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
+		call_id->idle_reason = IDLE_REASON_AUTHORIZED;
+		call_id->idle_id = g_idle_add (_call_on_idle, call_id);
+	} else if (nm_auth_subject_get_unix_process_uid (subject) == 0) {
+		_LOG2T (call_id, "CheckAuthorization(%s), subject=%s (succeeding for root)", action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
+		call_id->idle_reason = IDLE_REASON_AUTHORIZED;
+		call_id->idle_id = g_idle_add (_call_on_idle, call_id);
+	} else if (   !priv->proxy
+	           && !priv->new_proxy_cancellable) {
+		_LOG2T (call_id, "CheckAuthorization(%s), subject=%s (failing due to invalid DBUS proxy)", action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
+		call_id->idle_reason = IDLE_REASON_NO_DBUS;
+		call_id->idle_id = g_idle_add (_call_on_idle, call_id);
 	} else {
-		_LOGD ("call[%u]: CheckAuthorization(%s), subject=%s", data->call_id, action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
-
-		_call_check_authorization (data);
+		subject_value = nm_auth_subject_unix_process_to_polkit_gvariant (subject);
+		nm_assert (g_variant_is_floating (subject_value));
+
+		/* ((PolkitDetails *)NULL) */
+		g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
+		details_value = g_variant_builder_end (&builder);
+
+		call_id->dbus_parameters = g_variant_new ("(@(sa{sv})s@a{ss}us)",
+		                                          subject_value,
+		                                          action_id,
+		                                          details_value,
+		                                          (guint32) flags,
+		                                          cancellation_id_to_str_a (call_id->call_numid));
+		if (!priv->proxy) {
+			_LOG2T (call_id, "CheckAuthorization(%s), subject=%s (wait for proxy)", action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
+		} else {
+			_LOG2T (call_id, "CheckAuthorization(%s), subject=%s", action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
+			_call_check_authorize (call_id);
+		}
 	}
+
+	return call_id;
 }
 
-gboolean
-nm_auth_manager_polkit_authority_check_authorization_finish (NMAuthManager *self,
-                                                             GAsyncResult *res,
-                                                             gboolean *out_is_authorized,
-                                                             gboolean *out_is_challenge,
-                                                             GError **error)
+void
+nm_auth_manager_check_authorization_cancel (NMAuthManagerCallId *call_id)
 {
-	gboolean success = FALSE;
-	gboolean is_authorized = FALSE;
-	gboolean is_challenge = FALSE;
+	NMAuthManager *self;
+	gs_free_error GError *error = NULL;
 
-	g_return_val_if_fail (NM_IS_AUTH_MANAGER (self), FALSE);
-	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (res), FALSE);
-	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	g_return_if_fail (call_id);
 
-	if (!g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) {
-		CheckAuthorizationResult *result;
+	self = call_id->self;
 
-		result = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
-		is_authorized = !!result->is_authorized;
-		is_challenge = !!result->is_challenge;
-		success = TRUE;
-	}
-	g_assert ((success && !error) || (!success || error));
+	g_return_if_fail (NM_IS_AUTH_MANAGER (self));
+	g_return_if_fail (!c_list_is_empty (&call_id->calls_lst));
+
+	nm_assert (c_list_contains (&NM_AUTH_MANAGER_GET_PRIVATE (self)->calls_lst_head, &call_id->calls_lst));
 
-	if (out_is_authorized)
-		*out_is_authorized = is_authorized;
-	if (out_is_challenge)
-		*out_is_challenge = is_challenge;
-	return success;
+	nm_utils_error_set_cancelled (&error, FALSE, "NMAuthManager");
+	_LOG2T (call_id, "completed: failed due to call cancelled");
+	_call_id_invoke_callback (call_id,
+	                          FALSE,
+	                          FALSE,
+	                          error);
 }
 
 /*****************************************************************************/
@@ -350,14 +443,14 @@ static void
 _emit_changed_signal (NMAuthManager *self)
 {
 	_LOGD ("emit changed signal");
-	g_signal_emit_by_name (self, NM_AUTH_MANAGER_SIGNAL_CHANGED);
+	g_signal_emit (self, signals[CHANGED_SIGNAL], 0);
 }
 
 static void
 _log_name_owner (NMAuthManager *self, char **out_name_owner)
 {
 	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
-	char *name_owner;
+	gs_free char *name_owner = NULL;
 
 	name_owner = g_dbus_proxy_get_name_owner (priv->proxy);
 	if (name_owner)
@@ -365,10 +458,7 @@ _log_name_owner (NMAuthManager *self, char **out_name_owner)
 	else
 		_LOGD ("dbus name owner: none");
 
-	if (out_name_owner)
-		*out_name_owner = name_owner;
-	else
-		g_free (name_owner);
+	NM_SET_OUT (out_name_owner, g_steal_pointer (&name_owner));
 }
 
 static void
@@ -377,20 +467,16 @@ _dbus_on_name_owner_notify_cb (GObject    *object,
                                gpointer    user_data)
 {
 	NMAuthManager *self = user_data;
-	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
-	char *name_owner;
+	gs_free char *name_owner = NULL;
 
-	g_return_if_fail (priv->proxy == (void *) object);
+	nm_assert (NM_AUTH_MANAGER_GET_PRIVATE (self)->proxy == (GDBusProxy *) object);
 
 	_log_name_owner (self, &name_owner);
-
 	if (!name_owner) {
 		/* when the name disappears, we also want to raise a emit signal.
 		 * When it appears, we raise one already. */
 		_emit_changed_signal (self);
 	}
-
-	g_free (name_owner);
 }
 
 static void
@@ -398,9 +484,8 @@ _dbus_on_changed_signal_cb (GDBusProxy *proxy,
                             gpointer    user_data)
 {
 	NMAuthManager *self = user_data;
-	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
 
-	g_return_if_fail (priv->proxy == proxy);
+	nm_assert (NM_AUTH_MANAGER_GET_PRIVATE (self)->proxy == proxy);
 
 	_LOGD ("dbus signal: \"Changed\"");
 	_emit_changed_signal (self);
@@ -411,49 +496,39 @@ _dbus_new_proxy_cb (GObject *source_object,
                     GAsyncResult *res,
                     gpointer user_data)
 {
-	NMAuthManager **p_self = user_data;
-	NMAuthManager *self = NULL;
+	NMAuthManager *self;
 	NMAuthManagerPrivate *priv;
-	GError *error = NULL;
+	gs_free GError *error = NULL;
 	GDBusProxy *proxy;
-	CheckAuthData *data;
+	NMAuthManagerCallId *call_id;
 
 	proxy = g_dbus_proxy_new_for_bus_finish  (res, &error);
 
-	if (!*p_self) {
-		_LOGD ("_dbus_new_proxy_cb(): manager destroyed before callback finished. Abort");
-		g_clear_object (&proxy);
-		g_clear_error (&error);
-		g_free (p_self);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		return;
-	}
-	self = *p_self;
-	g_object_remove_weak_pointer (G_OBJECT (self), (void **)p_self);
-	g_free (p_self);
 
+	self = user_data;
 	priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
 
-	g_return_if_fail (priv->new_proxy_cancellable);
-	g_return_if_fail (!priv->proxy);
-
+	priv->proxy = proxy;
 	g_clear_object (&priv->new_proxy_cancellable);
 
-	priv->queued_calls = g_slist_reverse (priv->queued_calls);
-
-	priv->proxy = proxy;
 	if (!priv->proxy) {
-		_LOGE ("could not get polkit proxy: %s", error->message);
-		g_clear_error (&error);
-
-		while (priv->queued_calls) {
-			data = priv->queued_calls->data;
-			priv->queued_calls = g_slist_remove (priv->queued_calls, data);
-
-			_call_check_authorization_complete_with_error (data, "error creating DBUS proxy");
+		_LOGE ("could not create polkit proxy: %s", error->message);
+
+again:
+		c_list_for_each_entry (call_id, &priv->calls_lst_head, calls_lst) {
+			if (call_id->dbus_parameters) {
+				_LOG2T (call_id, "completed: failed due to no D-Bus proxy after startup");
+				_call_id_invoke_callback (call_id, FALSE, FALSE, error);
+				goto again;
+			}
 		}
 		return;
 	}
 
+	priv->cancel_cancellable = g_cancellable_new ();
+
 	g_signal_connect (priv->proxy,
 	                  "notify::g-name-owner",
 	                  G_CALLBACK (_dbus_on_name_owner_notify_cb),
@@ -464,17 +539,16 @@ _dbus_new_proxy_cb (GObject *source_object,
 
 	_log_name_owner (self, NULL);
 
-	while (priv->queued_calls) {
-		data = priv->queued_calls->data;
-		priv->queued_calls = g_slist_remove (priv->queued_calls, data);
-		_LOGD ("call[%u]: CheckAuthorization invoke now", data->call_id);
-		_call_check_authorization (data);
+	c_list_for_each_entry (call_id, &priv->calls_lst_head, calls_lst) {
+		if (call_id->dbus_parameters) {
+			_LOG2T (call_id, "CheckAuthorization invoke now");
+			_call_check_authorize (call_id);
+		}
 	}
+
 	_emit_changed_signal (self);
 }
 
-#endif
-
 /*****************************************************************************/
 
 NMAuthManager *
@@ -485,23 +559,42 @@ nm_auth_manager_get ()
 	return singleton_instance;
 }
 
-/*****************************************************************************/
-
-static void
-get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+void
+nm_auth_manager_force_shutdown (NMAuthManager *self)
 {
-	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE ((NMAuthManager *) object);
+	NMAuthManagerPrivate *priv;
 
-	switch (prop_id) {
-	case PROP_POLKIT_ENABLED:
-		g_value_set_boolean (value, priv->polkit_enabled);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
+	g_return_if_fail (NM_IS_AUTH_MANAGER (self));
+
+	priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
+
+	/* while we have pending requests (NMAuthManagerCallId), the instance
+	 * is kept alive.
+	 *
+	 * Even if the caller cancells all pending call-ids, we still need to keep
+	 * a reference to self, in order to handle pending CancelCheckAuthorization
+	 * requests.
+	 *
+	 * To do a corrdinated shutdown, do the following:
+	 * - cancel all pending NMAuthManagerCallId requests.
+	 * - ensure everybody unrefs the NMAuthManager instance. If by that, the instance
+	 *   gets destroyed, the shutdown already completed successfully.
+	 * - Otherwise, the object is kept alive by pending CancelCheckAuthorization requests.
+	 *   wait a certain timeout (1 second) for all requests to complete (by watching
+	 *   for destruction of NMAuthManager).
+	 * - if that doesn't happen within timeout, issue nm_auth_manager_force_shutdown() and
+	 *   wait longer. After that, soon the instance should be destroyed and you
+	 *   did a successful shutdown.
+	 * - if the instance was still not destroyed within a short timeout, you leaked
+	 *   resources. You cannot properly shutdown.
+	 */
+
+	priv->shutting_down = TRUE;
+	nm_clear_g_cancellable (&priv->cancel_cancellable);
 }
 
+/*****************************************************************************/
+
 static void
 set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 {
@@ -523,6 +616,9 @@ set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *p
 static void
 nm_auth_manager_init (NMAuthManager *self)
 {
+	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
+
+	c_list_init (&priv->calls_lst_head);
 }
 
 static void
@@ -533,16 +629,10 @@ constructed (GObject *object)
 
 	G_OBJECT_CLASS (nm_auth_manager_parent_class)->constructed (object);
 
-#if WITH_POLKIT
 	_LOGD ("create auth-manager: polkit %s", priv->polkit_enabled ? "enabled" : "disabled");
 
 	if (priv->polkit_enabled) {
-		NMAuthManager **p_self;
-
 		priv->new_proxy_cancellable = g_cancellable_new ();
-		p_self = g_new (NMAuthManager *, 1);
-		*p_self = self;
-		g_object_add_weak_pointer (G_OBJECT (self), (void **) p_self);
 		g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
 		                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
 		                          NULL,
@@ -551,14 +641,8 @@ constructed (GObject *object)
 		                          POLKIT_INTERFACE,
 		                          priv->new_proxy_cancellable,
 		                          _dbus_new_proxy_cb,
-		                          p_self);
+		                          self);
 	}
-#else
-	if (priv->polkit_enabled)
-		_LOGW ("create auth-manager: polkit disabled at compile time. All authentication requests will fail");
-	else
-		_LOGD ("create auth-manager: polkit disabled at compile time");
-#endif
 }
 
 NMAuthManager *
@@ -585,23 +669,21 @@ static void
 dispose (GObject *object)
 {
 	NMAuthManager* self = NM_AUTH_MANAGER (object);
-#if WITH_POLKIT
 	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
-#endif
 
 	_LOGD ("dispose");
 
-#if WITH_POLKIT
-	/* since we take a reference for each queued call, we don't expect to have any queued calls in dispose() */
-	g_assert (!priv->queued_calls);
+	nm_assert (c_list_is_empty (&priv->calls_lst_head));
+
+	priv->disposing = TRUE;
 
 	nm_clear_g_cancellable (&priv->new_proxy_cancellable);
+	nm_clear_g_cancellable (&priv->cancel_cancellable);
 
 	if (priv->proxy) {
 		g_signal_handlers_disconnect_by_data (priv->proxy, self);
 		g_clear_object (&priv->proxy);
 	}
-#endif
 
 	G_OBJECT_CLASS (nm_auth_manager_parent_class)->dispose (object);
 }
@@ -611,7 +693,6 @@ nm_auth_manager_class_init (NMAuthManagerClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 	object_class->constructed = constructed;
 	object_class->dispose = dispose;
@@ -619,7 +700,7 @@ nm_auth_manager_class_init (NMAuthManagerClass *klass)
 	obj_properties[PROP_POLKIT_ENABLED] =
 	     g_param_spec_boolean (NM_AUTH_MANAGER_POLKIT_ENABLED, "", "",
 	                           FALSE,
-	                           G_PARAM_READWRITE |
+	                           G_PARAM_WRITABLE |
 	                           G_PARAM_CONSTRUCT_ONLY |
 	                           G_PARAM_STATIC_STRINGS);
 
@@ -628,11 +709,7 @@ nm_auth_manager_class_init (NMAuthManagerClass *klass)
 	signals[CHANGED_SIGNAL] = g_signal_new (NM_AUTH_MANAGER_SIGNAL_CHANGED,
 	                                        NM_TYPE_AUTH_MANAGER,
 	                                        G_SIGNAL_RUN_LAST,
-	                                        0,                      /* class offset     */
-	                                        NULL,                   /* accumulator      */
-	                                        NULL,                   /* accumulator data */
+	                                        0, NULL, NULL,
 	                                        g_cclosure_marshal_VOID__VOID,
-	                                        G_TYPE_NONE,
-	                                        0);
+	                                        G_TYPE_NONE, 0);
 }
-