c634718dfa
The polkit agent does not take ownership of the request's cancellable,
so it may no longer be valid after the request finished and we need
to disconnect it before that. Before the GTask port in 8b4249ef2
,
it was safe to do that when freeing the request data because requests
completed in an idle, but now we need to do it before returning the
task result.
https://bugzilla.gnome.org/show_bug.cgi?id=757668
439 lines
13 KiB
C
439 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>
|
|
|
|
#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;
|
|
|
|
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);
|
|
|
|
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);
|
|
}
|