diff options
Diffstat (limited to 'src/shell-polkit-authentication-agent.c')
-rw-r--r-- | src/shell-polkit-authentication-agent.c | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/src/shell-polkit-authentication-agent.c b/src/shell-polkit-authentication-agent.c new file mode 100644 index 0000000..6815f01 --- /dev/null +++ b/src/shell-polkit-authentication-agent.c @@ -0,0 +1,429 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 20011 Red Hat, Inc. + * + * Author: David Zeuthen <davidz@redhat.com> + */ + +#include "config.h" + +#include <pwd.h> + +#include "shell-polkit-authentication-agent.h" + +#include <glib/gi18n.h> + +/* uncomment for useful debug output */ +/* #define SHOW_DEBUG */ + +#ifdef SHOW_DEBUG +static void +print_debug (const gchar *format, ...) +{ + g_autofree char *s = NULL; + g_autofree char *timestamp = NULL; + g_autoptr (GDateTime) now = NULL; + va_list ap; + + now = g_date_time_new_now_local (); + timestamp = g_date_time_format (now, "%H:%M:%S"); + + va_start (ap, format); + s = g_strdup_vprintf (format, ap); + va_end (ap); + + g_print ("ShellPolkitAuthenticationAgent: %s.%03d: %s\n", + timestamp, g_date_time_get_microsecond (now), s); +} +#else +static void +print_debug (const gchar *str, ...) +{ +} +#endif + +struct _AuthRequest; +typedef struct _AuthRequest AuthRequest; + +struct _ShellPolkitAuthenticationAgent { + PolkitAgentListener parent_instance; + + GList *scheduled_requests; + AuthRequest *current_request; + + gpointer handle; +}; + +/* Signals */ +enum +{ + INITIATE_SIGNAL, + CANCEL_SIGNAL, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (ShellPolkitAuthenticationAgent, shell_polkit_authentication_agent, POLKIT_AGENT_TYPE_LISTENER); + +static void 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); + +static gboolean initiate_authentication_finish (PolkitAgentListener *listener, + GAsyncResult *res, + GError **error); + +void +shell_polkit_authentication_agent_init (ShellPolkitAuthenticationAgent *agent) +{ +} + +void +shell_polkit_authentication_agent_register (ShellPolkitAuthenticationAgent *agent, + GError **error_out) +{ + GError *error = NULL; + PolkitSubject *subject; + + subject = polkit_unix_session_new_for_process_sync (getpid (), + NULL, /* GCancellable* */ + &error); + if (subject == NULL) + { + if (error == NULL) /* polkit version 104 and older don't properly set error on failure */ + error = g_error_new (POLKIT_ERROR, POLKIT_ERROR_FAILED, + "PolKit failed to properly get our session"); + goto out; + } + + agent->handle = polkit_agent_listener_register (POLKIT_AGENT_LISTENER (agent), + POLKIT_AGENT_REGISTER_FLAGS_NONE, + subject, + NULL, /* use default object path */ + NULL, /* GCancellable */ + &error); + + out: + if (error != NULL) + g_propagate_error (error_out, error); + + if (subject != NULL) + g_object_unref (subject); +} + +static void +shell_polkit_authentication_agent_finalize (GObject *object) +{ + ShellPolkitAuthenticationAgent *agent = SHELL_POLKIT_AUTHENTICATION_AGENT (object); + shell_polkit_authentication_agent_unregister (agent); + G_OBJECT_CLASS (shell_polkit_authentication_agent_parent_class)->finalize (object); +} + +static void +shell_polkit_authentication_agent_class_init (ShellPolkitAuthenticationAgentClass *klass) +{ + GObjectClass *gobject_class; + PolkitAgentListenerClass *listener_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = shell_polkit_authentication_agent_finalize; + + listener_class = POLKIT_AGENT_LISTENER_CLASS (klass); + listener_class->initiate_authentication = initiate_authentication; + listener_class->initiate_authentication_finish = initiate_authentication_finish; + + signals[INITIATE_SIGNAL] = + g_signal_new ("initiate", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, /* class_offset */ + NULL, /* accumulator */ + NULL, /* accumulator data */ + NULL, /* marshaller */ + G_TYPE_NONE, + 5, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRV); + + signals[CANCEL_SIGNAL] = + g_signal_new ("cancel", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, /* class_offset */ + NULL, /* accumulator */ + NULL, /* accumulator data */ + NULL, /* marshaller */ + G_TYPE_NONE, + 0); +} + +ShellPolkitAuthenticationAgent * +shell_polkit_authentication_agent_new (void) +{ + return SHELL_POLKIT_AUTHENTICATION_AGENT (g_object_new (SHELL_TYPE_POLKIT_AUTHENTICATION_AGENT, NULL)); +} + +struct _AuthRequest { + /* not holding ref */ + ShellPolkitAuthenticationAgent *agent; + GCancellable *cancellable; + gulong handler_id; + + /* copies */ + gchar *action_id; + gchar *message; + gchar *icon_name; + PolkitDetails *details; + gchar *cookie; + GList *identities; + + GTask *simple; +}; + +static void +auth_request_free (AuthRequest *request) +{ + g_free (request->action_id); + g_free (request->message); + g_free (request->icon_name); + g_object_unref (request->details); + g_free (request->cookie); + g_list_foreach (request->identities, (GFunc) g_object_unref, NULL); + g_list_free (request->identities); + g_object_unref (request->simple); + g_free (request); +} + +static void +auth_request_initiate (AuthRequest *request) +{ + gchar **user_names; + GPtrArray *p; + GList *l; + + p = g_ptr_array_new (); + for (l = request->identities; l != NULL; l = l->next) + { + if (POLKIT_IS_UNIX_USER (l->data)) + { + PolkitUnixUser *user = POLKIT_UNIX_USER (l->data); + gint uid; + gchar buf[4096]; + struct passwd pwd; + struct passwd *ppwd; + + uid = polkit_unix_user_get_uid (user); + if (getpwuid_r (uid, &pwd, buf, sizeof (buf), &ppwd) == 0) + { + if (!g_utf8_validate (pwd.pw_name, -1, NULL)) + { + g_warning ("Invalid UTF-8 in username for uid %d. Skipping", uid); + } + else + { + g_ptr_array_add (p, g_strdup (pwd.pw_name)); + } + } + else + { + g_warning ("Error looking up user name for uid %d", uid); + } + } + else + { + g_warning ("Unsupporting identity of GType %s", g_type_name (G_TYPE_FROM_INSTANCE (l->data))); + } + } + g_ptr_array_add (p, NULL); + user_names = (gchar **) g_ptr_array_free (p, FALSE); + g_signal_emit (request->agent, + signals[INITIATE_SIGNAL], + 0, /* detail */ + request->action_id, + request->message, + request->icon_name, + request->cookie, + user_names); + g_strfreev (user_names); +} + +static void auth_request_complete (AuthRequest *request, + gboolean dismissed); + +static gboolean +handle_cancelled_in_idle (gpointer user_data) +{ + AuthRequest *request = user_data; + + print_debug ("CANCELLED %s cookie %s", request->action_id, request->cookie); + if (request == request->agent->current_request) + { + g_signal_emit (request->agent, + signals[CANCEL_SIGNAL], + 0); /* detail */ + } + else + { + auth_request_complete (request, FALSE); + } + + return FALSE; +} + +static void +on_request_cancelled (GCancellable *cancellable, + gpointer user_data) +{ + AuthRequest *request = user_data; + guint id; + + /* post-pone to idle to handle GCancellable deadlock in + * + * https://bugzilla.gnome.org/show_bug.cgi?id=642968 + */ + id = g_idle_add (handle_cancelled_in_idle, request); + g_source_set_name_by_id (id, "[gnome-shell] handle_cancelled_in_idle"); +} + +static void +auth_request_dismiss (AuthRequest *request) +{ + auth_request_complete (request, TRUE); +} + +void +shell_polkit_authentication_agent_unregister (ShellPolkitAuthenticationAgent *agent) +{ + if (agent->scheduled_requests != NULL) + { + g_list_foreach (agent->scheduled_requests, (GFunc)auth_request_dismiss, NULL); + agent->scheduled_requests = NULL; + } + if (agent->current_request != NULL) + auth_request_dismiss (agent->current_request); + + if (agent->handle) + { + polkit_agent_listener_unregister (agent->handle); + agent->handle = NULL; + } +} + +static void maybe_process_next_request (ShellPolkitAuthenticationAgent *agent); + +static void +auth_request_complete (AuthRequest *request, + gboolean dismissed) +{ + ShellPolkitAuthenticationAgent *agent = request->agent; + gboolean is_current = agent->current_request == request; + + print_debug ("COMPLETING %s %s cookie %s", is_current ? "CURRENT" : "SCHEDULED", + request->action_id, request->cookie); + + if (!is_current) + agent->scheduled_requests = g_list_remove (agent->scheduled_requests, request); + g_cancellable_disconnect (request->cancellable, request->handler_id); + + if (dismissed) + g_task_return_new_error (request->simple, + POLKIT_ERROR, + POLKIT_ERROR_CANCELLED, + _("Authentication dialog was dismissed by the user")); + else + g_task_return_boolean (request->simple, TRUE); + + auth_request_free (request); + + if (is_current) + { + agent->current_request = NULL; + maybe_process_next_request (agent); + } +} + +static void +maybe_process_next_request (ShellPolkitAuthenticationAgent *agent) +{ + print_debug ("MAYBE_PROCESS cur=%p len(scheduled)=%d", agent->current_request, g_list_length (agent->scheduled_requests)); + + if (agent->current_request == NULL && agent->scheduled_requests != NULL) + { + AuthRequest *request; + + request = agent->scheduled_requests->data; + + agent->current_request = request; + agent->scheduled_requests = g_list_remove (agent->scheduled_requests, request); + + print_debug ("INITIATING %s cookie %s", request->action_id, request->cookie); + auth_request_initiate (request); + } +} + +static void +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) +{ + ShellPolkitAuthenticationAgent *agent = SHELL_POLKIT_AUTHENTICATION_AGENT (listener); + AuthRequest *request; + + request = g_new0 (AuthRequest, 1); + request->agent = agent; + request->action_id = g_strdup (action_id); + request->message = g_strdup (message); + request->icon_name = g_strdup (icon_name); + request->details = g_object_ref (details); + request->cookie = g_strdup (cookie); + request->identities = g_list_copy (identities); + g_list_foreach (request->identities, (GFunc) g_object_ref, NULL); + request->simple = g_task_new (listener, NULL, callback, user_data); + request->cancellable = cancellable; + request->handler_id = g_cancellable_connect (request->cancellable, + G_CALLBACK (on_request_cancelled), + request, + NULL); /* GDestroyNotify for request */ + + print_debug ("SCHEDULING %s cookie %s", request->action_id, request->cookie); + agent->scheduled_requests = g_list_append (agent->scheduled_requests, request); + + maybe_process_next_request (agent); +} + +static gboolean +initiate_authentication_finish (PolkitAgentListener *listener, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +void +shell_polkit_authentication_agent_complete (ShellPolkitAuthenticationAgent *agent, + gboolean dismissed) +{ + g_return_if_fail (SHELL_IS_POLKIT_AUTHENTICATION_AGENT (agent)); + g_return_if_fail (agent->current_request != NULL); + + auth_request_complete (agent->current_request, dismissed); +} |