]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
pam-systemd: talk to logind via varlink 35264/head
authorLennart Poettering <lennart@poettering.net>
Mon, 18 Nov 2024 10:25:20 +0000 (11:25 +0100)
committerLennart Poettering <lennart@poettering.net>
Wed, 15 Jan 2025 10:58:49 +0000 (11:58 +0100)
This makes sure we now use Varlink per default as transport for
allocating sessions.

This reduces the time it takes to do one run0 cycle by roughly ~10% on my
completely synthetic test setup (assuming the target user's service
manager is already started)

The D-Bus codepaths are kept in place for two reasons:
* To make upgrades easy
* If the user actually sets resource properties on the PAM session we
  fall back to the D-Bus codepaths, as we currently have no way to
  encode the scope properties in JSON, this is only supported for D-Bus
  serialization.

The latter should be revisited once it is possible to allocate a scope
unit from PID1 via varlink.

src/login/pam_systemd.c

index e0861f934c8208e5e6a48c1099ccde3fbe6d421e..fbc172e9a5abdc31bd818517b9917fe6eec965b9 100644 (file)
@@ -18,6 +18,9 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include "sd-bus.h"
+#include "sd-varlink.h"
+
 #include "alloc-util.h"
 #include "audit-util.h"
 #include "bus-common-errors.h"
@@ -35,6 +38,7 @@
 #include "format-util.h"
 #include "fs-util.h"
 #include "hostname-util.h"
+#include "json-util.h"
 #include "locale-util.h"
 #include "login-util.h"
 #include "macro.h"
@@ -1022,6 +1026,16 @@ static void session_context_mangle(
         c->remote = !isempty(c->remote_host) && !is_localhost(c->remote_host);
 }
 
