summary refs log tree commit diff
path: root/src/nm-auth-utils.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-04-23 18:00:21 +0200
committerMichael Biebl <biebl@debian.org>2018-04-23 18:00:21 +0200
commitf60117b41d5433be1b4a96d82cd11d0c3dce9b63 (patch)
tree2dd55c4ab0fdcbe9cddb26adb4a554b1a45c73c8 /src/nm-auth-utils.c
parent7e9ff09fcb2366b383b7ebbec80d2f6fff117290 (diff)
New upstream version 1.11.3 upstream/1.11.3
Diffstat (limited to 'src/nm-auth-utils.c')
-rw-r--r--src/nm-auth-utils.c489
1 files changed, 227 insertions, 262 deletions
diff --git a/src/nm-auth-utils.c b/src/nm-auth-utils.c
index f1aff430..b41f6efa 100644
--- a/src/nm-auth-utils.c
+++ b/src/nm-auth-utils.c
@@ -24,48 +24,83 @@
 
 #include <string.h>
 
-#include "nm-utils/nm-hash-utils.h"
+#include "nm-utils/nm-c-list.h"
+
 #include "nm-setting-connection.h"
 #include "nm-auth-subject.h"
 #include "nm-auth-manager.h"
 #include "nm-session-monitor.h"
 
+/*****************************************************************************/
+
 struct NMAuthChain {
-	guint32 refcount;
-	GSList *calls;
-	GHashTable *data;
+	GHashTable *data_hash;
+
+	CList auth_call_lst_head;
 
 	GDBusMethodInvocation *context;
 	NMAuthSubject *subject;
-	GError *error;
-
-	guint idle_id;
-	gboolean done;
 
 	NMAuthChainResultFunc done_func;
 	gpointer user_data;
+
+	guint32 refcount;
+
+	bool done:1;
 };
 
 typedef struct {
+	CList auth_call_lst;
 	NMAuthChain *chain;
-	GCancellable *cancellable;
+	NMAuthManagerCallId *call_id;
 	char *permission;
-	guint call_idle_id;
 } AuthCall;
 
+/*****************************************************************************/
+
+static void
+_ASSERT_call (AuthCall *call)
+{
+	nm_assert (call);
+	nm_assert (call->chain);
+	nm_assert (nm_c_list_contains_entry (&call->chain->auth_call_lst_head, call, auth_call_lst));
+}
+
+/*****************************************************************************/
+
+static void
+auth_call_free (AuthCall *call)
+{
+	if (call->call_id)
+		nm_auth_manager_check_authorization_cancel (call->call_id);
+	c_list_unlink_stale (&call->auth_call_lst);
+	g_free (call->permission);
+	g_slice_free (AuthCall, call);
+}
+
+/*****************************************************************************/
+
 typedef struct {
+
+	/* must be the first field. */
+	const char *tag;
+
 	gpointer data;
 	GDestroyNotify destroy;
+	char tag_data[];
 } ChainData;
 
 static ChainData *
-chain_data_new (gpointer data, GDestroyNotify destroy)
+chain_data_new (const char *tag, gpointer data, GDestroyNotify destroy)
 {
 	ChainData *tmp;
+	gsize l = strlen (tag);
 
-	tmp = g_slice_new (ChainData);
+	tmp = g_malloc (sizeof (ChainData) + l + 1);
+	tmp->tag = &tmp->tag_data[0];
 	tmp->data = data;
 	tmp->destroy = destroy;
+	memcpy (&tmp->tag_data[0], tag, l + 1);
 	return tmp;
 }
 
@@ -76,69 +111,7 @@ chain_data_free (gpointer data)
 
 	if (tmp->destroy)
 		tmp->destroy (tmp->data);
