chiark / gitweb /
logind: add session controllers
authorDavid Herrmann <dh.herrmann@gmail.com>
Tue, 17 Sep 2013 15:39:56 +0000 (17:39 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 17 Sep 2013 16:33:18 +0000 (11:33 -0500)
A session usually has only a single compositor or other application that
controls graphics and input devices on it. To avoid multiple applications
from hijacking each other's devices or even using the devices in parallel,
we add session controllers.

A session controller is an application that manages a session. Specific
API calls may be limited to controllers to avoid others from getting
unprivileged access to restricted resources. A session becomes a
controller by calling the RequestControl() dbus API call. It can drop it
via ReleaseControl().

logind tracks bus-names to release the controller once an application
closes the bus. We use the new bus-name tracking to do that. Note that
during ReleaseControl() we need to check whether some other session also
tracks the name before we remove it from the bus-name tracking list.

Currently, we only allow one controller at a time. However, the public API
does not enforce this restriction. So if it makes sense, we can allow
multiple controllers in parallel later. Or we can add a "scope" parameter,
which allows a different controller for graphics-devices, sound-devices
and whatever you want.
Note that currently you get -EBUSY if there is already a controller. You
can force the RequestControl() call (root-only) to drop the current
controller and recover the session during an emergency. To recover a seat,
this is not needed, though. You can simply create a new session or
force-activate it.

To become a session controller, a dbus caller must either be root or the
same user as the user of the session. This allows us to run a session
compositor as user and we no longer need any CAP_SYS_ADMIN.

src/login/logind-dbus.c
src/login/logind-session-dbus.c
src/login/logind-session.c
src/login/logind-session.h
src/login/logind.c

index 5decf8114c9c9f3f1e39ab71531e6cfd110cd3ec..113a2b73b6289f0a068f0cb17d0a1fd1d8194ef7 100644 (file)
@@ -2473,8 +2473,16 @@ DBusHandlerResult bus_message_filter(
                         goto finish;
                 }
 
+                /* drop all controllers owned by this name */
                 if (*old && !*new && (key = hashmap_remove(m->busnames, old))) {
+                        Session *session;
+                        Iterator i;
+
                         free(key);
+
+                        HASHMAP_FOREACH(session, m->sessions, i)
+                                if (session_is_controller(session, old))
+                                        session_drop_controller(session);
                 }
         }
 
index 2cc4d8587bff6452f6fee64142deea0efcdbe6b9..35bf4480cbf6490d46bd44757f5ba6bb2d3bed8e 100644 (file)
         "   <arg name=\"who\" type=\"s\"/>\n"                           \
         "   <arg name=\"signal\" type=\"s\"/>\n"                        \
         "  </method>\n"                                                 \
+        "  <method name=\"TakeControl\"/>\n"                            \
+        "   <arg name=\"force\" type=\"b\"/>\n"                         \
+        "  </method>\n"                                                 \
+        "  <method name=\"ReleaseControl\"/>\n"                         \
         "  <signal name=\"Lock\"/>\n"                                   \
         "  <signal name=\"Unlock\"/>\n"                                 \
         "  <property name=\"Id\" type=\"s\" access=\"read\"/>\n"        \
