gnome-shell/src/shell-polkit-authentication-agent.c
David Zeuthen 697139043f polkit: return error when authentication dialog is dismissed
This allows PolicyKit applications to disambiguate between when the
authentication dialog is dismissed versus when authentication fails
(e.g. the wrong password has been entered).

See https://bugs.freedesktop.org/show_bug.cgi?id=30653 for more
information.

Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-02-23 11:02:46 -05:00

436 lines
13 KiB
C

/* -*- 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-marshal.h"
#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE
#include <polkitagent/polkitagent.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, ...)
{
gchar *s;
va_list ap;
gchar timebuf[64];
GTimeVal now;
time_t now_t;
struct tm broken_down;
g_get_current_time (&now);
now_t = now.tv_sec;
localtime_r (&now_t, &broken_down);
strftime (timebuf, sizeof timebuf, "%H:%M:%S", &broken_down);
va_start (ap, format);
s = g_strdup_vprintf (format, ap);
va_end (ap);
g_print ("ShellPolkitAuthenticationAgent: %s.%03d: %s\n", timebuf, (gint) (now.tv_usec / 1000), s);
g_free (s);
}
#else
static void
print_debug (const gchar *str, ...)
{
}
#endif
struct _ShellPolkitAuthenticationAgentClass
{
PolkitAgentListenerClass parent_class;
};
struct _AuthRequest;
typedef struct _AuthRequest AuthRequest;
struct _ShellPolkitAuthenticationAgent {
PolkitAgentListener parent_instance;
GList *scheduled_requests;
AuthRequest *current_request;
};
/* 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);
static void
shell_polkit_authentication_agent_init (ShellPolkitAuthenticationAgent *agent)
{
gpointer handle;
PolkitSubject *subject;
GError *error;
subject = NULL;
error = NULL;
subject = polkit_unix_session_new_for_process_sync (getpid (),
NULL, /* GCancellable* */
&error);
if (subject == NULL)
{
g_warning ("Error getting session for the process we are in: %s (%s %d)",
error->message,
g_quark_to_string (error->domain),
error->code);
g_error_free (error);
goto out;
}
handle = polkit_agent_listener_register (POLKIT_AGENT_LISTENER (agent),
POLKIT_AGENT_REGISTER_FLAGS_NONE,
subject,
NULL, /* use default object path */
NULL, /* GCancellable */
&error);
if (handle == NULL)
{
g_warning ("Error registering polkit authentication agent: %s (%s %d)",
error->message,
g_quark_to_string (error->domain),
error->code);
g_error_free (error);
goto out;
}
/* We don't need to register so skip saving handle */
out:
if (subject != NULL)
g_object_unref (subject);
}
static void
shell_polkit_authentication_agent_finalize (GObject *object)
{
/* ShellPolkitAuthenticationAgent *agent = SHELL_POLKIT_AUTHENTICATION_AGENT (object); */
/* Specifically left empty since the object stays alive forever - if code
* is reused it would need to free outstanding requests etc.
*/
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 */
_shell_marshal_VOID__STRING_STRING_STRING_STRING_BOXED,
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 */
g_cclosure_marshal_VOID__VOID,
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;
GSimpleAsyncResult *simple;
};
static void
auth_request_free (AuthRequest *request)
{
g_cancellable_disconnect (request->cancellable, request->handler_id);
g_free (request->action_id);
g_free (request->message);
g_free (request->icon_name);
g_object_unref (request->details);
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;
/* post-pone to idle to handle GCancellable deadlock in
*
* https://bugzilla.gnome.org/show_bug.cgi?id=642968
*/
g_idle_add (handle_cancelled_in_idle, request);
}
static void maybe_process_next_request (ShellPolkitAuthenticationAgent *agent);
static void
auth_request_complete (AuthRequest *request,
gboolean dismissed)
{
ShellPolkitAuthenticationAgent *agent = request->agent;
if (dismissed)
g_simple_async_result_set_error (request->simple,
POLKIT_ERROR,
POLKIT_ERROR_CANCELLED,
_("Authentation dialog was dismissed by the user"));
if (agent->current_request == request)
{
print_debug ("COMPLETING CURRENT %s cookie %s", request->action_id, request->cookie);
g_simple_async_result_complete_in_idle (request->simple);
auth_request_free (request);
agent->current_request = NULL;
maybe_process_next_request (agent);
}
else
{
print_debug ("COMPLETING SCHEDULED %s cookie %s", request->action_id, request->cookie);
agent->scheduled_requests = g_list_remove (agent->scheduled_requests, request);
g_simple_async_result_complete_in_idle (request->simple);
auth_request_free (request);
}
}
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_simple_async_result_new (G_OBJECT (listener),
callback,
user_data,
initiate_authentication);
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)
{
GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
if (g_simple_async_result_propagate_error (simple, error))
return FALSE;
else
return TRUE;
}
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);
}