-	memset (tmp, 0, sizeof (ChainData));
-	g_slice_free (ChainData, tmp);
-}
-
-static gboolean
-auth_chain_finish (gpointer user_data)
-{
-	NMAuthChain *self = user_data;
-
-	self->idle_id = 0;
-	self->done = TRUE;
-
-	/* Ensure we stay alive across the callback */
-	self->refcount++;
-	self->done_func (self, self->error, self->context, self->user_data);
-	nm_auth_chain_unref (self);
-	return FALSE;
-}
-
-/* Creates the NMAuthSubject automatically */
-NMAuthChain *
-nm_auth_chain_new_context (GDBusMethodInvocation *context,
-                           NMAuthChainResultFunc done_func,
-                           gpointer user_data)
-{
-	NMAuthSubject *subject;
-	NMAuthChain *chain;
-
-	g_return_val_if_fail (context != NULL, NULL);
-
-	subject = nm_auth_subject_new_unix_process_from_context (context);
-	if (!subject)
-		return NULL;
-
-	chain = nm_auth_chain_new_subject (subject,
-	                                   context,
-	                                   done_func,
-	                                   user_data);
-	g_object_unref (subject);
-	return chain;
-}
-
-/* Requires an NMAuthSubject */
-NMAuthChain *
-nm_auth_chain_new_subject (NMAuthSubject *subject,
-                           GDBusMethodInvocation *context,
-                           NMAuthChainResultFunc done_func,
-                           gpointer user_data)
-{
-	NMAuthChain *self;
-
-	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), NULL);
-	g_return_val_if_fail (nm_auth_subject_is_unix_process (subject) || nm_auth_subject_is_internal (subject), NULL);
-
-	self = g_slice_new0 (NMAuthChain);
-	self->refcount = 1;
-	self->data = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, chain_data_free);
-	self->done_func = done_func;
-	self->user_data = user_data;
-	self->context = context ? g_object_ref (context) : NULL;
-	self->subject = g_object_ref (subject);
-
-	return self;
+	g_free (tmp);
 }
 
 static gpointer
@@ -146,15 +119,17 @@ _get_data (NMAuthChain *self, const char *tag)
 {
 	ChainData *tmp;
 
-	tmp = g_hash_table_lookup (self->data, tag);
+	if (!self->data_hash)
+		return NULL;
+	tmp = g_hash_table_lookup (self->data_hash, &tag);
 	return tmp ? tmp->data : NULL;
 }
 
 gpointer
 nm_auth_chain_get_data (NMAuthChain *self, const char *tag)
 {
-	g_return_val_if_fail (self != NULL, NULL);
-	g_return_val_if_fail (tag != NULL, NULL);
+	g_return_val_if_fail (self, NULL);
+	g_return_val_if_fail (tag, NULL);
 
 	return _get_data (self, tag);
 }
@@ -175,19 +150,22 @@ nm_auth_chain_steal_data (NMAuthChain *self, const char *tag)
 {
 	ChainData *tmp;
 	gpointer value = NULL;
-	void *orig_key;
-
-	g_return_val_if_fail (self != NULL, NULL);
-	g_return_val_if_fail (tag != NULL, NULL);
-
-	if (g_hash_table_lookup_extended (self->data, tag, &orig_key, (gpointer)&tmp)) {
-		g_hash_table_steal (self->data, tag);
-		value = tmp->data;
-		/* Make sure the destroy handler isn't called when freeing */
-		tmp->destroy = NULL;
-		chain_data_free (tmp);
-		g_free (orig_key);
-	}
+
+	g_return_val_if_fail (self, NULL);
+	g_return_val_if_fail (tag, NULL);
+
+	if (!self->data_hash)
+		return NULL;
+
+	tmp = g_hash_table_lookup (self->data_hash, &tag);
+	if (!tmp)
+		return NULL;
+
+	value = tmp->data;
+
+	/* Make sure the destroy handler isn't called when freeing */
+	tmp->destroy = NULL;
+	g_hash_table_remove (self->data_hash, tmp);
 	return value;
 }
 