+static bool can_use_varlink(const SessionContext *c) {
+        /* Since PID 1 currently doesn't do Varlink right now, we cannot directly set properties for the
+         * scope, for now. */
+        return !c->memory_max &&
+                !c->runtime_max_sec &&
+                !c->tasks_max &&
+                !c->cpu_weight &&
+                !c->io_weight;
+}
+
 static int register_session(
                 pam_handle_t *handle,
                 SessionContext *c,
@@ -1029,13 +1043,6 @@ static int register_session(
                 bool debug,
                 char **ret_seat) {
 
-        /* Let's release the D-Bus connection once this function exits, after all the session might live
-         * quite a long time, and we are not going to process the bus connection in that time, so let's
-         * better close before the daemon kicks us off because we are not processing anything. */
-        _cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL;
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
-        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         int r;
 
         assert(handle);
@@ -1045,18 +1052,17 @@ static int register_session(
 
         /* We don't register session class none with logind */
         if (streq(c->class, "none")) {
-                pam_debug_syslog(handle, debug, "Skipping logind registration for session class none");
-                goto skip;
+                pam_debug_syslog(handle, debug, "Skipping logind registration for session class none.");
+                *ret_seat = NULL;
+                return PAM_SUCCESS;
         }
 
         /* Make most of this a NOP on non-logind systems */
-        if (!logind_running())
-                goto skip;
-
-        /* Talk to logind over the message bus */
-        r = pam_acquire_bus_connection(handle, "pam-systemd", debug, &bus, &d);
-        if (r != PAM_SUCCESS)
-                return r;
+        if (!logind_running()) {
+                pam_debug_syslog(handle, debug, "Skipping logind registration as logind is not running.");
+                *ret_seat = NULL;
+                return PAM_SUCCESS;
+        }
 
         pam_debug_syslog(handle, debug,
                          "Asking logind to create session: "
@@ -1071,68 +1077,187 @@ static int register_session(
                          "memory_max=%s tasks_max=%s cpu_weight=%s io_weight=%s runtime_max_sec=%s",
                          strna(c->memory_max), strna(c->tasks_max), strna(c->cpu_weight), strna(c->io_weight), strna(c->runtime_max_sec));
 
-        r = create_session_message(
-                        bus,
-                        handle,
-                        ur,
-                        c,
-                        /* avoid_pidfd = */ false,
-                        &m);
-        if (r < 0)
-                return pam_bus_log_create_error(handle, r);
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; /* the following variables point into this message, hence pin it for longer */
+        _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; /* similar */
+        const char *id = NULL, *object_path = NULL, *runtime_path = NULL, *real_seat = NULL;
+        int session_fd = -EBADF, existing = false;
+        uint32_t original_uid = UID_INVALID, real_vtnr = 0;
 
-        r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
-        if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
-                sd_bus_error_free(&error);
-                pam_debug_syslog(handle, debug,
-                                 "CreateSessionWithPIDFD() API is not available, retrying with CreateSession().");
-
-                m = sd_bus_message_unref(m);
-                r = create_session_message(bus,
-                                           handle,
-                                           ur,
-                                           c,
-                                           /* avoid_pidfd = */ true,
-                                           &m);
+        bool done = false;
+        if (can_use_varlink(c)) {
+
+                r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.Login");
+                if (r < 0)
+                        log_debug_errno(r, "Failed to connect to logind via Varlink, falling back to D-Bus: %m");
+                else {
+                        r = sd_varlink_set_allow_fd_passing_input(vl, true);
+                        if (r < 0)
+                                return pam_syslog_errno(handle, LOG_ERR, r, "Failed to enable input fd passing on Varlink socket: %m");
+
+                        r = sd_varlink_set_allow_fd_passing_output(vl, true);
+                        if (r < 0)
+                                return pam_syslog_errno(handle, LOG_ERR, r, "Failed to enable output fd passing on Varlink socket: %m");
+
+                        r = sd_varlink_set_relative_timeout(vl, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC);
+                        if (r < 0)
+                                return pam_syslog_errno(handle, LOG_ERR, r, "Failed to set relative timeout on Varlink socket: %m");
+
+                        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+                        r = pidref_set_self(&pidref);
+                        if (r < 0)
+                                return pam_syslog_errno(handle, LOG_ERR, r, "Failed to acquire PID reference on ourselves: %m");
+
+                        sd_json_variant *vreply = NULL;
+                        const char *error_id = NULL;
+                        r = sd_varlink_callbo(
+                                        vl,
+                                        "io.systemd.Login.CreateSession",
+                                        &vreply,
+                                        &error_id,
+                                        SD_JSON_BUILD_PAIR_UNSIGNED("UID", ur->uid),
+                                        JSON_BUILD_PAIR_PIDREF("PID", &pidref),
+                                        JSON_BUILD_PAIR_STRING_NON_EMPTY("Service", c->service),
+                                        SD_JSON_BUILD_PAIR("Type", JSON_BUILD_STRING_UNDERSCORIFY(c->type)),
+                                        SD_JSON_BUILD_PAIR("Class", JSON_BUILD_STRING_UNDERSCORIFY(c->class)),
+                                        JSON_BUILD_PAIR_STRING_NON_EMPTY("Desktop", c->desktop),
+                                        JSON_BUILD_PAIR_STRING_NON_EMPTY("Seat", c->seat),
+                                        SD_JSON_BUILD_PAIR_CONDITION(c->vtnr > 0, "VTNr", SD_JSON_BUILD_UNSIGNED(c->vtnr)),
+                                        JSON_BUILD_PAIR_STRING_NON_EMPTY("TTY", c->tty),
+                                        JSON_BUILD_PAIR_STRING_NON_EMPTY("Display", c->display),
+                                        SD_JSON_BUILD_PAIR_BOOLEAN("Remote", c->remote),
+                                        JSON_BUILD_PAIR_STRING_NON_EMPTY("RemoteUser", c->remote_user),
+                                        JSON_BUILD_PAIR_STRING_NON_EMPTY("RemoteHost", c->remote_host));
+                        if (r < 0)
+                                return pam_syslog_errno(handle, LOG_ERR, r,
+                                                        "Failed to register session: %s", error_id);
+                        if (streq_ptr(error_id, "io.systemd.Login.AlreadySessionMember")) {
+                                /* We are already in a session, don't do anything */
+                                pam_debug_syslog(handle, debug, "Not creating session: %s", error_id);
+                                *ret_seat = NULL;
+                                return PAM_SUCCESS;
+                        }
+                        if (error_id)
+                                return pam_syslog_errno(handle, LOG_ERR, sd_varlink_error_to_errno(error_id, vreply),
+                                                        "Failed to issue CreateSession() varlink call: %s", error_id);
+
+                        struct {
+                                const char *id;
+                                const char *runtime_path;
+                                unsigned session_fd_idx;
+                                uid_t uid;
+                                const char *seat;
+                                unsigned vtnr;
+                                bool existing;
+                        } p = {
+                                .session_fd_idx = UINT_MAX,
+                                .uid = UID_INVALID,
+                        };
+
+                        static const sd_json_dispatch_field dispatch_table[] = {
+                                { "Id",                    SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, voffsetof(p, id),             SD_JSON_MANDATORY },
+                                { "RuntimePath",           SD_JSON_VARIANT_STRING,        json_dispatch_const_path,      voffsetof(p, runtime_path),   SD_JSON_MANDATORY },
+                                { "SessionFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,       voffsetof(p, session_fd_idx), SD_JSON_MANDATORY },
+                                { "UID",                   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid,      voffsetof(p, uid),            SD_JSON_MANDATORY },
+                                { "Seat",                  SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, voffsetof(p, seat),           0                 },
+                                { "VTNr",                  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint,         voffsetof(p, vtnr),           0                 },
+                                {}
+                        };
+
+                        r = sd_json_dispatch(vreply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
+                        if (r < 0)
+                                return pam_syslog_errno(handle, LOG_ERR, r, "Failed to parse CreateSession() reply: %m");
+
+                        session_fd = sd_varlink_peek_fd(vl, p.session_fd_idx);
+                        if (session_fd < 0)
+                                return pam_syslog_errno(handle, LOG_ERR, session_fd, "Failed to extract session fd from CreateSession() reply: %m");
+
+                        id = p.id;
+                        runtime_path = p.runtime_path;
+                        original_uid = p.uid;
+                        real_seat = p.seat;
+                        real_vtnr = p.vtnr;
+                        existing = false; /* Even on D-Bus logind only returns false these days */
+
+                        done = true;
+                }
+        }
+
+        if (!done) {
+                /* Let's release the D-Bus connection once we are done here, after all the session might live
+                 * quite a long time, and we are not going to process the bus connection in that time, so
+                 * let's better close before the daemon kicks us off because we are not processing
+                 * anything. */
+                _cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL;
+                _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+
+                /* Talk to logind over the message bus */
+                r = pam_acquire_bus_connection(handle, "pam-systemd", debug, &bus, &d);
+                if (r != PAM_SUCCESS)
+                        return r;
+
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+                r = create_session_message(
+                                bus,
+                                handle,
+                                ur,
+                                c,
+                                /* avoid_pidfd = */ false,
+                                &m);
                 if (r < 0)
                         return pam_bus_log_create_error(handle, r);
 
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
                 r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
-        }
-        if (r < 0) {
-                if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) {
-                        /* We are already in a session, don't do anything */
+                if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
+                        sd_bus_error_free(&error);
                         pam_debug_syslog(handle, debug,
-                                         "Not creating session: %s", bus_error_message(&error, r));
-                        goto skip;
+                                         "CreateSessionWithPIDFD() API is not available, retrying with CreateSession().");
+
+                        m = sd_bus_message_unref(m);
+                        r = create_session_message(bus,
+                                                   handle,
+                                                   ur,
+                                                   c,
+                                                   /* avoid_pidfd = */ true,
+                                                   &m);
+                        if (r < 0)
+                                return pam_bus_log_create_error(handle, r);
+
+                        r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
+                }
+                if (r < 0) {
+                        if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) {
+                                /* We are already in a session, don't do anything */
+                                pam_debug_syslog(handle, debug,
+                                                 "Not creating session: %s", bus_error_message(&error, r));
+                                *ret_seat = NULL;
+                                return PAM_SUCCESS;
+                        }
+
+                        pam_syslog(handle, LOG_ERR,
+                                   "Failed to create session: %s", bus_error_message(&error, r));
+                        return PAM_SESSION_ERR;
                 }
 
-                pam_syslog(handle, LOG_ERR,
-                           "Failed to create session: %s", bus_error_message(&error, r));
-                return PAM_SESSION_ERR;
+                r = sd_bus_message_read(
+                                reply,
+                                "soshusub",
+                                &id,
+                                &object_path,
+                                &runtime_path,
+                                &session_fd,
+                                &original_uid,
+                                &real_seat,
+                                &real_vtnr,
+                                &existing);
+                if (r < 0)
+                        return pam_bus_log_parse_error(handle, r);
         }
 
-        const char *id, *object_path, *runtime_path, *real_seat;
-        int session_fd = -EBADF, existing;
-        uint32_t original_uid, real_vtnr;
-        r = sd_bus_message_read(
-                        reply,
-                        "soshusub",
-                        &id,
-                        &object_path,
-                        &runtime_path,
-                        &session_fd,
-                        &original_uid,
-                        &real_seat,
-                        &real_vtnr,
-                        &existing);
-        if (r < 0)
-                return pam_bus_log_parse_error(handle, r);
-
         pam_debug_syslog(handle, debug,
                          "Reply from logind: "
                          "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
-                         id, object_path, runtime_path, session_fd, real_seat, real_vtnr, original_uid);
+                         id, strna(object_path), runtime_path, session_fd, real_seat, real_vtnr, original_uid);
 
         /* Please update manager_default_environment() in core/manager.c accordingly if more session envvars
          * shall be added. */
@@ -1197,17 +1322,15 @@ static int register_session(
 
         /* Everything worked, hence let's patch in the data we learned. Since 'real_set' points into the
          * D-Bus message, let's copy it and return it as a buffer */
-        char *rs = strdup(real_seat);
-        if (!rs)
-                return pam_log_oom(handle);
+        char *rs = NULL;
+        if (real_seat) {
+                rs = strdup(real_seat);
+                if (!rs)
+                        return pam_log_oom(handle);
+        }
 
         c->seat = *ret_seat = rs;
         c->vtnr = real_vtnr;
-
-        return PAM_SUCCESS;
-
-skip:
-        *ret_seat = NULL;
         return PAM_SUCCESS;
 }
 
@@ -1343,20 +1466,47 @@ _public_ PAM_EXTERN int pam_sm_close_session(
 
         id = pam_getenv(handle, "XDG_SESSION_ID");
         if (id && !existing) {
-                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-                _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+                _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
+                bool done = false;
 
-                /* Before we go and close the FIFO we need to tell logind that this is a clean session
-                 * shutdown, so that it doesn't just go and slaughter us immediately after closing the fd */
+                r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.Login");
+                if (r < 0)
+                        log_debug_errno(r, "Failed to connect to logind via Varlink, falling back to D-Bus: %m");
+                else {
+                        _cleanup_(sd_json_variant_unrefp) sd_json_variant *vreply = NULL;
+                        const char *error_id = NULL;
+                        r = sd_varlink_callbo(
+                                        vl,
+                                        "io.systemd.Login.ReleaseSession",
+                                        /* ret_reply= */ NULL,
+                                        &error_id,
+                                        SD_JSON_BUILD_PAIR_STRING("Id", id));
+                        if (r < 0)
+                                return pam_syslog_errno(handle, LOG_ERR, r, "Failed to register session: %s", error_id);
+                        if (error_id)
+                                return pam_syslog_errno(handle, LOG_ERR, sd_varlink_error_to_errno(error_id, vreply),
+                                                        "Failed to issue ReleaseSession() varlink call: %s", error_id);
 
-                r = pam_acquire_bus_connection(handle, "pam-systemd", debug, &bus, NULL);
-                if (r != PAM_SUCCESS)
-                        return r;
+                        done = true;
+                }
 
-                r = bus_call_method(bus, bus_login_mgr, "ReleaseSession", &error, NULL, "s", id);
-                if (r < 0)
-                        return pam_syslog_pam_error(handle, LOG_ERR, PAM_SESSION_ERR,
-                                                    "Failed to release session: %s", bus_error_message(&error, r));
+                if (!done) {
+                        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                        _cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL;
+                        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+
+                        /* Before we go and close the FIFO we need to tell logind that this is a clean session
+                         * shutdown, so that it doesn't just go and slaughter us immediately after closing the fd */
+
+                        r = pam_acquire_bus_connection(handle, "pam-systemd", debug, &bus, &d);
+                        if (r != PAM_SUCCESS)
+                                return r;
+
+                        r = bus_call_method(bus, bus_login_mgr, "ReleaseSession", &error, NULL, "s", id);
+                        if (r < 0)
+                                return pam_syslog_pam_error(handle, LOG_ERR, PAM_SESSION_ERR,
+                                                            "Failed to release session: %s", bus_error_message(&error, r));
+                }
         }
 
         /* Note that we are knowingly leaking the FIFO fd here. This way, logind can watch us die. If we