/* * Copyright (C) 2008 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307, USA. * * Author: David Zeuthen */ #include "config.h" #include #include "polkitagentlistener.h" /** * SECTION:polkitagentlistener * @title: PolkitAgentListener * @short_description: Abstract base class for Authentication Agents * @stability: Unstable * * #PolkitAgentListener is an abstract base class used for implementing authentication * agents. To implement an authentication agent, simply subclass #PolkitAgentListener and * implement the @initiate_authentication and @initiate_authentication_finish methods. * * Typically authentication agents use #PolkitAgentSession to * authenticate users (via passwords) and communicate back the * authentication result to the PolicyKit daemon. * * To register a #PolkitAgentListener with the PolicyKit daemon, use * polkit_agent_listener_register() or * polkit_agent_listener_register_with_options(). */ typedef struct { GObject parent_instance; GDBusConnection *system_bus; guint auth_agent_registration_id; GDBusInterfaceInfo *interface_info; PolkitAuthority *authority; gulong notify_owner_handler_id; gboolean is_registered; PolkitAgentListener *listener; GVariant *registration_options; PolkitSubject *subject; gchar *object_path; GHashTable *cookie_to_pending_auth; GThread *thread; GError *thread_initialization_error; gboolean thread_initialized; GMainContext *thread_context; GMainLoop *thread_loop; } Server; static void server_free (Server *server) { if (server->is_registered) { GError *error; error = NULL; if (!polkit_authority_unregister_authentication_agent_sync (server->authority, server->subject, server->object_path, NULL, &error)) { g_warning ("Error unregistering authentication agent: %s", error->message); g_error_free (error); } } if (server->thread_initialization_error != NULL) g_error_free (server->thread_initialization_error); if (server->thread_context != NULL) g_main_context_unref (server->thread_context); if (server->thread_loop != NULL) g_main_loop_unref (server->thread_loop); if (server->interface_info != NULL) g_dbus_interface_info_unref (server->interface_info); if (server->registration_options != NULL) g_variant_unref (server->registration_options); if (server->listener != NULL) g_object_unref (server->listener); if (server->auth_agent_registration_id > 0) g_dbus_connection_unregister_object (server->system_bus, server->auth_agent_registration_id); if (server->notify_owner_handler_id > 0) g_signal_handler_disconnect (server->authority, server->notify_owner_handler_id); if (server->authority != NULL) g_object_unref (server->authority); if (server->system_bus != NULL) g_object_unref (server->system_bus); if (server->cookie_to_pending_auth != NULL) g_hash_table_unref (server->cookie_to_pending_auth); if (server->subject != NULL) g_object_unref (server->subject); g_free (server->object_path); g_free (server); } static gboolean server_register (Server *server, GError **error) { GError *local_error; gboolean ret; const gchar *locale; ret = FALSE; locale = g_getenv ("LANG"); if (locale == NULL) locale = "en_US.UTF-8"; local_error = NULL; if (!polkit_authority_register_authentication_agent_with_options_sync (server->authority, server->subject, locale, server->object_path, server->registration_options, NULL, &local_error)) { g_propagate_error (error, local_error); } else { server->is_registered = TRUE; ret = TRUE; } return ret; } static void on_notify_authority_owner (GObject *object, GParamSpec *pspec, gpointer user_data) { Server *server = user_data; gchar *owner; owner = polkit_authority_get_owner (server->authority); if (owner == NULL) { g_debug ("PolicyKit daemon disconnected from the bus.\n"); if (server->is_registered) g_debug ("We are no longer a registered authentication agent.\n"); server->is_registered = FALSE; } else { /* only register if there is a name owner */ if (!server->is_registered) { GError *error; g_debug ("PolicyKit daemon reconnected to bus.\n"); g_debug ("Attempting to re-register as an authentication agent.\n"); error = NULL; if (server_register (server, &error)) { g_debug ("We are now a registered authentication agent.\n"); } else { g_debug ("Failed to register as an authentication agent: %s\n", error->message); g_error_free (error); } } } g_free (owner); } static gboolean server_init_sync (Server *server, GCancellable *cancellable, GError **error) { gboolean ret; ret = FALSE; server->system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, cancellable, error); if (server->system_bus == NULL) goto out; server->authority = polkit_authority_get_sync (cancellable, error); if (server->authority == NULL) goto out; /* the only use of this proxy is to re-register with the polkit daemon * if it jumps off the bus and comes back (which is useful for debugging) */ server->notify_owner_handler_id = g_signal_connect (server->authority, "notify::owner", G_CALLBACK (on_notify_authority_owner), server); ret = TRUE; out: return ret; } static Server * server_new (PolkitSubject *subject, const gchar *object_path, GCancellable *cancellable, GError **error) { Server *server; server = g_new0 (Server, 1); server->subject = g_object_ref (subject); server->object_path = object_path != NULL ? g_strdup (object_path) : g_strdup ("/org/freedesktop/PolicyKit1/AuthenticationAgent"); server->cookie_to_pending_auth = g_hash_table_new (g_str_hash, g_str_equal); if (!server_init_sync (server, cancellable, error)) { server_free (server); return NULL; } return server; } static void auth_agent_handle_begin_authentication (Server *server, GVariant *parameters, GDBusMethodInvocation *invocation); static void auth_agent_handle_cancel_authentication (Server *server, GVariant *parameters, GDBusMethodInvocation *invocation); static void auth_agent_handle_method_call (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { Server *server = user_data; /* The shipped D-Bus policy also ensures that only uid 0 can invoke * methods on our interface. So no need to check the caller. */ if (g_strcmp0 (method_name, "BeginAuthentication") == 0) auth_agent_handle_begin_authentication (server, parameters, invocation); else if (g_strcmp0 (method_name, "CancelAuthentication") == 0) auth_agent_handle_cancel_authentication (server, parameters, invocation); else g_assert_not_reached (); } static const gchar *auth_agent_introspection_data = "" " " " " " " " " " " " " " " " " " " " " " " " " " " ""; static const GDBusInterfaceVTable auth_agent_vtable = { auth_agent_handle_method_call, NULL, /* _handle_get_property */ NULL /* _handle_set_property */ }; static gboolean server_export_object (Server *server, GError **error) { gboolean ret; ret = FALSE; server->auth_agent_registration_id = g_dbus_connection_register_object (server->system_bus, server->object_path, server->interface_info, &auth_agent_vtable, server, NULL, /* user_data GDestroyNotify */ error); if (server->auth_agent_registration_id > 0) ret = TRUE; return ret; } static gpointer server_thread_func (gpointer user_data) { Server *server = user_data; server->thread_context = g_main_context_new (); server->thread_loop = g_main_loop_new (server->thread_context, FALSE); g_main_context_push_thread_default (server->thread_context); if (!server_export_object (server, &server->thread_initialization_error)) { server->thread_initialized = TRUE; goto out; } server->thread_initialized = TRUE; g_main_loop_run (server->thread_loop); out: g_main_context_pop_thread_default (server->thread_context); return NULL; } /** * polkit_agent_listener_register_with_options: * @listener: A #PolkitAgentListener. * @flags: A set of flags from the #PolkitAgentRegisterFlags enumeration. * @subject: The subject to become an authentication agent for, typically a #PolkitUnixSession object. * @object_path: The D-Bus object path to use for the authentication agent or %NULL for the default object path. * @options: (allow-none): A #GVariant with options or %NULL. * @cancellable: A #GCancellable or %NULL. * @error: Return location for error. * * Like polkit_agent_listener_register() but takes options to influence registration. See the * RegisterAuthenticationAgentWithOptions() D-Bus method for details. * * Returns: (transfer full): %NULL if @error is set, otherwise a * registration handle that can be used with * polkit_agent_listener_unregister(). */ gpointer polkit_agent_listener_register_with_options (PolkitAgentListener *listener, PolkitAgentRegisterFlags flags, PolkitSubject *subject, const gchar *object_path, GVariant *options, GCancellable *cancellable, GError **error) { Server *server; GDBusNodeInfo *node_info; g_return_val_if_fail (POLKIT_AGENT_IS_LISTENER (listener), NULL); g_return_val_if_fail (POLKIT_IS_SUBJECT (subject), NULL); g_return_val_if_fail (object_path == NULL || g_variant_is_object_path (object_path), NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); if (object_path == NULL) object_path = "/org/freedesktop/PolicyKit1/AuthenticationAgent"; server = server_new (subject, object_path, cancellable, error); if (server == NULL) goto out; node_info = g_dbus_node_info_new_for_xml (auth_agent_introspection_data, error); if (node_info == NULL) { server_free (server); server = NULL; goto out; } server->interface_info = g_dbus_interface_info_ref (g_dbus_node_info_lookup_interface (node_info, "org.freedesktop.PolicyKit1.AuthenticationAgent")); g_dbus_node_info_unref (node_info); server->listener = g_object_ref (listener); server->registration_options = options != NULL ? g_variant_ref_sink (options) : NULL; if (flags & POLKIT_AGENT_REGISTER_FLAGS_RUN_IN_THREAD) { server->thread = g_thread_try_new ("polkit agent listener", server_thread_func, server, error); if (server->thread == NULL) { server_free (server); server = NULL; goto out; } /* wait for the thread to export and object (TODO: probably use a condition variable instead) */ while (!server->thread_initialized) g_thread_yield (); if (server->thread_initialization_error != NULL) { g_propagate_error (error, server->thread_initialization_error); server->thread_initialization_error = NULL; g_thread_join (server->thread); server_free (server); server = NULL; goto out; } } else { if (!server_export_object (server, error)) { server_free (server); server = NULL; goto out; } } if (!server_register (server, error)) { server_free (server); server = NULL; goto out; } out: return server; } /** * polkit_agent_listener_register: * @listener: A #PolkitAgentListener. * @flags: A set of flags from the #PolkitAgentRegisterFlags enumeration. * @subject: The subject to become an authentication agent for, typically a #PolkitUnixSession object. * @object_path: The D-Bus object path to use for the authentication agent or %NULL for the default object path. * @cancellable: A #GCancellable or %NULL. * @error: Return location for error. * * Registers @listener with the PolicyKit daemon as an authentication * agent for @subject. This is implemented by registering a D-Bus * object at @object_path on the unique name assigned by the system * message bus. * * Whenever the PolicyKit daemon needs to authenticate a processes * that is related to @subject, the methods * polkit_agent_listener_initiate_authentication() and * polkit_agent_listener_initiate_authentication_finish() will be * invoked on @listener. * * Note that registration of an authentication agent can fail; for * example another authentication agent may already be registered for * @subject. * * Note that the calling thread is blocked until a reply is received. * * Returns: (transfer full): %NULL if @error is set, otherwise a * registration handle that can be used with * polkit_agent_listener_unregister(). */ gpointer polkit_agent_listener_register (PolkitAgentListener *listener, PolkitAgentRegisterFlags flags, PolkitSubject *subject, const gchar *object_path, GCancellable *cancellable, GError **error) { return polkit_agent_listener_register_with_options (listener, flags, subject, object_path, NULL, cancellable, error); } /** * polkit_agent_listener_unregister: * @registration_handle: A handle obtained from polkit_agent_listener_register(). * * Unregisters @listener. */ void polkit_agent_listener_unregister (gpointer registration_handle) { Server *server = registration_handle; if (server->thread != NULL) { g_main_loop_quit (server->thread_loop); g_thread_join (server->thread); } server_free (server); } static void listener_died (gpointer user_data, GObject *where_the_object_was) { Server *server = user_data; server_free (server); } /** * polkit_agent_register_listener: * @listener: A #PolkitAgentListener. * @subject: The subject to become an authentication agent for, typically a #PolkitUnixSession object. * @object_path: The D-Bus object path to use for the authentication agent or %NULL for the default object path. * @error: Return location for error. * * (deprecated) */ gboolean polkit_agent_register_listener (PolkitAgentListener *listener, PolkitSubject *subject, const gchar *object_path, GError **error) { Server *server; gboolean ret; ret = FALSE; server = polkit_agent_listener_register (listener, POLKIT_AGENT_REGISTER_FLAGS_NONE, subject, object_path, NULL, error); if (server == NULL) goto out; /* drop the ref that server took */ g_object_unref (server->listener); /* take a weak ref and kill server when listener dies */ g_object_weak_ref (G_OBJECT (server->listener), listener_died, server); ret = TRUE; out: return ret; } typedef struct { gchar *cookie; GHashTable *cookie_to_pending_auth; GDBusMethodInvocation *invocation; GCancellable *cancellable; } AuthData; static void auth_data_free (AuthData *data) { g_free (data->cookie); g_object_unref (data->invocation); g_object_unref (data->cancellable); g_hash_table_unref (data->cookie_to_pending_auth); g_free (data); } /* ---------------------------------------------------------------------------------------------------- */ static void auth_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { AuthData *data = user_data; GError *error; error = NULL; if (!polkit_agent_listener_initiate_authentication_finish (POLKIT_AGENT_LISTENER (source_object), res, &error)) { g_dbus_method_invocation_return_gerror (data->invocation, error); g_error_free (error); } else { g_dbus_method_invocation_return_value (data->invocation, NULL); } g_hash_table_remove (data->cookie_to_pending_auth, data->cookie); auth_data_free (data); } static void auth_agent_handle_begin_authentication (Server *server, GVariant *parameters, GDBusMethodInvocation *invocation) { const gchar *action_id; const gchar *message; const gchar *icon_name; GVariant *details_gvariant; const gchar *cookie; GVariant *identities_gvariant; GList *identities; PolkitDetails *details; GVariantIter iter; GVariant *child; guint n; AuthData *data; identities = NULL; details = NULL; g_variant_get (parameters, "(&s&s&s@a{ss}&s@a(sa{sv}))", &action_id, &message, &icon_name, &details_gvariant, &cookie, &identities_gvariant); details = polkit_details_new_for_gvariant (details_gvariant); g_variant_iter_init (&iter, identities_gvariant); n = 0; while ((child = g_variant_iter_next_value (&iter)) != NULL) { PolkitIdentity *identity; GError *error; error = NULL; identity = polkit_identity_new_for_gvariant (child, &error); g_variant_unref (child); if (identity == NULL) { g_prefix_error (&error, "Error extracting identity %d: ", n); g_dbus_method_invocation_return_gerror (invocation, error); g_error_free (error); goto out; } n++; identities = g_list_prepend (identities, identity); } identities = g_list_reverse (identities); data = g_new0 (AuthData, 1); data->cookie_to_pending_auth = g_hash_table_ref (server->cookie_to_pending_auth); data->cookie = g_strdup (cookie); data->invocation = g_object_ref (invocation); data->cancellable = g_cancellable_new (); g_hash_table_insert (server->cookie_to_pending_auth, (gpointer) cookie, data); polkit_agent_listener_initiate_authentication (server->listener, action_id, message, icon_name, details, cookie, identities, data->cancellable, auth_cb, data); out: g_list_foreach (identities, (GFunc) g_object_unref, NULL); g_list_free (identities); g_object_unref (details); g_variant_unref (details_gvariant); g_variant_unref (identities_gvariant); } /* ---------------------------------------------------------------------------------------------------- */ static void auth_agent_handle_cancel_authentication (Server *server, GVariant *parameters, GDBusMethodInvocation *invocation) { AuthData *data; const gchar *cookie; g_variant_get (parameters, "(&s)", &cookie); data = g_hash_table_lookup (server->cookie_to_pending_auth, cookie); if (data == NULL) { g_dbus_method_invocation_return_error (invocation, POLKIT_ERROR, POLKIT_ERROR_FAILED, "No pending authentication request for cookie '%s'", cookie); } else { g_cancellable_cancel (data->cancellable); g_dbus_method_invocation_return_value (invocation, NULL); } } /* ---------------------------------------------------------------------------------------------------- */ G_DEFINE_ABSTRACT_TYPE (PolkitAgentListener, polkit_agent_listener, G_TYPE_OBJECT); static void polkit_agent_listener_init (PolkitAgentListener *listener) { } static void polkit_agent_listener_class_init (PolkitAgentListenerClass *klass) { } /** * polkit_agent_listener_initiate_authentication: * @listener: A #PolkitAgentListener. * @action_id: The action to authenticate for. * @message: The message to present to the user. * @icon_name: A themed icon name representing the action or %NULL. * @details: Details describing the action. * @cookie: The cookie for the authentication request. * @identities: (element-type Polkit.Identity): A list of #PolkitIdentity objects that the user can choose to authenticate as. * @cancellable: A #GCancellable. * @callback: Function to call when the user is done authenticating. * @user_data: Data to pass to @callback. * * Called on a registered authentication agent (see * polkit_agent_listener_register()) when the user owning the session * needs to prove he is one of the identities listed in @identities. * * When the user is done authenticating (for example by dismissing an * authentication dialog or by successfully entering a password or * otherwise proving the user is one of the identities in * @identities), @callback will be invoked. The caller then calls * polkit_agent_listener_initiate_authentication_finish() to get the * result. * * #PolkitAgentListener derived subclasses imlementing this method * MUST not ignore @cancellable; callers of this * function can and will use it. Additionally, @callback must be * invoked in the thread-default main * loop of the thread that this method is called from. */ void polkit_agent_listener_initiate_authentication (PolkitAgentListener *listener, const gchar *action_id, const gchar *message, const gchar *icon_name, PolkitDetails *details, const gchar *cookie, GList *identities, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail (POLKIT_AGENT_IS_LISTENER (listener)); g_return_if_fail (details == NULL || POLKIT_IS_DETAILS (details)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (action_id != NULL); g_return_if_fail (message != NULL); g_return_if_fail (cookie != NULL); g_return_if_fail (identities != NULL); POLKIT_AGENT_LISTENER_GET_CLASS (listener)->initiate_authentication (listener, action_id, message, icon_name, details, cookie, identities, cancellable, callback, user_data); } /** * polkit_agent_listener_initiate_authentication_finish: * @listener: A #PolkitAgentListener. * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback function passed to polkit_agent_listener_initiate_authentication(). * @error: Return location for error. * * Finishes an authentication request from the PolicyKit daemon, see * polkit_agent_listener_initiate_authentication() for details. * * Returns: %TRUE if @error is set. **/ gboolean polkit_agent_listener_initiate_authentication_finish (PolkitAgentListener *listener, GAsyncResult *res, GError **error) { g_return_val_if_fail (POLKIT_AGENT_IS_LISTENER (listener), FALSE); g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); return POLKIT_AGENT_LISTENER_GET_CLASS (listener)->initiate_authentication_finish (listener, res, error); }