@@ -197,165 +175,105 @@ nm_auth_chain_set_data (NMAuthChain *self,
                         gpointer data,
                         GDestroyNotify data_destroy)
 {
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (tag != NULL);
-
-	if (data == NULL)
-		g_hash_table_remove (self->data, tag);
-	else {
-		g_hash_table_insert (self->data,
-		                     g_strdup (tag),
-		                     chain_data_new (data, data_destroy));
+	g_return_if_fail (self);
+	g_return_if_fail (tag);
+
+	if (data == NULL) {
+		if (self->data_hash)
+			g_hash_table_remove (self->data_hash, &tag);
+	} else {
+		if (!self->data_hash) {
+			self->data_hash = g_hash_table_new_full (nm_pstr_hash, nm_pstr_equal,
+			                                         NULL, chain_data_free);
+		}
+		g_hash_table_add (self->data_hash,
+		                  chain_data_new (tag, data, data_destroy));
 	}
 }
 
-gulong
-nm_auth_chain_get_data_ulong (NMAuthChain *self, const char *tag)
-{
-	gulong *data;
+/*****************************************************************************/
 
-	g_return_val_if_fail (self != NULL, 0);
-	g_return_val_if_fail (tag != NULL, 0);
-
-	data = _get_data (self, tag);
-	return data ? *data : 0ul;
-}
-
-void
-nm_auth_chain_set_data_ulong (NMAuthChain *self,
-                              const char *tag,
-                              gulong data)
+NMAuthCallResult
+nm_auth_chain_get_result (NMAuthChain *self, const char *permission)
 {
-	gulong *ptr;
+	gpointer data;
 
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (tag != NULL);
+	g_return_val_if_fail (self, NM_AUTH_CALL_RESULT_UNKNOWN);
+	g_return_val_if_fail (permission, NM_AUTH_CALL_RESULT_UNKNOWN);
 
-	ptr = g_malloc (sizeof (*ptr));
-	*ptr = data;
-	nm_auth_chain_set_data (self, tag, ptr, g_free);
+	data = _get_data (self, permission);
+	return data ? GPOINTER_TO_UINT (data) : NM_AUTH_CALL_RESULT_UNKNOWN;
 }
 
 NMAuthSubject *
 nm_auth_chain_get_subject (NMAuthChain *self)
 {
-	g_return_val_if_fail (self != NULL, NULL);
+	g_return_val_if_fail (self, NULL);
 
 	return self->subject;
 }
 
-NMAuthCallResult
-nm_auth_chain_get_result (NMAuthChain *self, const char *permission)
-{
-	gpointer data;
-
-	g_return_val_if_fail (self != NULL, NM_AUTH_CALL_RESULT_UNKNOWN);
-	g_return_val_if_fail (permission != NULL, NM_AUTH_CALL_RESULT_UNKNOWN);
+/*****************************************************************************/
 
-	data = _get_data (self, permission);
-	return data ? GPOINTER_TO_UINT (data) : NM_AUTH_CALL_RESULT_UNKNOWN;
-}
-
-static AuthCall *
-auth_call_new (NMAuthChain *chain, const char *permission)
+static gboolean
+auth_chain_finish (NMAuthChain *self)
 {
-	AuthCall *call;
+	self->done = TRUE;
 
-	call = g_slice_new0 (AuthCall);
-	call->chain = chain;
-	call->permission = g_strdup (permission);
-	return call;
+	/* Ensure we stay alive across the callback */
+	nm_assert (self->refcount == 1);
+	self->refcount++;
+	self->done_func (self, NULL, self->context, self->user_data);
+	nm_assert (NM_IN_SET (self->refcount, 1, 2));
+	nm_auth_chain_destroy (self);
+	return FALSE;
 }
 
 static void
-auth_call_free (AuthCall *call)
-{
-	g_free (call->permission);
-	g_clear_object (&call->cancellable);
-	g_slice_free (AuthCall, call);
-}
-
-static gboolean
 auth_call_complete (AuthCall *call)
 {
 	NMAuthChain *self;
 
-	g_return_val_if_fail (call, G_SOURCE_REMOVE);
+	_ASSERT_call (call);
 
 	self = call->chain;
 
-	g_return_val_if_fail (self, G_SOURCE_REMOVE);
-	g_return_val_if_fail (g_slist_find (self->calls, call), G_SOURCE_REMOVE);
-
-	self->calls = g_slist_remove (self->calls, call);
+	nm_assert (!self->done);
 
-	if (!self->calls) {
-		g_assert (!self->idle_id && !self->done);
-		self->idle_id = g_idle_add (auth_chain_finish, self);
-	}
 	auth_call_free (call);
-	return FALSE;
-}
 
-static void
-auth_call_cancel (gpointer user_data)
-{
-	AuthCall *call = user_data;
-
-	if (nm_clear_g_cancellable (&call->cancellable)) {
-		/* we don't free call immediately. Instead we cancel the async operation
-		 * and set cancellable to NULL. pk_call_cb() will check for this and
-		 * do the final cleanup. */
-	} else {
-		g_source_remove (call->call_idle_id);
-		auth_call_free (call);
+	if (c_list_is_empty (&self->auth_call_lst_head)) {
+		/* we are on an idle-handler or a clean call-stack (non-reentrant). */
+		auth_chain_finish (self);
 	}
 }
 
-#if WITH_POLKIT
 static void
-pk_call_cb (GObject *object, GAsyncResult *result, gpointer user_data)
+pk_call_cb (NMAuthManager *auth_manager,
+            NMAuthManagerCallId *call_id,
+            gboolean is_authorized,
+            gboolean is_challenge,
+            GError *error,
+            gpointer user_data)
 {
-	AuthCall *call = user_data;
-	GError *error = NULL;
-	gboolean is_authorized = FALSE, is_challenge = FALSE;
-	guint call_result = NM_AUTH_CALL_RESULT_UNKNOWN;
-
-	nm_auth_manager_polkit_authority_check_authorization_finish (NM_AUTH_MANAGER (object),
-	                                                             result,
-	                                                             &is_authorized,
-	                                                             &is_challenge,
-	                                                             &error);
-
-	/* If the call is already canceled do nothing */
-	if (!call->cancellable) {
-		nm_log_dbg (LOGD_CORE, "callback already cancelled");
-		g_clear_error (&error);
-		auth_call_free (call);
+	AuthCall *call;
+	NMAuthCallResult call_result;
+
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		return;
-	}
 
-	if (error) {
-		/* Don't ruin the chain. Just leave the result unknown. */
-		nm_log_warn (LOGD_CORE, "error requesting auth for %s: %s",
-		             call->permission, error->message);
-		g_clear_error (&error);
-	} else {
-		if (is_authorized) {
-			/* Caller has the permission */
-			call_result = NM_AUTH_CALL_RESULT_YES;
-		} else if (is_challenge) {
-			/* Caller could authenticate to get the permission */
-			call_result = NM_AUTH_CALL_RESULT_AUTH;
-		} else
-			call_result = NM_AUTH_CALL_RESULT_NO;
-	}
+	call = user_data;
+
+	nm_assert (call->call_id == call_id);
+
+	call->call_id = NULL;
+
+	call_result = nm_auth_call_result_eval (is_authorized, is_challenge, error);
 
 	nm_auth_chain_set_data (call->chain, call->permission, GUINT_TO_POINTER (call_result), NULL);
 
 	auth_call_complete (call);
 }
-#endif
 
 void
 nm_auth_chain_add_call (NMAuthChain *self,
@@ -365,81 +283,109 @@ nm_auth_chain_add_call (NMAuthChain *self,
 	AuthCall *call;
 	NMAuthManager *auth_manager = nm_auth_manager_get ();
 
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (permission && *permission);
+	g_return_if_fail (self);
 	g_return_if_fail (self->subject);
+	g_return_if_fail (!self->done);
+	g_return_if_fail (permission && *permission);
 	g_return_if_fail (nm_auth_subject_is_unix_process (self->subject) || nm_auth_subject_is_internal (self->subject));
-	g_return_if_fail (!self->idle_id && !self->done);
 
-	call = auth_call_new (self, permission);
-	self->calls = g_slist_append (self->calls, call);
+	call = g_slice_new0 (AuthCall);
+	call->chain = self;
+	call->permission = g_strdup (permission);
+	c_list_link_tail (&self->auth_call_lst_head, &call->auth_call_lst);
+	call->call_id = nm_auth_manager_check_authorization (auth_manager,
+	                                                     self->subject,
+	                                                     permission,
+	                                                     allow_interaction,
+	                                                     pk_call_cb,
+	                                                     call);
+}
 
-	if (   nm_auth_subject_is_internal (self->subject)
-	    || nm_auth_subject_get_unix_process_uid (self->subject) == 0
-	    || !nm_auth_manager_get_polkit_enabled (auth_manager)) {
-		/* Root user or non-polkit always gets the permission */
-		nm_auth_chain_set_data (self, permission, GUINT_TO_POINTER (NM_AUTH_CALL_RESULT_YES), NULL);
-		call->call_idle_id = g_idle_add ((GSourceFunc) auth_call_complete, call);
-	} else {
-		/* Non-root always gets authenticated when using polkit */
-#if WITH_POLKIT
-		call->cancellable = g_cancellable_new ();
-		nm_auth_manager_polkit_authority_check_authorization (auth_manager,
-		                                                      self->subject,
-		                                                      permission,
-		                                                      allow_interaction,
-		                                                      call->cancellable,
-		                                                      pk_call_cb,
-		                                                      call);
-#else
-		if (!call->chain->error) {
-			call->chain->error = g_error_new_literal (NM_MANAGER_ERROR,
-			                                          NM_MANAGER_ERROR_FAILED,
-			                                          "Polkit support is disabled at compile time");
-		}
-		call->call_idle_id = g_idle_add ((GSourceFunc) auth_call_complete, call);
-#endif
-	}
+/*****************************************************************************/
+
+/* Creates the NMAuthSubject automatically */
+NMAuthChain *
+nm_auth_chain_new_context (GDBusMethodInvocation *context,
+                           NMAuthChainResultFunc done_func,
+                           gpointer user_data)
+{
+	NMAuthSubject *subject;
+	NMAuthChain *chain;
+
+	g_return_val_if_fail (context, NULL);
+
+	subject = nm_auth_subject_new_unix_process_from_context (context);
+	if (!subject)
+		return NULL;
+
+	chain = nm_auth_chain_new_subject (subject,
+	                                   context,
+	                                   done_func,
+	                                   user_data);
+	g_object_unref (subject);
+	return chain;
+}
+
+/* Requires an NMAuthSubject */
+NMAuthChain *
+nm_auth_chain_new_subject (NMAuthSubject *subject,
+                           GDBusMethodInvocation *context,
+                           NMAuthChainResultFunc done_func,
+                           gpointer user_data)
+{
+	NMAuthChain *self;
+
+	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), NULL);
+	nm_assert (nm_auth_subject_is_unix_process (subject) || nm_auth_subject_is_internal (subject));
+
+	self = g_slice_new0 (NMAuthChain);
+	c_list_init (&self->auth_call_lst_head);
+	self->refcount = 1;
+	self->done_func = done_func;
+	self->user_data = user_data;
+	self->context = context ? g_object_ref (context) : NULL;
+	self->subject = g_object_ref (subject);
+	return self;
 }
 
 /**
- * nm_auth_chain_unref:
+ * nm_auth_chain_destroy:
  * @self: the auth-chain
  *
- * Unrefs the auth-chain. By unrefing the auth-chain, you also cancel
+ * Destroys the auth-chain. By destroying the auth-chain, you also cancel
  * the receipt of the done-callback. IOW, the callback will not be invoked.
  *
- * The only exception is, if you call nm_auth_chain_unref() from inside
+ * The only exception is, if may call nm_auth_chain_destroy() from inside
  * the callback. In this case, @self stays alive until the callback returns.
+ *
+ * Note that you might only destroy an auth-chain exactly once, and never
+ * after the callback was handled.
  */
 void
-nm_auth_chain_unref (NMAuthChain *self)
+nm_auth_chain_destroy (NMAuthChain *self)
 {
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (self->refcount > 0);
-
-	self->refcount--;
-	if (self->refcount > 0)
-		return;
+	AuthCall *call;
 
-	if (self->idle_id)
-		g_source_remove (self->idle_id);
+	g_return_if_fail (self);
+	g_return_if_fail (NM_IN_SET (self->refcount, 1, 2));
 
-	g_object_unref (self->subject);
+	if (--self->refcount > 0)
+		return;
 
-	if (self->context)
-		g_object_unref (self->context);
+	nm_clear_g_object (&self->subject);
+	nm_clear_g_object (&self->context);
 
-	g_slist_free_full (self->calls, auth_call_cancel);
+	while ((call = c_list_first_entry (&self->auth_call_lst_head, AuthCall, auth_call_lst)))
+		auth_call_free (call);
 
-	g_clear_error (&self->error);
-	g_hash_table_destroy (self->data);
+	nm_clear_pointer (&self->data_hash, g_hash_table_destroy);
 
-	memset (self, 0, sizeof (NMAuthChain));
 	g_slice_free (NMAuthChain, self);
 }
 
-/************ utils **************/
+/******************************************************************************
+ * utils
+ *****************************************************************************/
 
 gboolean
 nm_auth_is_subject_in_acl (NMConnection *connection,
@@ -450,7 +396,7 @@ nm_auth_is_subject_in_acl (NMConnection *connection,
 	const char *user = NULL;
 	gulong uid;
 
-	g_return_val_if_fail (connection != NULL, FALSE);
+	g_return_val_if_fail (connection, FALSE);
 	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), FALSE);
 	g_return_val_if_fail (nm_auth_subject_is_internal (subject) || nm_auth_subject_is_unix_process (subject), FALSE);
 
@@ -464,8 +410,8 @@ nm_auth_is_subject_in_acl (NMConnection *connection,
 		return TRUE;
 
 	if (!nm_session_monitor_uid_to_user (uid, &user)) {
-		if (out_error_desc)
-			*out_error_desc = g_strdup_printf ("Could not determine username for uid %lu", uid);
+		NM_SET_OUT (out_error_desc,
+		            g_strdup_printf ("Could not determine username for uid %lu", uid));
 		return FALSE;
 	}
 
@@ -479,12 +425,31 @@ nm_auth_is_subject_in_acl (NMConnection *connection,
 
 	/* Match the username returned by the session check to a user in the ACL */
 	if (!nm_setting_connection_permissions_user_allowed (s_con, user)) {
-		if (out_error_desc)
-			*out_error_desc = g_strdup_printf ("uid %lu has no permission to perform this operation", uid);
+		NM_SET_OUT (out_error_desc,
+		            g_strdup_printf ("uid %lu has no permission to perform this operation", uid));
 		return FALSE;
 	}
 
 	return TRUE;
 }
 
+gboolean
+nm_auth_is_subject_in_acl_set_error (NMConnection *connection,
+                                     NMAuthSubject *subject,
+                                     GQuark err_domain,
+                                     int err_code,
+                                     GError **error)
+{
+	char *error_desc = NULL;
+
+	nm_assert (!error || !*error);
+
+	if (nm_auth_is_subject_in_acl (connection,
+	                               subject,
+	                               error ? &error_desc : NULL))
+		return TRUE;
 
+	g_set_error_literal (error, err_domain, err_code, error_desc);
+	g_free (error_desc);
+	return FALSE;
+}