diff options
Diffstat (limited to 'src/ck-manager.c')
-rw-r--r-- | src/ck-manager.c | 667 |
1 files changed, 609 insertions, 58 deletions
diff --git a/src/ck-manager.c b/src/ck-manager.c index 5c77547..b6bcb2b 100644 --- a/src/ck-manager.c +++ b/src/ck-manager.c @@ -38,6 +38,8 @@ #include <dbus/dbus-glib.h> #include <dbus/dbus-glib-lowlevel.h> +#include <polkit/polkit.h> + #include "ck-manager.h" #include "ck-manager-glue.h" #include "ck-seat.h" @@ -58,6 +60,8 @@ struct CkManagerPrivate { + PolKitContext *pol_ctx; + GHashTable *seats; GHashTable *sessions; GHashTable *leaders; @@ -634,6 +638,561 @@ log_seat_device_removed_event (CkManager *manager, g_free (device_id); } +static char * +get_cookie_for_pid (CkManager *manager, + guint pid) +{ + char *cookie; + + /* FIXME: need a better way to get the cookie */ + + cookie = ck_unix_pid_get_env (pid, "XDG_SESSION_COOKIE"); + + return cookie; +} + +static CkSession * +get_session_for_unix_process (CkManager *manager, + guint pid) +{ + CkSessionLeader *leader; + CkSession *session; + char *cookie; + + session = NULL; + leader = NULL; + + cookie = get_cookie_for_pid (manager, pid); + if (cookie == NULL) { + goto out; + } + + leader = g_hash_table_lookup (manager->priv->leaders, cookie); + if (leader == NULL) { + goto out; + } + + session = g_hash_table_lookup (manager->priv->sessions, ck_session_leader_peek_session_id (leader)); + + out: + g_free (cookie); + + return session; +} + +static PolKitSession * +new_polkit_session_from_session (CkManager *manager, + CkSession *ck_session) +{ + PolKitSession *pk_session; + PolKitSeat *pk_seat; + uid_t uid; + gboolean is_active; + gboolean is_local; + char *sid; + char *ssid; + char *remote_host; + + sid = NULL; + ssid = NULL; + remote_host = NULL; + + ck_session_get_seat_id (ck_session, &sid, NULL); + + g_object_get (ck_session, + "active", &is_active, + "is-local", &is_local, + "id", &ssid, + "unix-user", &uid, + "remote-host-name", &remote_host, + NULL); + + pk_session = polkit_session_new (); + if (pk_session == NULL) { + goto out; + } + if (!polkit_session_set_uid (pk_session, uid)) { + polkit_session_unref (pk_session); + pk_session = NULL; + goto out; + } + if (!polkit_session_set_ck_objref (pk_session, ssid)) { + polkit_session_unref (pk_session); + pk_session = NULL; + goto out; + } + if (!polkit_session_set_ck_is_active (pk_session, is_active)) { + polkit_session_unref (pk_session); + pk_session = NULL; + goto out; + } + if (!polkit_session_set_ck_is_local (pk_session, is_local)) { + polkit_session_unref (pk_session); + pk_session = NULL; + goto out; + } + if (!is_local) { + if (!polkit_session_set_ck_remote_host (pk_session, remote_host)) { + polkit_session_unref (pk_session); + pk_session = NULL; + goto out; + } + + } + + + pk_seat = polkit_seat_new (); + if (pk_seat == NULL) { + polkit_session_unref (pk_session); + pk_session = NULL; + goto out; + } + if (!polkit_seat_set_ck_objref (pk_seat, sid)) { + polkit_seat_unref (pk_seat); + pk_seat = NULL; + polkit_session_unref (pk_session); + pk_session = NULL; + goto out; + } + if (!polkit_seat_validate (pk_seat)) { + polkit_seat_unref (pk_seat); + pk_seat = NULL; + polkit_session_unref (pk_session); + pk_session = NULL; + goto out; + } + + if (!polkit_session_set_seat (pk_session, pk_seat)) { + polkit_seat_unref (pk_seat); + pk_seat = NULL; + polkit_session_unref (pk_session); + pk_session = NULL; + goto out; + } + polkit_seat_unref (pk_seat); /* session object now owns this object */ + pk_seat = NULL; + + if (!polkit_session_validate (pk_session)) { + polkit_session_unref (pk_session); + pk_session = NULL; + goto out; + } + +out: + g_free (ssid); + g_free (sid); + g_free (remote_host); + + return pk_session; +} + +static PolKitCaller * +new_polkit_caller_from_dbus_name (CkManager *manager, + const char *dbus_name) +{ + PolKitCaller *caller; + pid_t pid; + uid_t uid; + char *selinux_context; + PolKitSession *pk_session; + DBusMessage *message; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter sub_iter; + char *str; + int num_elems; + DBusConnection *con; + DBusError error; + CkSession *ck_session; + + dbus_error_init (&error); + + con = dbus_g_connection_get_connection (manager->priv->connection); + + g_return_val_if_fail (con != NULL, NULL); + g_return_val_if_fail (dbus_name != NULL, NULL); + + selinux_context = NULL; + + caller = NULL; + ck_session = NULL; + pk_session = NULL; + + uid = dbus_bus_get_unix_user (con, dbus_name, &error); + if (dbus_error_is_set (&error)) { + g_warning ("Could not get uid for connection: %s %s", + error.name, + error.message); + dbus_error_free (&error); + goto out; + } + + message = dbus_message_new_method_call ("org.freedesktop.DBus", + "/org/freedesktop/DBus/Bus", + "org.freedesktop.DBus", + "GetConnectionUnixProcessID"); + dbus_message_iter_init_append (message, &iter); + dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &dbus_name); + reply = dbus_connection_send_with_reply_and_block (con, message, -1, &error); + + if (reply == NULL || dbus_error_is_set (&error)) { + g_warning ("Error doing GetConnectionUnixProcessID on Bus: %s: %s", + error.name, + error.message); + dbus_message_unref (message); + if (reply != NULL) { + dbus_message_unref (reply); + } + dbus_error_free (&error); + goto out; + } + dbus_message_iter_init (reply, &iter); + dbus_message_iter_get_basic (&iter, &pid); + dbus_message_unref (message); + dbus_message_unref (reply); + + message = dbus_message_new_method_call ("org.freedesktop.DBus", + "/org/freedesktop/DBus/Bus", + "org.freedesktop.DBus", + "GetConnectionSELinuxSecurityContext"); + dbus_message_iter_init_append (message, &iter); + dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &dbus_name); + reply = dbus_connection_send_with_reply_and_block (con, message, -1, &error); + /* SELinux might not be enabled */ + if (dbus_error_is_set (&error) && + strcmp (error.name, "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown") == 0) { + dbus_message_unref (message); + if (reply != NULL) { + dbus_message_unref (reply); + } + dbus_error_init (&error); + } else if (reply == NULL || dbus_error_is_set (&error)) { + g_warning ("Error doing GetConnectionSELinuxSecurityContext on Bus: %s: %s", error.name, error.message); + dbus_message_unref (message); + if (reply != NULL) { + dbus_message_unref (reply); + } + goto out; + } else { + /* TODO: verify signature */ + dbus_message_iter_init (reply, &iter); + dbus_message_iter_recurse (&iter, &sub_iter); + dbus_message_iter_get_fixed_array (&sub_iter, (void *) &str, &num_elems); + if (str != NULL && num_elems > 0) { + selinux_context = g_strndup (str, num_elems); + } + dbus_message_unref (message); + dbus_message_unref (reply); + } + + ck_session = get_session_for_unix_process (manager, pid); + if (ck_session == NULL) { + /* OK, this is not a catastrophe; just means the caller is not a + * member of any session or that ConsoleKit is not available.. + */ + goto not_in_session; + } + + pk_session = new_polkit_session_from_session (manager, ck_session); + if (pk_session == NULL) { + g_warning ("Got a session but couldn't construct polkit session object!"); + goto out; + } + if (!polkit_session_validate (pk_session)) { + polkit_session_unref (pk_session); + pk_session = NULL; + goto out; + } + +not_in_session: + + caller = polkit_caller_new (); + if (caller == NULL) { + if (pk_session != NULL) { + polkit_session_unref (pk_session); + pk_session = NULL; + } + goto out; + } + + if (!polkit_caller_set_dbus_name (caller, dbus_name)) { + if (pk_session != NULL) { + polkit_session_unref (pk_session); + pk_session = NULL; + } + polkit_caller_unref (caller); + caller = NULL; + goto out; + } + if (!polkit_caller_set_uid (caller, uid)) { + if (pk_session != NULL) { + polkit_session_unref (pk_session); + pk_session = NULL; + } + polkit_caller_unref (caller); + caller = NULL; + goto out; + } + if (!polkit_caller_set_pid (caller, pid)) { + if (pk_session != NULL) { + polkit_session_unref (pk_session); + pk_session = NULL; + } + polkit_caller_unref (caller); + caller = NULL; + goto out; + } + if (selinux_context != NULL) { + if (!polkit_caller_set_selinux_context (caller, selinux_context)) { + if (pk_session != NULL) { + polkit_session_unref (pk_session); + pk_session = NULL; + } + polkit_caller_unref (caller); + caller = NULL; + goto out; + } + } + if (pk_session != NULL) { + if (!polkit_caller_set_ck_session (caller, pk_session)) { + if (pk_session != NULL) { + polkit_session_unref (pk_session); + pk_session = NULL; + } + polkit_caller_unref (caller); + caller = NULL; + goto out; + } + polkit_session_unref (pk_session); /* caller object now own this object */ + pk_session = NULL; + } + + if (!polkit_caller_validate (caller)) { + polkit_caller_unref (caller); + caller = NULL; + goto out; + } + +out: + g_free (selinux_context); + + return caller; +} + +static gboolean +_check_polkit_for_action (CkManager *manager, + DBusGMethodInvocation *context, + const char *action) +{ + const char *sender; + GError *error; + DBusError dbus_error; + PolKitCaller *pk_caller; + PolKitAction *pk_action; + PolKitResult pk_result; + + error = NULL; + + g_debug ("constructing polkit data"); + + /* Check that caller is privileged */ + sender = dbus_g_method_get_sender (context); + dbus_error_init (&dbus_error); + + pk_caller = new_polkit_caller_from_dbus_name (manager, sender); + if (pk_caller == NULL) { + error = g_error_new (CK_MANAGER_ERROR, + CK_MANAGER_ERROR_GENERAL, + "Error getting information about caller: %s: %s", + dbus_error.name, + dbus_error.message); + dbus_error_free (&dbus_error); + dbus_g_method_return_error (context, error); + g_error_free (error); + return FALSE; + } + + pk_action = polkit_action_new (); + polkit_action_set_action_id (pk_action, action); + + g_debug ("checking if caller %s is authorized", sender); + + /* this version crashes if error is used */ + pk_result = polkit_context_is_caller_authorized (manager->priv->pol_ctx, + pk_action, + pk_caller, + TRUE, + NULL); + g_debug ("answer is: %s", (pk_result == POLKIT_RESULT_YES) ? "yes" : "no"); + + polkit_caller_unref (pk_caller); + polkit_action_unref (pk_action); + + if (pk_result != POLKIT_RESULT_YES) { + error = g_error_new (CK_MANAGER_ERROR, + CK_MANAGER_ERROR_NOT_PRIVILEGED, + "Not privileged for action: %s", + action); + dbus_error_free (&dbus_error); + dbus_g_method_return_error (context, error); + g_error_free (error); + return FALSE; + } + + return TRUE; +} + +/* adapted from PolicyKit */ +static gboolean +get_caller_info (CkManager *manager, + const char *sender, + uid_t *calling_uid, + pid_t *calling_pid) +{ + gboolean res; + GError *error = NULL; + + res = FALSE; + + if (sender == NULL) { + goto out; + } + + if (! dbus_g_proxy_call (manager->priv->bus_proxy, "GetConnectionUnixUser", &error, + G_TYPE_STRING, sender, + G_TYPE_INVALID, + G_TYPE_UINT, calling_uid, + G_TYPE_INVALID)) { + g_debug ("GetConnectionUnixUser() failed: %s", error->message); + g_error_free (error); + goto out; + } + + if (! dbus_g_proxy_call (manager->priv->bus_proxy, "GetConnectionUnixProcessID", &error, + G_TYPE_STRING, sender, + G_TYPE_INVALID, + G_TYPE_UINT, calling_pid, + G_TYPE_INVALID)) { + g_debug ("GetConnectionUnixProcessID() failed: %s", error->message); + g_error_free (error); + goto out; + } + + res = TRUE; + + g_debug ("uid = %d", *calling_uid); + g_debug ("pid = %d", *calling_pid); + +out: + return res; +} + +/* + Example: + dbus-send --system --dest=org.freedesktop.ConsoleKit \ + --type=method_call --print-reply --reply-timeout=2000 \ + /org/freedesktop/ConsoleKit/Manager \ + org.freedesktop.ConsoleKit.Manager.Restart +*/ +gboolean +ck_manager_restart (CkManager *manager, + DBusGMethodInvocation *context) +{ + gboolean ret; + gboolean res; + const char *action; + GError *error; + + ret = FALSE; + + if (g_hash_table_size (manager->priv->sessions) > 1) { + action = "org.freedesktop.consolekit.system.restart-multiple-users"; + } else { + action = "org.freedesktop.consolekit.system.restart"; + } + + g_debug ("ConsoleKit Restart: %s", action); + + res = _check_polkit_for_action (manager, context, action); + + if (! res) { + goto out; + } + + g_debug ("ConsoleKit preforming Restart: %s", action); + + error = NULL; + res = g_spawn_command_line_async (LIBDIR "/ConsoleKit/scripts/ck-system-restart", + &error); + if (! res) { + GError *new_error; + + g_warning ("Unable to restart system: %s", error->message); + + new_error = g_error_new (CK_MANAGER_ERROR, + CK_MANAGER_ERROR_GENERAL, + "Unable to restart system: %s", error->message); + dbus_g_method_return_error (context, new_error); + g_error_free (new_error); + + g_error_free (error); + } else { + ret = TRUE; + dbus_g_method_return (context); + } + + out: + + return ret; +} + +gboolean +ck_manager_stop (CkManager *manager, + DBusGMethodInvocation *context) +{ + gboolean ret; + gboolean res; + const char *action; + GError *error; + + ret = TRUE; + + if (g_hash_table_size (manager->priv->sessions) > 1) { + action = "org.freedesktop.consolekit.system.stop-multiple-users"; + } else { + action = "org.freedesktop.consolekit.system.stop"; + } + + res = _check_polkit_for_action (manager, context, action); + if (! res) { + goto out; + } + + g_debug ("Stopping system"); + error = NULL; + res = g_spawn_command_line_async (LIBDIR "/ConsoleKit/scripts/ck-system-stop", + &error); + if (! res) { + GError *new_error; + + g_warning ("Unable to stop system: %s", error->message); + + new_error = g_error_new (CK_MANAGER_ERROR, + CK_MANAGER_ERROR_GENERAL, + "Unable to stop system: %s", error->message); + dbus_g_method_return_error (context, new_error); + g_error_free (new_error); + + g_error_free (error); + } else { + ret = TRUE; + dbus_g_method_return (context); + } + + out: + return ret; +} + static void on_seat_active_session_changed (CkSeat *seat, const char *ssid, @@ -844,51 +1403,6 @@ find_seat_for_session (CkManager *manager, return seat; } -/* adapted from PolicyKit */ -static gboolean -get_caller_info (CkManager *manager, - const char *sender, - uid_t *calling_uid, - pid_t *calling_pid) -{ - gboolean res; - GError *error = NULL; - - res = FALSE; - - if (sender == NULL) { - goto out; - } - - if (! dbus_g_proxy_call (manager->priv->bus_proxy, "GetConnectionUnixUser", &error, - G_TYPE_STRING, sender, - G_TYPE_INVALID, - G_TYPE_UINT, calling_uid, - G_TYPE_INVALID)) { - g_debug ("GetConnectionUnixUser() failed: %s", error->message); - g_error_free (error); - goto out; - } - - if (! dbus_g_proxy_call (manager->priv->bus_proxy, "GetConnectionUnixProcessID", &error, - G_TYPE_STRING, sender, - G_TYPE_INVALID, - G_TYPE_UINT, calling_pid, - G_TYPE_INVALID)) { - g_debug ("GetConnectionUnixProcessID() failed: %s", error->message); - g_error_free (error); - goto out; - } - - res = TRUE; - - g_debug ("uid = %d", *calling_uid); - g_debug ("pid = %d", *calling_pid); - -out: - return res; -} - static gboolean manager_set_system_idle_hint (CkManager *manager, gboolean idle_hint) @@ -1278,19 +1792,6 @@ ck_manager_get_session_for_cookie (CkManager *manager, return TRUE; } -static char * -get_cookie_for_pid (CkManager *manager, - guint pid) -{ - char *cookie; - - /* FIXME: need a better way to get the cookie */ - - cookie = ck_unix_pid_get_env (pid, "XDG_SESSION_COOKIE"); - - return cookie; -} - /* Example: dbus-send --system --dest=org.freedesktop.ConsoleKit \ @@ -1682,10 +2183,60 @@ bus_name_owner_changed (DBusGProxy *bus_proxy, } static gboolean +pk_io_watch_have_data (GIOChannel *channel, + GIOCondition condition, + gpointer user_data) +{ + int fd; + PolKitContext *pk_context = user_data; + + fd = g_io_channel_unix_get_fd (channel); + polkit_context_io_func (pk_context, fd); + return TRUE; +} + +static int +pk_io_add_watch (PolKitContext *pk_context, + int fd) +{ + guint id = 0; + GIOChannel *channel; + + channel = g_io_channel_unix_new (fd); + if (channel == NULL) { + goto out; + } + + id = g_io_add_watch (channel, G_IO_IN, pk_io_watch_have_data, pk_context); + if (id == 0) { + g_io_channel_unref (channel); + goto out; + } + g_io_channel_unref (channel); + +out: + return id; +} + +static void +pk_io_remove_watch (PolKitContext *pk_context, + int watch_id) +{ + g_source_remove (watch_id); +} + +static gboolean register_manager (CkManager *manager) { GError *error = NULL; + manager->priv->pol_ctx = polkit_context_new (); + polkit_context_set_io_watch_functions (manager->priv->pol_ctx, pk_io_add_watch, pk_io_remove_watch); + if (! polkit_context_init (manager->priv->pol_ctx, NULL)) { + g_critical ("cannot initialize libpolkit"); + return FALSE; + } + error = NULL; manager->priv->connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); if (manager->priv->connection == NULL) { |