/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* NetworkManager -- Network link manager * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright (C) 2010 Red Hat, Inc. */ #include #include #include #include #if WITH_POLKIT #include #endif #include "nm-setting-connection.h" #include "nm-manager-auth.h" #include "nm-logging.h" #include "nm-dbus-manager.h" struct NMAuthChain { guint32 refcount; #if WITH_POLKIT PolkitAuthority *authority; #endif GSList *calls; GHashTable *data; DBusGMethodInvocation *context; char *owner; GError *error; NMAuthChainResultFunc done_func; gpointer user_data; }; typedef struct { NMAuthChain *chain; GCancellable *cancellable; char *permission; guint idle_id; gboolean disposed; } AuthCall; typedef struct { gpointer data; GDestroyNotify destroy; } ChainData; static void free_data (gpointer data) { ChainData *tmp = data; if (tmp->destroy) tmp->destroy (tmp->data); memset (tmp, 0, sizeof (ChainData)); g_free (tmp); } #if WITH_POLKIT static PolkitAuthority * pk_authority_get (void) { static PolkitAuthority *authority = NULL; GError *error = NULL; if (authority == NULL) { authority = polkit_authority_get_sync (NULL, &error); if (authority == NULL) { nm_log_err (LOGD_CORE, "Failed to initialize PolicyKit: (%d) %s", error ? error->code : -1, (error && error->message) ? error->message : "(unknown)"); g_clear_error (&error); return NULL; } } /* Yes, ref every time; we want to keep the object alive */ return g_object_ref (authority); } #endif static NMAuthChain * _auth_chain_new (DBusGMethodInvocation *context, DBusGProxy *proxy, DBusMessage *message, const char *dbus_sender, NMAuthChainResultFunc done_func, gpointer user_data) { NMAuthChain *self; g_return_val_if_fail (context || proxy || message || dbus_sender, NULL); self = g_malloc0 (sizeof (NMAuthChain)); self->refcount = 1; #if WITH_POLKIT self->authority = pk_authority_get (); #endif self->data = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_data); self->done_func = done_func; self->user_data = user_data; self->context = context; if (proxy) self->owner = g_strdup (dbus_g_proxy_get_bus_name (proxy)); else if (context) self->owner = dbus_g_method_get_sender (context); else if (message) self->owner = g_strdup (dbus_message_get_sender (message)); else if (dbus_sender) self->owner = g_strdup (dbus_sender); if (!self->owner) { /* Need an owner */ g_warn_if_fail (self->owner); nm_auth_chain_unref (self); self = NULL; } return self; } NMAuthChain * nm_auth_chain_new (DBusGMethodInvocation *context, DBusGProxy *proxy, NMAuthChainResultFunc done_func, gpointer user_data) { return _auth_chain_new (context, proxy, NULL, NULL, done_func, user_data); } NMAuthChain * nm_auth_chain_new_raw_message (DBusMessage *message, NMAuthChainResultFunc done_func, gpointer user_data) { return _auth_chain_new (NULL, NULL, message, NULL, done_func, user_data); } NMAuthChain * nm_auth_chain_new_dbus_sender (const char *dbus_sender, NMAuthChainResultFunc done_func, gpointer user_data) { return _auth_chain_new (NULL, NULL, NULL, dbus_sender, done_func, user_data); } gpointer nm_auth_chain_get_data (NMAuthChain *self, const char *tag) { ChainData *tmp; g_return_val_if_fail (self != NULL, NULL); g_return_val_if_fail (tag != NULL, NULL); tmp = g_hash_table_lookup (self->data, tag); return tmp ? tmp->data : NULL; } /** * nm_auth_chain_steal_data: * @self: A #NMAuthChain. * @tag: A "tag" uniquely identifying the data to steal. * * Removes the datum assocated with @tag from the chain's data associations, * without invoking the association's destroy handler. The caller assumes * ownership over the returned value. * * Returns: the datum originally associated with @tag */ gpointer nm_auth_chain_steal_data (NMAuthChain *self, const char *tag) { ChainData *tmp; gpointer value = NULL; g_return_val_if_fail (self != NULL, NULL); g_return_val_if_fail (tag != NULL, NULL); tmp = g_hash_table_lookup (self->data, tag); if (tmp) { g_hash_table_steal (self->data, tag); value = tmp->data; /* Make sure the destroy handler isn't called when freeing */ tmp->destroy = NULL; free_data (tmp); } return value; } void nm_auth_chain_set_data (NMAuthChain *self, const char *tag, gpointer data, GDestroyNotify data_destroy) { ChainData *tmp; g_return_if_fail (self != NULL); g_return_if_fail (tag != NULL); if (data == NULL) g_hash_table_remove (self->data, tag); else { tmp = g_malloc0 (sizeof (ChainData)); tmp->data = data; tmp->destroy = data_destroy; g_hash_table_insert (self->data, g_strdup (tag), tmp); } } gulong nm_auth_chain_get_data_ulong (NMAuthChain *self, const char *tag) { gulong *ptr; g_return_val_if_fail (self != NULL, 0); g_return_val_if_fail (tag != NULL, 0); ptr = nm_auth_chain_get_data (self, tag); return *ptr; } void nm_auth_chain_set_data_ulong (NMAuthChain *self, const char *tag, gulong data) { gulong *ptr; g_return_if_fail (self != NULL); g_return_if_fail (tag != NULL); ptr = g_malloc (sizeof (*ptr)); *ptr = data; nm_auth_chain_set_data (self, tag, ptr, g_free); } NMAuthCallResult nm_auth_chain_get_result (NMAuthChain *self, const char *permission) { g_return_val_if_fail (self != NULL, NM_AUTH_CALL_RESULT_UNKNOWN); g_return_val_if_fail (permission != NULL, NM_AUTH_CALL_RESULT_UNKNOWN); return GPOINTER_TO_UINT (nm_auth_chain_get_data (self, permission)); } static void nm_auth_chain_check_done (NMAuthChain *self) { g_return_if_fail (self != NULL); if (g_slist_length (self->calls) == 0) { /* Ensure we say alive across the callback */ self->refcount++; self->done_func (self, self->error, self->context, self->user_data); nm_auth_chain_unref (self); } } static void nm_auth_chain_remove_call (NMAuthChain *self, AuthCall *call) { g_return_if_fail (self != NULL); g_return_if_fail (call != NULL); self->calls = g_slist_remove (self->calls, call); } static AuthCall * auth_call_new (NMAuthChain *chain, const char *permission) { AuthCall *call; call = g_malloc0 (sizeof (AuthCall)); call->chain = chain; call->permission = g_strdup (permission); call->cancellable = g_cancellable_new (); chain->calls = g_slist_append (chain->calls, call); return call; } static void auth_call_cancel (AuthCall *call) { call->disposed = TRUE; g_cancellable_cancel (call->cancellable); } static void auth_call_free (AuthCall *call) { g_return_if_fail (call != NULL); call->disposed = TRUE; g_free (call->permission); call->permission = NULL; call->chain = NULL; g_object_unref (call->cancellable); call->cancellable = NULL; if (call->idle_id) g_source_remove (call->idle_id); memset (call, 0, sizeof (*call)); g_free (call); } /* This can get used from scheduled idles, hence the boolean return */ static gboolean auth_call_complete (AuthCall *call) { g_return_val_if_fail (call != NULL, FALSE); call->idle_id = 0; nm_auth_chain_remove_call (call->chain, call); nm_auth_chain_check_done (call->chain); auth_call_free (call); return FALSE; } static void auth_call_schedule_early_finish (AuthCall *call, GError *error) { if (!call->chain->error) call->chain->error = error; if (!call->idle_id) call->idle_id = g_idle_add ((GSourceFunc) auth_call_complete, call); } #if WITH_POLKIT static void pk_call_cb (GObject *object, GAsyncResult *result, gpointer user_data) { AuthCall *call = user_data; NMAuthChain *chain = call->chain; PolkitAuthorizationResult *pk_result; GError *error = NULL; /* If the call is already disposed do nothing */ if (call->disposed) { auth_call_free (call); return; } pk_result = polkit_authority_check_authorization_finish (chain->authority, result, &error); if (error) { if (!chain->error) chain->error = g_error_copy (error); nm_log_warn (LOGD_CORE, "error requesting auth for %s: (%d) %s", call->permission, error ? error->code : -1, error && error->message ? error->message : "(unknown)"); } else { guint call_result = NM_AUTH_CALL_RESULT_UNKNOWN; if (polkit_authorization_result_get_is_authorized (pk_result)) { /* Caller has the permission */ call_result = NM_AUTH_CALL_RESULT_YES; } else if (polkit_authorization_result_get_is_challenge (pk_result)) { /* 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 (chain, call->permission, GUINT_TO_POINTER (call_result), NULL); } g_clear_error (&error); if (pk_result) g_object_unref (pk_result); auth_call_complete (call); } #endif gboolean nm_auth_chain_add_call (NMAuthChain *self, const char *permission, gboolean allow_interaction) { AuthCall *call; #if WITH_POLKIT PolkitSubject *subject; PolkitCheckAuthorizationFlags flags = POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE; g_return_val_if_fail (self != NULL, FALSE); g_return_val_if_fail (self->owner != NULL, FALSE); g_return_val_if_fail (permission != NULL, FALSE); subject = polkit_system_bus_name_new (self->owner); if (!subject) return FALSE; call = auth_call_new (self, permission); if (self->authority == NULL) { /* No polkit, no authorization */ auth_call_schedule_early_finish (call, g_error_new_literal (0, 0, "PolicyKit unavailable")); g_object_unref (subject); return FALSE; } if (allow_interaction) flags = POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION; polkit_authority_check_authorization (self->authority, subject, permission, NULL, flags, call->cancellable, pk_call_cb, call); g_object_unref (subject); #else /* -- NO POLKIT -- */ g_return_val_if_fail (self != NULL, FALSE); /* When PolicyKit is disabled, everything is authorized */ call = auth_call_new (self, permission); nm_auth_chain_set_data (self, permission, GUINT_TO_POINTER (NM_AUTH_CALL_RESULT_YES), NULL); auth_call_schedule_early_finish (call, NULL); #endif return TRUE; } void nm_auth_chain_unref (NMAuthChain *self) { GSList *iter; g_return_if_fail (self != NULL); self->refcount--; if (self->refcount > 0) return; #if WITH_POLKIT if (self->authority) g_object_unref (self->authority); #endif g_free (self->owner); for (iter = self->calls; iter; iter = g_slist_next (iter)) auth_call_cancel ((AuthCall *) iter->data); g_slist_free (self->calls); g_clear_error (&self->error); g_hash_table_destroy (self->data); memset (self, 0, sizeof (NMAuthChain)); g_free (self); } /************ utils **************/ gboolean nm_auth_get_caller_uid (DBusGMethodInvocation *context, NMDBusManager *dbus_mgr, gulong *out_uid, char **out_error_desc) { DBusConnection *connection; char *sender = NULL; gboolean success = FALSE; DBusError dbus_error; g_return_val_if_fail (context != NULL, FALSE); g_return_val_if_fail (out_uid != NULL, FALSE); if (!dbus_mgr) { dbus_mgr = nm_dbus_manager_get (); g_assert (dbus_mgr); } else g_object_ref (dbus_mgr); *out_uid = G_MAXULONG; sender = dbus_g_method_get_sender (context); if (!sender) { if (out_error_desc) *out_error_desc = g_strdup ("Could not determine D-Bus requestor"); goto out; } connection = nm_dbus_manager_get_dbus_connection (dbus_mgr); if (!connection) { if (out_error_desc) *out_error_desc = g_strdup ("Could not get the D-Bus system bus"); goto out; } dbus_error_init (&dbus_error); /* FIXME: do this async */ *out_uid = dbus_bus_get_unix_user (connection, sender, &dbus_error); if (dbus_error_is_set (&dbus_error)) { if (out_error_desc) *out_error_desc = g_strdup_printf ("Could not determine the user ID of the requestor"); dbus_error_free (&dbus_error); *out_uid = G_MAXULONG; } else success = TRUE; out: g_object_unref (dbus_mgr); g_free (sender); return success; } gboolean nm_auth_uid_in_acl (NMConnection *connection, NMSessionMonitor *smon, gulong uid, char **out_error_desc) { NMSettingConnection *s_con; const char *user = NULL; GError *local = NULL; g_return_val_if_fail (connection != NULL, FALSE); g_return_val_if_fail (smon != NULL, FALSE); s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION)); g_assert (s_con); /* Reject the request if the request comes from no session at all */ if (!nm_session_monitor_uid_has_session (smon, uid, &user, &local)) { if (out_error_desc) { *out_error_desc = g_strdup_printf ("No session found for uid %lu (%s)", uid, local && local->message ? local->message : "unknown"); } g_clear_error (&local); return FALSE; } if (!user) { if (out_error_desc) *out_error_desc = g_strdup_printf ("Could not determine username for uid %lu", uid); return FALSE; } /* 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); return FALSE; } return TRUE; } typedef struct { GDestroyNotify changed_callback; gpointer changed_data; } PkChangedInfo; static GSList *funcs = NULL; #if WITH_POLKIT static void pk_authority_changed_cb (GObject *object, gpointer unused) { GSList *iter; for (iter = funcs; iter; iter = g_slist_next (iter)) { PkChangedInfo *info = iter->data; info->changed_callback (info->changed_data); } } #endif void nm_auth_changed_func_register (GDestroyNotify callback, gpointer callback_data) { #if WITH_POLKIT PolkitAuthority *authority; static guint32 changed_id = 0; #endif PkChangedInfo *info; GSList *iter; gboolean found = FALSE; #if WITH_POLKIT authority = pk_authority_get (); if (!authority) return; /* Hook up the changed signal the first time a callback is registered */ if (changed_id == 0) { changed_id = g_signal_connect (authority, "changed", G_CALLBACK (pk_authority_changed_cb), &funcs); } #endif /* No duplicates */ for (iter = funcs; iter; iter = g_slist_next (iter)) { info = iter->data; if ((callback == info->changed_callback) && (callback_data == info->changed_data)) { found = TRUE; break; } } g_warn_if_fail (found == FALSE); if (found == FALSE) { info = g_malloc0 (sizeof (*info)); info->changed_callback = callback; info->changed_data = callback_data; funcs = g_slist_append (funcs, info); } #if WITH_POLKIT g_object_unref (authority); #endif } void nm_auth_changed_func_unregister (GDestroyNotify callback, gpointer callback_data) { GSList *iter; for (iter = funcs; iter; iter = g_slist_next (iter)) { PkChangedInfo *info = iter->data; if ((callback == info->changed_callback) && (callback_data == info->changed_data)) { g_free (info); funcs = g_slist_delete_link (funcs, iter); break; } } }