@@ -366,6 +370,44 @@ static DBusHandlerResult session_message_dispatch(
                 if (!reply)
                         goto oom;
 
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "TakeControl")) {
+                dbus_bool_t force;
+                unsigned long ul;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_BOOLEAN, &force,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(connection, message, &error, -EINVAL);
+
+                ul = dbus_bus_get_unix_user(connection, dbus_message_get_sender(message), &error);
+                if (ul == (unsigned long) -1)
+                        return bus_send_error_reply(connection, message, &error, -EIO);
+
+                if (ul != 0 && (force || ul != s->user->uid))
+                        return bus_send_error_reply(connection, message, NULL, -EPERM);
+
+                r = session_set_controller(s, bus_message_get_sender_with_fallback(message), force);
+                if (r < 0)
+                        return bus_send_error_reply(connection, message, NULL, r);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "ReleaseControl")) {
+                const char *sender = bus_message_get_sender_with_fallback(message);
+
+                if (!session_is_controller(s, sender))
+                        return bus_send_error_reply(connection, message, NULL, -EPERM);
+
+                session_drop_controller(s);
+
+                reply = dbus_message_new_method_return(message);
+                if (!reply)
+                        goto oom;
+
         } else {
                 const BusBoundProperties bps[] = {
                         { "org.freedesktop.login1.Session", bus_login_session_properties,      s       },
index 2d22a68b6eb441438f6c75a51ab58f886d98c9af..fe5fa27be11b161c4103cc2dcf201e8111c4757b 100644 (file)
@@ -73,6 +73,8 @@ void session_free(Session *s) {
         if (s->in_gc_queue)
                 LIST_REMOVE(Session, gc_queue, s->manager->session_gc_queue, s);
 
+        session_drop_controller(s);
+
         if (s->user) {
                 LIST_REMOVE(Session, sessions_by_user, s->user->sessions, s);
 
@@ -918,6 +920,52 @@ int session_kill(Session *s, KillWho who, int signo) {
         return manager_kill_unit(s->manager, s->scope, who, signo, NULL);
 }
 
+bool session_is_controller(Session *s, const char *sender)
+{
+        assert(s);
+
+        return streq_ptr(s->controller, sender);
+}
+
+int session_set_controller(Session *s, const char *sender, bool force) {
+        char *t;
+        int r;
+
+        assert(s);
+        assert(sender);
+
+        if (session_is_controller(s, sender))
+                return 0;
+        if (s->controller && !force)
+                return -EBUSY;
+
+        t = strdup(sender);
+        if (!t)
+                return -ENOMEM;
+
+        r = manager_watch_busname(s->manager, sender);
+        if (r) {
+                free(t);
+                return r;
+        }
+
+        session_drop_controller(s);
+
+        s->controller = t;
+        return 0;
+}
+
+void session_drop_controller(Session *s) {
+        assert(s);
+
+        if (!s->controller)
+                return;
+
+        manager_drop_busname(s->manager, s->controller);
+        free(s->controller);
+        s->controller = NULL;
+}
+
 static const char* const session_state_table[_SESSION_STATE_MAX] = {
         [SESSION_OPENING] = "opening",
         [SESSION_ONLINE] = "online",
index 9cf64850be8bc72b90ac60b9fc09c88573192d2c..839fb230dcb91725c845b587ed356c6b763a3230 100644 (file)
@@ -106,6 +106,8 @@ struct Session {
 
         DBusMessage *create_message;
 
+        char *controller;
+
         LIST_FIELDS(Session, sessions_by_user);
         LIST_FIELDS(Session, sessions_by_seat);
 
@@ -154,3 +156,7 @@ SessionClass session_class_from_string(const char *s) _pure_;
 
 const char *kill_who_to_string(KillWho k) _const_;
 KillWho kill_who_from_string(const char *s) _pure_;
+
+bool session_is_controller(Session *s, const char *sender);
+int session_set_controller(Session *s, const char *sender, bool force);
+void session_drop_controller(Session *s);
index 89e4eeea16db1dbdcd8121bc32ff241d48bea839..c99c2844e8deba0ab25a2f0ed8e3c31b1b4f0cf4 100644 (file)
@@ -391,11 +391,21 @@ int manager_watch_busname(Manager *m, const char *name) {
 }
 
 void manager_drop_busname(Manager *m, const char *name) {
+        Session *session;
+        Iterator i;
         char *key;
 
         assert(m);
         assert(name);
 
+        if (!hashmap_get(m->busnames, name))
+                return;
+
+        /* keep it if the name still owns a controller */
+        HASHMAP_FOREACH(session, m->sessions, i)
+                if (session_is_controller(session, name))
+                        return;
+
         key = hashmap_remove(m->busnames, name);
         if (key)
                 free(key);