diff options
Diffstat (limited to 'src/nm-auth-utils.c')
| -rw-r--r-- | src/nm-auth-utils.c | 489 |
1 files changed, 262 insertions, 227 deletions
diff --git a/src/nm-auth-utils.c b/src/nm-auth-utils.c index b41f6efa..f1aff430 100644 --- a/src/nm-auth-utils.c +++ b/src/nm-auth-utils.c @@ -24,83 +24,48 @@ #include <string.h> -#include "nm-utils/nm-c-list.h" - +#include "nm-utils/nm-hash-utils.h" #include "nm-setting-connection.h" #include "nm-auth-subject.h" #include "nm-auth-manager.h" #include "nm-session-monitor.h" -/*****************************************************************************/ - struct NMAuthChain { - GHashTable *data_hash; - - CList auth_call_lst_head; + guint32 refcount; + GSList *calls; + GHashTable *data; 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; - NMAuthManagerCallId *call_id; + GCancellable *cancellable; 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 (const char *tag, gpointer data, GDestroyNotify destroy) +chain_data_new (gpointer data, GDestroyNotify destroy) { ChainData *tmp; - gsize l = strlen (tag); - tmp = g_malloc (sizeof (ChainData) + l + 1); - tmp->tag = &tmp->tag_data[0]; + tmp = g_slice_new (ChainData); tmp->data = data; tmp->destroy = destroy; - memcpy (&tmp->tag_data[0], tag, l + 1); return tmp; } @@ -111,7 +76,69 @@ chain_data_free (gpointer data) if (tmp->destroy) tmp->destroy (tmp->data); - g_free (tmp); + 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; } static gpointer @@ -119,17 +146,15 @@ _get_data (NMAuthChain *self, const char *tag) { ChainData *tmp; - if (!self->data_hash) - return NULL; - tmp = g_hash_table_lookup (self->data_hash, &tag); + tmp = g_hash_table_lookup (self->data, tag); return tmp ? tmp->data : NULL; } gpointer nm_auth_chain_get_data (NMAuthChain *self, const char *tag) { - g_return_val_if_fail (self, NULL); - g_return_val_if_fail (tag, NULL); + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (tag != NULL, NULL); return _get_data (self, tag); } @@ -150,22 +175,19 @@ nm_auth_chain_steal_data (NMAuthChain *self, const char *tag) { ChainData *tmp; gpointer value = NULL; - - 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); + 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); + } return value; } @@ -175,105 +197,165 @@ nm_auth_chain_set_data (NMAuthChain *self, gpointer data, GDestroyNotify 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)); + 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)); } } -/*****************************************************************************/ +gulong +nm_auth_chain_get_data_ulong (NMAuthChain *self, const char *tag) +{ + gulong *data; -NMAuthCallResult -nm_auth_chain_get_result (NMAuthChain *self, const char *permission) + 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) { - gpointer data; + gulong *ptr; - g_return_val_if_fail (self, NM_AUTH_CALL_RESULT_UNKNOWN); - g_return_val_if_fail (permission, NM_AUTH_CALL_RESULT_UNKNOWN); + g_return_if_fail (self != NULL); + g_return_if_fail (tag != NULL); - data = _get_data (self, permission); - return data ? GPOINTER_TO_UINT (data) : NM_AUTH_CALL_RESULT_UNKNOWN; + ptr = g_malloc (sizeof (*ptr)); + *ptr = data; + nm_auth_chain_set_data (self, tag, ptr, g_free); } NMAuthSubject * nm_auth_chain_get_subject (NMAuthChain *self) { - g_return_val_if_fail (self, NULL); + g_return_val_if_fail (self != NULL, NULL); return self->subject; } -/*****************************************************************************/ +NMAuthCallResult +nm_auth_chain_get_result (NMAuthChain *self, const char *permission) +{ + gpointer data; -static gboolean -auth_chain_finish (NMAuthChain *self) + 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) { - self->done = TRUE; + AuthCall *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; + call = g_slice_new0 (AuthCall); + call->chain = chain; + call->permission = g_strdup (permission); + return call; } 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; - _ASSERT_call (call); + g_return_val_if_fail (call, G_SOURCE_REMOVE); self = call->chain; - nm_assert (!self->done); + g_return_val_if_fail (self, G_SOURCE_REMOVE); + g_return_val_if_fail (g_slist_find (self->calls, call), G_SOURCE_REMOVE); - auth_call_free (call); + self->calls = g_slist_remove (self->calls, 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 (!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 -pk_call_cb (NMAuthManager *auth_manager, - NMAuthManagerCallId *call_id, - gboolean is_authorized, - gboolean is_challenge, - GError *error, - gpointer user_data) +auth_call_cancel (gpointer user_data) { - AuthCall *call; - NMAuthCallResult call_result; + AuthCall *call = user_data; - if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - return; - - call = user_data; - - nm_assert (call->call_id == call_id); + 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); + } +} - call->call_id = NULL; +#if WITH_POLKIT +static void +pk_call_cb (GObject *object, GAsyncResult *result, 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); + return; + } - call_result = nm_auth_call_result_eval (is_authorized, is_challenge, error); + 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; + } 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, @@ -283,109 +365,81 @@ nm_auth_chain_add_call (NMAuthChain *self, AuthCall *call; NMAuthManager *auth_manager = nm_auth_manager_get (); - g_return_if_fail (self); - g_return_if_fail (self->subject); - g_return_if_fail (!self->done); + g_return_if_fail (self != NULL); g_return_if_fail (permission && *permission); + g_return_if_fail (self->subject); 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 = 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); -} - -/*****************************************************************************/ + call = auth_call_new (self, permission); + self->calls = g_slist_append (self->calls, call); -/* 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; + 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 + } } /** - * nm_auth_chain_destroy: + * nm_auth_chain_unref: * @self: the auth-chain * - * Destroys the auth-chain. By destroying the auth-chain, you also cancel + * Unrefs the auth-chain. By unrefing the auth-chain, you also cancel * the receipt of the done-callback. IOW, the callback will not be invoked. * - * The only exception is, if may call nm_auth_chain_destroy() from inside + * The only exception is, if you call nm_auth_chain_unref() 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_destroy (NMAuthChain *self) +nm_auth_chain_unref (NMAuthChain *self) { - AuthCall *call; - - g_return_if_fail (self); - g_return_if_fail (NM_IN_SET (self->refcount, 1, 2)); + g_return_if_fail (self != NULL); + g_return_if_fail (self->refcount > 0); - if (--self->refcount > 0) + self->refcount--; + if (self->refcount > 0) return; - nm_clear_g_object (&self->subject); - nm_clear_g_object (&self->context); + if (self->idle_id) + g_source_remove (self->idle_id); - while ((call = c_list_first_entry (&self->auth_call_lst_head, AuthCall, auth_call_lst))) - auth_call_free (call); + g_object_unref (self->subject); - nm_clear_pointer (&self->data_hash, g_hash_table_destroy); + if (self->context) + g_object_unref (self->context); + g_slist_free_full (self->calls, auth_call_cancel); + + g_clear_error (&self->error); + g_hash_table_destroy (self->data); + + memset (self, 0, sizeof (NMAuthChain)); g_slice_free (NMAuthChain, self); } -/****************************************************************************** - * utils - *****************************************************************************/ +/************ utils **************/ gboolean nm_auth_is_subject_in_acl (NMConnection *connection, @@ -396,7 +450,7 @@ nm_auth_is_subject_in_acl (NMConnection *connection, const char *user = NULL; gulong uid; - g_return_val_if_fail (connection, FALSE); + g_return_val_if_fail (connection != NULL, 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); @@ -410,8 +464,8 @@ nm_auth_is_subject_in_acl (NMConnection *connection, return TRUE; if (!nm_session_monitor_uid_to_user (uid, &user)) { - NM_SET_OUT (out_error_desc, - g_strdup_printf ("Could not determine username for uid %lu", uid)); + if (out_error_desc) + *out_error_desc = g_strdup_printf ("Could not determine username for uid %lu", uid); return FALSE; } @@ -425,31 +479,12 @@ 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)) { - NM_SET_OUT (out_error_desc, - g_strdup_printf ("uid %lu has no permission to perform this operation", uid)); + if (out_error_desc) + *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; -} |