From 5099a50d4398e190387d204f5df81cc176bd33e2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 27 Nov 2023 18:35:32 +0100 Subject: [PATCH 01/10] logind: track user service managers as 'manager' session class Previously, all user code was part of a session except for the code run as part of user@.service, which wasn't. This tries to make this more uniform: we'll track the user@.service runtime also as a session, but of the special type "manager". This means we have a really good overview finally of all user code that is running and can make decisions on what to start when and how long to keep it around. The pam_systemd client side will now be reasonably uniform: it just calls the CreateSession() bus call with the right class, and we'll return any data it needs. This means the weird "side-channel" we previously used to initialize XDG_RUNTIME_DIR for the user@.service goes away (see next commit). This conditionalizes various behaviours now cleanly depending on the session class: 1. SESSION_CLASS_WANTS_SCOPE() will be true for all classes except for the manager class. It declares whther the client shall be migrated into their own scope, which we generally want for sessions but not for the manager, since it already has its own service unit. 2. SESSION_CLASS_WANTS_SERVICE_MANAGER() will be true for all classes except for the manager class. It declares whether we shall start the service manager if a session of this class is around. Of course, this is off for the service manager, since this would always pin itself. 3. SESSION_CLASS_PIN_USER() will be true for all classes except for the manager class. It declares whether the we shall keep the User structure around for a user as long as the session is around. Now you might wonder why have these as three functions, even though they mostly give the same answers? That's because this all is preparation to add further session classes later that will return different answers for the three calls. (For example, a later patch adds "background-light" which will return true for SESSION_CLASS_WANTS_SCOPE() and SESSION_CLASS_PIN_USER(), but false for SESSION_CLASS_WANTS_SERVICE_MANAGER(). i.e. it will get a scope, and pin user tracking, but not start a service manager. --- src/login/logind-dbus.c | 30 +++++++-------- src/login/logind-session-dbus.c | 2 +- src/login/logind-session.c | 27 +++++++++----- src/login/logind-session.h | 17 +++++++-- src/login/logind-user.c | 65 +++++++++++++++++++++++++-------- src/login/logind-user.h | 1 + 6 files changed, 98 insertions(+), 44 deletions(-) diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 4f8525b46a1..31efb0d113d 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -865,25 +865,19 @@ static int create_session( c = SESSION_USER; } - /* Check if we are already in a logind session. Or if we are in user@.service - * which is a special PAM session that avoids creating a logind session. */ - r = manager_get_user_by_pid(m, leader.pid, NULL); + /* Check if we are already in a logind session, and if so refuse. */ + r = manager_get_session_by_pidref(m, &leader, /* ret_session= */ NULL); if (r < 0) return r; if (r > 0) return sd_bus_error_setf(error, BUS_ERROR_SESSION_BUSY, "Already running in a session or user slice"); - /* - * Old gdm and lightdm start the user-session on the same VT as - * the greeter session. But they destroy the greeter session - * after the user-session and want the user-session to take - * over the VT. We need to support this for - * backwards-compatibility, so make sure we allow new sessions - * on a VT that a greeter is running on. Furthermore, to allow - * re-logins, we have to allow a greeter to take over a used VT for - * the exact same reasons. - */ + /* Old gdm and lightdm start the user-session on the same VT as the greeter session. But they destroy + * the greeter session after the user-session and want the user-session to take over the VT. We need + * to support this for backwards-compatibility, so make sure we allow new sessions on a VT that a + * greeter is running on. Furthermore, to allow re-logins, we have to allow a greeter to take over a + * used VT for the exact same reasons. */ if (c != SESSION_GREETER && vtnr > 0 && vtnr < MALLOC_ELEMENTSOF(m->seat0->positions) && @@ -1017,8 +1011,14 @@ static int create_session( session->create_message = sd_bus_message_ref(message); - /* Now, let's wait until the slice unit and stuff got created. We send the reply back from - * session_send_create_reply(). */ + /* Now call into session_send_create_reply(), which will reply to this method call for us. Or it + * won't – in case we just spawned a session scope and/or user service manager, and they aren't ready + * yet. We'll call session_create_reply() again once the session scope or the user service manager is + * ready, where the function will check again if a reply is then ready to be sent, and then do so if + * all is complete - or wait again. */ + r = session_send_create_reply(session, /* error= */ NULL); + if (r < 0) + return r; return 1; diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c index 15f04c4a66a..6da3dd59d5a 100644 --- a/src/login/logind-session-dbus.c +++ b/src/login/logind-session-dbus.c @@ -800,7 +800,7 @@ static bool session_ready(Session *s) { /* Returns true when the session is ready, i.e. all jobs we enqueued for it are done (regardless if successful or not) */ return !s->scope_job && - !s->user->service_job; + (!SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class) || !s->user->service_job); } int session_send_create_reply(Session *s, sd_bus_error *error) { diff --git a/src/login/logind-session.c b/src/login/logind-session.c index c60811f72ef..d8fe018e1da 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -722,8 +722,11 @@ static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_er assert(s); assert(s->user); + if (!SESSION_CLASS_WANTS_SCOPE(s->class)) + return 0; + if (!s->scope) { - _cleanup_strv_free_ char **after = NULL; + _cleanup_strv_free_ char **wants = NULL, **after = NULL; _cleanup_free_ char *scope = NULL; const char *description; @@ -735,6 +738,12 @@ static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_er description = strjoina("Session ", s->id, " of User ", s->user->user_record->user_name); + /* These two have StopWhenUnneeded= set, hence add a dep towards them */ + wants = strv_new(s->user->runtime_dir_service, + SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class) ? s->user->service : STRV_IGNORE); + if (!wants) + return log_oom(); + /* We usually want to order session scopes after systemd-user-sessions.service since the * latter unit is used as login session barrier for unprivileged users. However the barrier * doesn't apply for root as sysadmin should always be able to log in (and without waiting @@ -754,9 +763,7 @@ static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_er &s->leader, s->user->slice, description, - /* These two have StopWhenUnneeded= set, hence add a dep towards them */ - STRV_MAKE(s->user->runtime_dir_service, - s->user->service), + wants, after, user_record_home_directory(s->user->user_record), properties, @@ -1632,11 +1639,13 @@ static const char* const session_type_table[_SESSION_TYPE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(session_type, SessionType); static const char* const session_class_table[_SESSION_CLASS_MAX] = { - [SESSION_USER] = "user", - [SESSION_USER_EARLY] = "user-early", - [SESSION_GREETER] = "greeter", - [SESSION_LOCK_SCREEN] = "lock-screen", - [SESSION_BACKGROUND] = "background", + [SESSION_USER] = "user", + [SESSION_USER_EARLY] = "user-early", + [SESSION_GREETER] = "greeter", + [SESSION_LOCK_SCREEN] = "lock-screen", + [SESSION_BACKGROUND] = "background", + [SESSION_MANAGER] = "manager", + [SESSION_MANAGER_EARLY] = "manager-early", }; DEFINE_STRING_TABLE_LOOKUP(session_class, SessionClass); diff --git a/src/login/logind-session.h b/src/login/logind-session.h index a9c44b465f7..2b61f966b5f 100644 --- a/src/login/logind-session.h +++ b/src/login/logind-session.h @@ -25,13 +25,24 @@ typedef enum SessionClass { SESSION_GREETER, /* A login greeter pseudo-session */ SESSION_LOCK_SCREEN, /* A lock screen */ SESSION_BACKGROUND, /* Things like cron jobs, which are non-interactive */ + SESSION_MANAGER, /* The service manager */ + SESSION_MANAGER_EARLY, /* The service manager for root (which is allowed to run before systemd-user-sessions.service) */ _SESSION_CLASS_MAX, _SESSION_CLASS_INVALID = -EINVAL, } SessionClass; -/* Whether we shall allow sessions of this class to run before 'systemd-user-sessions.service'. For now, - * there's only one class we allow this for. It's generally set for root sessions, but no one else. */ -#define SESSION_CLASS_IS_EARLY(class) ((class) == SESSION_USER_EARLY) +/* Whether we shall allow sessions of this class to run before 'systemd-user-sessions.service'. It's + * generally set for root sessions, but no one else. */ +#define SESSION_CLASS_IS_EARLY(class) IN_SET((class), SESSION_USER_EARLY, SESSION_MANAGER_EARLY) + +/* Which session classes want their own scope units? (all of them, except the manager, which comes in its own service unit already */ +#define SESSION_CLASS_WANTS_SCOPE(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN, SESSION_BACKGROUND) + +/* Which session classes want their own per-user service manager? */ +#define SESSION_CLASS_WANTS_SERVICE_MANAGER(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN, SESSION_BACKGROUND) + +/* Which session classes can pin our user tracking? */ +#define SESSION_CLASS_PIN_USER(class) (!IN_SET((class), SESSION_MANAGER, SESSION_MANAGER_EARLY)) typedef enum SessionType { SESSION_UNSPECIFIED, diff --git a/src/login/logind-user.c b/src/login/logind-user.c index 4868eb48fb9..6e41ac6ef60 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -336,7 +336,17 @@ int user_load(User *u) { return 0; } -static void user_start_service(User *u) { +static bool user_wants_service_manager(User *u) { + assert(u); + + LIST_FOREACH(sessions_by_user, s, u->sessions) + if (SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class)) + return true; + + return false; +} + +void user_start_service_manager(User *u) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -346,6 +356,12 @@ static void user_start_service(User *u) { * start the per-user slice or the systemd-runtime-dir@.service instance, as those are pulled in both by * user@.service and the session scopes as dependencies. */ + if (u->stopping) /* Don't try to start this if the user is going down */ + return; + + if (!user_wants_service_manager(u)) /* Only start user service manager if there's at least one session which wants it */ + return; + u->service_job = mfree(u->service_job); r = manager_start_unit(u->manager, u->service, &error, &u->service_job); @@ -448,7 +464,7 @@ int user_start(User *u) { u->stopping = false; if (!u->started) - log_debug("Starting services for new user %s.", u->user_record->user_name); + log_debug("Tracking new user %s.", u->user_record->user_name); /* Save the user data so far, because pam_systemd will read the XDG_RUNTIME_DIR out of it while starting up * systemd --user. We need to do user_save_internal() because we have not "officially" started yet. */ @@ -458,7 +474,7 @@ int user_start(User *u) { (void) user_update_slice(u); /* Start user@UID.service */ - user_start_service(u); + user_start_service_manager(u); if (!u->started) { if (!dual_timestamp_is_set(&u->timestamp)) @@ -651,6 +667,19 @@ static usec_t user_get_stop_delay(User *u) { return u->manager->user_stop_delay; } +static bool user_pinned_by_sessions(User *u) { + assert(u); + + /* Returns true if at least one session exists that shall keep the user tracking alive. That + * generally means one session that isn't the service manager still exists. */ + + LIST_FOREACH(sessions_by_user, i, u->sessions) + if (SESSION_CLASS_PIN_USER(i->class)) + return true; + + return false; +} + bool user_may_gc(User *u, bool drop_not_started) { int r; @@ -659,7 +688,7 @@ bool user_may_gc(User *u, bool drop_not_started) { if (drop_not_started && !u->started) return true; - if (u->sessions) + if (user_pinned_by_sessions(u)) return false; if (u->last_session_timestamp != USEC_INFINITY) { @@ -718,22 +747,26 @@ UserState user_get_state(User *u) { if (!u->started || u->service_job) return USER_OPENING; - if (u->sessions) { - bool all_closing = true; + bool any = false, all_closing = true; + LIST_FOREACH(sessions_by_user, i, u->sessions) { + SessionState state; - LIST_FOREACH(sessions_by_user, i, u->sessions) { - SessionState state; + /* Ignore sessions that don't pin the user, i.e. are not supposed to have an effect on user state */ + if (!SESSION_CLASS_PIN_USER(i->class)) + continue; - state = session_get_state(i); - if (state == SESSION_ACTIVE) - return USER_ACTIVE; - if (state != SESSION_CLOSING) - all_closing = false; - } + state = session_get_state(i); + if (state == SESSION_ACTIVE) + return USER_ACTIVE; + if (state != SESSION_CLOSING) + all_closing = false; - return all_closing ? USER_CLOSING : USER_ONLINE; + any = true; } + if (any) + return all_closing ? USER_CLOSING : USER_ONLINE; + if (user_check_linger_file(u) > 0 && user_unit_active(u)) return USER_LINGERING; @@ -828,7 +861,7 @@ void user_update_last_session_timer(User *u) { assert(u); - if (u->sessions) { + if (user_pinned_by_sessions(u)) { /* There are sessions, turn off the timer */ u->last_session_timestamp = USEC_INFINITY; u->timer_event_source = sd_event_source_unref(u->timer_event_source); diff --git a/src/login/logind-user.h b/src/login/logind-user.h index 21b9f8f348e..7108520f659 100644 --- a/src/login/logind-user.h +++ b/src/login/logind-user.h @@ -57,6 +57,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(User *, user_free); bool user_may_gc(User *u, bool drop_not_started); void user_add_to_gc_queue(User *u); +void user_start_service_manager(User *u); int user_start(User *u); int user_stop(User *u, bool force); int user_finalize(User *u); From 4cb4e6cf6dce2b66dcb59a8534aa6ca885e2f732 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 27 Nov 2023 17:31:50 +0100 Subject: [PATCH 02/10] pam_systemd: register systemd user service manager as class='manager' Now that we have thew new class, start making us of it in pam_systemd.so when running for user@.service. --- src/login/pam_systemd.c | 27 +++++++++---------------- test/units/testsuite-35.sh | 40 +++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 38 deletions(-) diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index 297689203b0..d7814a7275d 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -936,30 +936,21 @@ _public_ PAM_EXTERN int pam_sm_open_session( if (r != PAM_SUCCESS) return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@"); - /* Make sure we don't enter a loop by talking to systemd-logind when it is actually waiting for the - * background to finish start-up. If the service is "systemd-user" we simply set XDG_RUNTIME_DIR and - * leave. */ - - if (streq_ptr(service, "systemd-user")) { - char rt[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)]; - - xsprintf(rt, "/run/user/"UID_FMT, ur->uid); - r = configure_runtime_directory(handle, ur, rt); - if (r != PAM_SUCCESS) - return r; - - goto success; - } - - /* Otherwise, we ask logind to create a session for us */ - seat = getenv_harder(handle, "XDG_SEAT", NULL); cvtnr = getenv_harder(handle, "XDG_VTNR", NULL); type = getenv_harder(handle, "XDG_SESSION_TYPE", type_pam); class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam); desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam); - if (tty && strchr(tty, ':')) { + if (streq_ptr(service, "systemd-user")) { + /* If we detect that we are running in the "systemd-user" PAM stack, then let's patch the class to + * 'manager' if not set, simply for robustness reasons. */ + type = "unspecified"; + class = IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) ? + "manager-early" : "manager"; + tty = NULL; + + } else if (tty && strchr(tty, ':')) { /* A tty with a colon is usually an X11 display, placed there to show up in utmp. We rearrange things * and don't pretend that an X display was a tty. */ if (isempty(display)) diff --git a/test/units/testsuite-35.sh b/test/units/testsuite-35.sh index 8ea801ee96e..78bc07d8963 100755 --- a/test/units/testsuite-35.sh +++ b/test/units/testsuite-35.sh @@ -258,7 +258,7 @@ cleanup_session() ( systemctl stop getty@tty2.service - for s in $(loginctl --no-legend list-sessions | awk '$3 == "logind-test-user" { print $1 }'); do + for s in $(loginctl --no-legend list-sessions | grep tty | awk '$3 == "logind-test-user" { print $1 }'); do echo "INFO: stopping session $s" loginctl terminate-session "$s" done @@ -308,18 +308,18 @@ check_session() ( local seat session leader_pid - if [[ $(loginctl --no-legend | grep -c "logind-test-user") != 1 ]]; then + if [[ $(loginctl --no-legend | grep tty | grep -c "logind-test-user") != 1 ]]; then echo "no session or multiple sessions for logind-test-user." >&2 return 1 fi - seat=$(loginctl --no-legend | grep 'logind-test-user *seat' | awk '{ print $4 }') + seat=$(loginctl --no-legend | grep tty | grep 'logind-test-user *seat' | awk '{ print $4 }') if [[ -z "$seat" ]]; then echo "no seat found for user logind-test-user" >&2 return 1 fi - session=$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1 }') + session=$(loginctl --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }') if [[ -z "$session" ]]; then echo "no session found for user logind-test-user" >&2 return 1 @@ -364,7 +364,7 @@ EOF check_session && break done check_session - assert_eq "$(loginctl --no-legend | awk '$3=="logind-test-user" { print $5 }')" "tty2" + assert_eq "$(loginctl --no-legend | grep tty | awk '$3=="logind-test-user" { print $5 }')" "tty2" } testcase_sanity_check() { @@ -455,7 +455,7 @@ EOF udevadm info "$dev" # trigger logind and activate session - loginctl activate "$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1 }')" + loginctl activate "$(loginctl --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }')" # check ACL sleep 1 @@ -496,7 +496,7 @@ testcase_lock_idle_action() { return fi - if loginctl --no-legend | grep -q logind-test-user; then + if loginctl --no-legend | grep tty | grep -q logind-test-user; then echo >&2 "Session of the 'logind-test-user' is already present." exit 1 fi @@ -545,7 +545,7 @@ testcase_session_properties() { trap cleanup_session RETURN create_session - s=$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $1 }') + s=$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }') /usr/lib/systemd/tests/unit-tests/manual/test-session-properties "/org/freedesktop/login1/session/_3${s?}" /dev/tty2 } @@ -561,17 +561,17 @@ testcase_list_users_sessions_seats() { create_session # Activate the session - loginctl activate "$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1 }')" + loginctl activate "$(loginctl --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }')" - session=$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $1 }') + session=$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }') : check that we got a valid session id busctl get-property org.freedesktop.login1 "/org/freedesktop/login1/session/_3${session?}" org.freedesktop.login1.Session Id - assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $2 }')" "$(id -ru logind-test-user)" - seat=$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $4 }') - assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $5 }')" tty2 - assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $6 }')" active - assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $7 }')" no - assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $8 }')" '-' + assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $2 }')" "$(id -ru logind-test-user)" + seat=$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $4 }') + assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $5 }')" tty2 + assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $6 }')" active + assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $7 }')" no + assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $8 }')" '-' loginctl list-seats --no-legend | grep -Fwq "${seat?}" @@ -582,10 +582,10 @@ testcase_list_users_sessions_seats() { loginctl enable-linger logind-test-user assert_eq "$(loginctl list-users --no-legend | awk '$2 == "logind-test-user" { print $3 }')" yes - for s in $(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $1 }'); do + for s in $(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }'); do loginctl terminate-session "$s" done - if ! timeout 30 bash -c "while loginctl --no-legend | grep -q logind-test-user; do sleep 1; done"; then + if ! timeout 30 bash -c "while loginctl --no-legend | grep tty | grep -q logind-test-user; do sleep 1; done"; then echo "WARNING: session for logind-test-user still active, ignoring." return fi @@ -613,7 +613,7 @@ testcase_stop_idle_session() { create_session trap teardown_stop_idle_session RETURN - id="$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1; }')" + id="$(loginctl --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1; }')" ts="$(date '+%H:%M:%S')" mkdir -p /run/systemd/logind.conf.d @@ -625,7 +625,7 @@ EOF sleep 5 assert_eq "$(journalctl -b -u systemd-logind.service --since="$ts" --grep "Session \"$id\" of user \"logind-test-user\" is idle, stopping." | wc -l)" 1 - assert_eq "$(loginctl --no-legend | grep -c "logind-test-user")" 0 + assert_eq "$(loginctl --no-legend | grep tty | grep -c "logind-test-user")" 0 } testcase_ambient_caps() { From b5100c736f1fce2b6b22c07cf2725e4ec3764a75 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 27 Nov 2023 18:39:02 +0100 Subject: [PATCH 03/10] logind: add "background-light" session class This is the same as the "background" class, but does *not* pull in a service manager. It might be useful for things like select cron jobs that do not intend to call per-user IPC calls. Replaces: #23569 Fixes: #23978 --- src/login/logind-session.c | 15 ++++++++------- src/login/logind-session.h | 3 ++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/login/logind-session.c b/src/login/logind-session.c index d8fe018e1da..e9d95e1db8f 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -1639,13 +1639,14 @@ static const char* const session_type_table[_SESSION_TYPE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(session_type, SessionType); static const char* const session_class_table[_SESSION_CLASS_MAX] = { - [SESSION_USER] = "user", - [SESSION_USER_EARLY] = "user-early", - [SESSION_GREETER] = "greeter", - [SESSION_LOCK_SCREEN] = "lock-screen", - [SESSION_BACKGROUND] = "background", - [SESSION_MANAGER] = "manager", - [SESSION_MANAGER_EARLY] = "manager-early", + [SESSION_USER] = "user", + [SESSION_USER_EARLY] = "user-early", + [SESSION_GREETER] = "greeter", + [SESSION_LOCK_SCREEN] = "lock-screen", + [SESSION_BACKGROUND] = "background", + [SESSION_BACKGROUND_LIGHT] = "background-light", + [SESSION_MANAGER] = "manager", + [SESSION_MANAGER_EARLY] = "manager-early", }; DEFINE_STRING_TABLE_LOOKUP(session_class, SessionClass); diff --git a/src/login/logind-session.h b/src/login/logind-session.h index 2b61f966b5f..3cc416bf682 100644 --- a/src/login/logind-session.h +++ b/src/login/logind-session.h @@ -25,6 +25,7 @@ typedef enum SessionClass { SESSION_GREETER, /* A login greeter pseudo-session */ SESSION_LOCK_SCREEN, /* A lock screen */ SESSION_BACKGROUND, /* Things like cron jobs, which are non-interactive */ + SESSION_BACKGROUND_LIGHT, /* Like SESSION_BACKGROUND, but without the service manager */ SESSION_MANAGER, /* The service manager */ SESSION_MANAGER_EARLY, /* The service manager for root (which is allowed to run before systemd-user-sessions.service) */ _SESSION_CLASS_MAX, @@ -36,7 +37,7 @@ typedef enum SessionClass { #define SESSION_CLASS_IS_EARLY(class) IN_SET((class), SESSION_USER_EARLY, SESSION_MANAGER_EARLY) /* Which session classes want their own scope units? (all of them, except the manager, which comes in its own service unit already */ -#define SESSION_CLASS_WANTS_SCOPE(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN, SESSION_BACKGROUND) +#define SESSION_CLASS_WANTS_SCOPE(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN, SESSION_BACKGROUND, SESSION_BACKGROUND_LIGHT) /* Which session classes want their own per-user service manager? */ #define SESSION_CLASS_WANTS_SERVICE_MANAGER(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN, SESSION_BACKGROUND) From b4f01bc1df625842c2db423b460a755c5817c1f9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Nov 2023 11:18:01 +0100 Subject: [PATCH 04/10] logind: rework logic to decide whether lock + idle + display applies to a session Let's streamline the logic that decides whether the screen lock, idle timeout or display election mechanism applies to a session class. Let's add explicitly SESSION_CLASS_IS_XYZ() macros for each, and then resue them at all suitable places, and refuse any attempts to use the functionality on the wrong clases with a friendly error message. --- src/login/logind-core.c | 3 +++ src/login/logind-session-dbus.c | 18 +++++++++++++++--- src/login/logind-session.c | 15 +++++++++++---- src/login/logind-session.h | 11 ++++++++++- src/login/logind-user.c | 5 ++++- 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/login/logind-core.c b/src/login/logind-core.c index 26133ee6efc..582cbcbea84 100644 --- a/src/login/logind-core.c +++ b/src/login/logind-core.c @@ -413,6 +413,9 @@ int manager_get_idle_hint(Manager *m, dual_timestamp *t) { dual_timestamp k; int ih; + if (!SESSION_CLASS_CAN_IDLE(s->class)) + continue; + ih = session_get_idle_hint(s, &k); if (ih < 0) return ih; diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c index 6da3dd59d5a..d4e656ae85d 100644 --- a/src/login/logind-session-dbus.c +++ b/src/login/logind-session-dbus.c @@ -216,7 +216,9 @@ int bus_session_method_lock(sd_bus_message *message, void *userdata, sd_bus_erro if (r == 0) return 1; /* Will call us back */ - r = session_send_lock(s, strstr(sd_bus_message_get_member(message), "Lock")); + r = session_send_lock(s, /* lock= */ strstr(sd_bus_message_get_member(message), "Lock")); + if (r == -ENOTTY) + return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session does not support lock screen."); if (r < 0) return r; @@ -248,7 +250,7 @@ static int method_set_idle_hint(sd_bus_message *message, void *userdata, sd_bus_ r = session_set_idle_hint(s, b); if (r == -ENOTTY) - return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Idle hint control is not supported on non-graphical sessions."); + return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Idle hint control is not supported on non-graphical and non-user sessions."); if (r < 0) return r; @@ -278,7 +280,11 @@ static int method_set_locked_hint(sd_bus_message *message, void *userdata, sd_bu if (uid != 0 && uid != s->user->user_record->uid) return sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED, "Only owner of session may set locked hint"); - session_set_locked_hint(s, b); + r = session_set_locked_hint(s, b); + if (r == -ENOTTY) + return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session does not support lock screen."); + if (r < 0) + return r; return sd_bus_reply_method_return(message, NULL); } @@ -765,6 +771,9 @@ int session_send_lock(Session *s, bool lock) { assert(s); + if (!SESSION_CLASS_CAN_LOCK(s->class)) + return -ENOTTY; + p = session_bus_path(s); if (!p) return -ENOMEM; @@ -786,6 +795,9 @@ int session_send_lock_all(Manager *m, bool lock) { HASHMAP_FOREACH(session, m->sessions) { int k; + if (!SESSION_CLASS_CAN_LOCK(session->class)) + continue; + k = session_send_lock(session, lock); if (k < 0) r = k; diff --git a/src/login/logind-session.c b/src/login/logind-session.c index e9d95e1db8f..3226f3db783 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -1155,7 +1155,9 @@ found_atime: int session_set_idle_hint(Session *s, bool b) { assert(s); - if (!SESSION_TYPE_IS_GRAPHICAL(s->type)) + if (!SESSION_CLASS_CAN_IDLE(s->class)) /* Only some session classes know the idle concept at all */ + return -ENOTTY; + if (!SESSION_TYPE_IS_GRAPHICAL(s->type)) /* And only graphical session types can set the field explicitly */ return -ENOTTY; if (s->idle_hint == b) @@ -1181,15 +1183,20 @@ int session_get_locked_hint(Session *s) { return s->locked_hint; } -void session_set_locked_hint(Session *s, bool b) { +int session_set_locked_hint(Session *s, bool b) { assert(s); + if (!SESSION_CLASS_CAN_LOCK(s->class)) + return -ENOTTY; + if (s->locked_hint == b) - return; + return 0; s->locked_hint = b; + (void) session_save(s); + (void) session_send_changed(s, "LockedHint", NULL); - session_send_changed(s, "LockedHint", NULL); + return 1; } void session_set_type(Session *s, SessionType t) { diff --git a/src/login/logind-session.h b/src/login/logind-session.h index 3cc416bf682..9cba60c18f6 100644 --- a/src/login/logind-session.h +++ b/src/login/logind-session.h @@ -45,6 +45,15 @@ typedef enum SessionClass { /* Which session classes can pin our user tracking? */ #define SESSION_CLASS_PIN_USER(class) (!IN_SET((class), SESSION_MANAGER, SESSION_MANAGER_EARLY)) +/* Which session classes decide whether system is idle? (should only cover sessions that have input, and are not idle screens themselves)*/ +#define SESSION_CLASS_CAN_IDLE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER)) + +/* Which session classes have a lock screen concept? */ +#define SESSION_CLASS_CAN_LOCK(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY)) + +/* Which sessions are candidates to become "display" sessions */ +#define SESSION_CLASS_CAN_DISPLAY(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER)) + typedef enum SessionType { SESSION_UNSPECIFIED, SESSION_TTY, @@ -158,7 +167,7 @@ bool session_is_active(Session *s); int session_get_idle_hint(Session *s, dual_timestamp *t); int session_set_idle_hint(Session *s, bool b); int session_get_locked_hint(Session *s); -void session_set_locked_hint(Session *s, bool b); +int session_set_locked_hint(Session *s, bool b); void session_set_type(Session *s, SessionType t); int session_set_display(Session *s, const char *display); int session_set_tty(Session *s, const char *tty); diff --git a/src/login/logind-user.c b/src/login/logind-user.c index 6e41ac6ef60..6de7c47c7a7 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -591,6 +591,9 @@ int user_get_idle_hint(User *u, dual_timestamp *t) { dual_timestamp k; int ih; + if (!SESSION_CLASS_CAN_IDLE(s->class)) + continue; + ih = session_get_idle_hint(s, &k); if (ih < 0) return ih; @@ -783,7 +786,7 @@ static bool elect_display_filter(Session *s) { /* Return true if the session is a candidate for the user’s ‘primary session’ or ‘display’. */ assert(s); - return IN_SET(s->class, SESSION_USER, SESSION_GREETER) && s->started && !s->stopping; + return SESSION_CLASS_CAN_DISPLAY(s->class) && s->started && !s->stopping; } static int elect_display_compare(Session *s1, Session *s2) { From d2a4c37972494aaf3deea5bc971bc45ab8f73f25 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Nov 2023 11:24:10 +0100 Subject: [PATCH 05/10] logind: rework GC logic In logind we generally want to stop user@.service for a user once they log out. So the usual rule is that whenever a User object is around that has no pinning sessions we should close it. Except that it isn't that easy. We allow that user@.service is also manually started, in which case the User object is created but not pinned by any session. Let's rework how this is handled: we define two different GC modes. In one GC mode we'll keep the User object around whenever *any* session exists (thus: including the user@.service session), and one where we only keep it around whenever a *pinning* session exists (i.e. when a user actually logs in, but the user@.service session doesn't count like that). And the trick is now that we start out in the *any* GC mode, and switch to the *pinning* GC mode once the first user session logs in. This should make things more robust as we know exactly in which state we are and when to GC a user. --- src/login/logind-dbus.c | 10 +++++++++- src/login/logind-user.c | 41 +++++++++++++++++++++++++++++++++-------- src/login/logind-user.h | 11 +++++++++++ 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 31efb0d113d..99b9da6ba10 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -937,9 +937,17 @@ static int create_session( goto fail; session->original_type = session->type = t; - session->class = c; session->remote = remote; session->vtnr = vtnr; + session->class = c; + + /* Once the first session that is of a pinning class shows up we'll change the GC mode for the user + * from USER_GC_BY_ANY to USER_GC_BY_PIN, so that the user goes away once the last pinning session + * goes away. Background: we want that user@.service – when started manually – remains around (which + * itself is a non-pinning session), but gets stopped when the last pinning session goes away. */ + + if (SESSION_CLASS_PIN_USER(c)) + user->gc_mode = USER_GC_BY_PIN; if (!isempty(tty)) { session->tty = strdup(tty); diff --git a/src/login/logind-user.c b/src/login/logind-user.c index 6de7c47c7a7..e6e57ad79ee 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -63,6 +63,7 @@ int user_new(User **ret, .manager = m, .user_record = user_record_ref(ur), .last_session_timestamp = USEC_INFINITY, + .gc_mode = USER_GC_BY_ANY, }; if (asprintf(&u->state_file, "/run/systemd/users/" UID_FMT, ur->uid) < 0) @@ -162,10 +163,12 @@ static int user_save_internal(User *u) { "# This is private data. Do not parse.\n" "NAME=%s\n" "STATE=%s\n" /* friendly user-facing state */ - "STOPPING=%s\n", /* low-level state */ + "STOPPING=%s\n" /* low-level state */ + "GC_MODE=%s\n", u->user_record->user_name, user_state_to_string(user_get_state(u)), - yes_no(u->stopping)); + yes_no(u->stopping), + user_gc_mode_to_string(u->gc_mode)); /* LEGACY: no-one reads RUNTIME= anymore, drop it at some point */ if (u->runtime_path) @@ -302,7 +305,7 @@ int user_save(User *u) { } int user_load(User *u) { - _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL; + _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL, *gc_mode = NULL; int r; assert(u); @@ -312,7 +315,8 @@ int user_load(User *u) { "STOPPING", &stopping, "REALTIME", &realtime, "MONOTONIC", &monotonic, - "LAST_SESSION_TIMESTAMP", &last_session_timestamp); + "LAST_SESSION_TIMESTAMP", &last_session_timestamp, + "GC_MODE", &gc_mode); if (r == -ENOENT) return 0; if (r < 0) @@ -333,6 +337,10 @@ int user_load(User *u) { if (last_session_timestamp) (void) deserialize_usec(last_session_timestamp, &u->last_session_timestamp); + u->gc_mode = user_gc_mode_from_string(gc_mode); + if (u->gc_mode < 0) + u->gc_mode = USER_GC_BY_PIN; + return 0; } @@ -676,11 +684,21 @@ static bool user_pinned_by_sessions(User *u) { /* Returns true if at least one session exists that shall keep the user tracking alive. That * generally means one session that isn't the service manager still exists. */ - LIST_FOREACH(sessions_by_user, i, u->sessions) - if (SESSION_CLASS_PIN_USER(i->class)) - return true; + switch (u->gc_mode) { - return false; + case USER_GC_BY_ANY: + return u->sessions; + + case USER_GC_BY_PIN: + LIST_FOREACH(sessions_by_user, i, u->sessions) + if (SESSION_CLASS_PIN_USER(i->class)) + return true; + + return false; + + default: + assert_not_reached(); + } } bool user_may_gc(User *u, bool drop_not_started) { @@ -912,6 +930,13 @@ static const char* const user_state_table[_USER_STATE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(user_state, UserState); +static const char* const user_gc_mode_table[_USER_GC_MODE_MAX] = { + [USER_GC_BY_PIN] = "pin", + [USER_GC_BY_ANY] = "any", +}; + +DEFINE_STRING_TABLE_LOOKUP(user_gc_mode, UserGCMode); + int config_parse_tmpfs_size( const char* unit, const char *filename, diff --git a/src/login/logind-user.h b/src/login/logind-user.h index 7108520f659..9bda5dde421 100644 --- a/src/login/logind-user.h +++ b/src/login/logind-user.h @@ -19,6 +19,13 @@ typedef enum UserState { _USER_STATE_INVALID = -EINVAL, } UserState; +typedef enum UserGCMode { + USER_GC_BY_ANY, /* any session pins this user */ + USER_GC_BY_PIN, /* only sessions with an explicitly pinning class pin this user */ + _USER_GC_MODE_MAX, + _USER_GC_MODE_INVALID = -EINVAL, +} UserGCMode; + struct User { Manager *manager; @@ -41,6 +48,7 @@ struct User { /* Set up when the last session of the user logs out */ sd_event_source *timer_event_source; + UserGCMode gc_mode; bool in_gc_queue:1; bool started:1; /* Whenever the user being started, has been started or is being stopped again. */ @@ -73,4 +81,7 @@ void user_update_last_session_timer(User *u); const char* user_state_to_string(UserState s) _const_; UserState user_state_from_string(const char *s) _pure_; +const char* user_gc_mode_to_string(UserGCMode m) _const_; +UserGCMode user_gc_mode_from_string(const char *s) _pure_; + CONFIG_PARSER_PROTOTYPE(config_parse_compat_user_tasks_max); From ad23439eae718ac3634f260be0d29e01445983a8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 4 Jan 2024 13:40:00 +0100 Subject: [PATCH 06/10] logind: tighten for which classes of sessions we do stop-on-idle We only want to do this for fully set up, interactive sessions, i.e. user and user-early, but not for any others, hence restrict the rules a bit. Follow-up for: 508b4786e8592e82eb4832549f74aaa54335d14c --- src/login/logind-session.c | 2 +- src/login/logind-session.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/login/logind-session.c b/src/login/logind-session.c index 3226f3db783..69dc52ad66c 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -817,7 +817,7 @@ static int session_setup_stop_on_idle_timer(Session *s) { assert(s); - if (s->manager->stop_idle_session_usec == USEC_INFINITY || IN_SET(s->class, SESSION_GREETER, SESSION_LOCK_SCREEN)) + if (s->manager->stop_idle_session_usec == USEC_INFINITY || !SESSION_CLASS_CAN_STOP_ON_IDLE(s->class)) return 0; r = sd_event_add_time_relative( diff --git a/src/login/logind-session.h b/src/login/logind-session.h index 9cba60c18f6..a7d90e997e5 100644 --- a/src/login/logind-session.h +++ b/src/login/logind-session.h @@ -54,6 +54,9 @@ typedef enum SessionClass { /* Which sessions are candidates to become "display" sessions */ #define SESSION_CLASS_CAN_DISPLAY(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER)) +/* Which sessions classes should be subject to stop-in-idle */ +#define SESSION_CLASS_CAN_STOP_ON_IDLE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY)) + typedef enum SessionType { SESSION_UNSPECIFIED, SESSION_TTY, From 3b52ef6f004e7f028b7adc6a550327939d93b721 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Nov 2023 12:57:16 +0100 Subject: [PATCH 07/10] test: add integration test for new 'background-light' session class --- test/units/testsuite-35.sh | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/test/units/testsuite-35.sh b/test/units/testsuite-35.sh index 78bc07d8963..5c230c0a45b 100755 --- a/test/units/testsuite-35.sh +++ b/test/units/testsuite-35.sh @@ -680,6 +680,51 @@ EOF rm -f "$SCRIPT" "$PAMSERVICE" } +background_at_return() { + rm -f /etc/pam.d/"$PAMSERVICE" + unset PAMSERVICE +} + +testcase_background() { + + local uid TRANSIENTUNIT1 TRANSIENTUNIT2 + + uid=$(id -u logind-test-user) + + systemctl stop user@"$uid".service + + PAMSERVICE="pamserv$RANDOM" + TRANSIENTUNIT1="bg$RANDOM.service" + TRANSIENTUNIT2="bgg$RANDOM.service" + + trap background_at_return RETURN + + cat > /etc/pam.d/"$PAMSERVICE" < Date: Wed, 29 Nov 2023 12:19:53 +0100 Subject: [PATCH 08/10] man: document the expanded catalogue of session classes --- man/pam_systemd.xml | 57 ++++++++++++++++++++++++++++++++---- man/sd_session_is_active.xml | 15 +++++----- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/man/pam_systemd.xml b/man/pam_systemd.xml index c9a3ccbb5f2..2c3bbec5d81 100644 --- a/man/pam_systemd.xml +++ b/man/pam_systemd.xml @@ -90,11 +90,58 @@ class= - Takes a string argument which sets the session class. The XDG_SESSION_CLASS - environment variable (see below) takes precedence. One of user, greeter, - lock-screen or background. See - sd_session_get_class3 for - details about the session class. + Takes a string argument which sets the session class. The + XDG_SESSION_CLASS environment variable (see below) takes precedence. See + sd_session_get_class3 + for a way to query the class of a session. The following session classes are defined: + + + Session Classes + + + + + + Name + Explanation + + + + + user + A regular interactive user session. This is the default class for sessions for which a TTY or X display is known at session registration time. + + + user-early + Similar to user but sessions of this class are not ordered after systemd-user-sessions.service, i.e. may be started before regular sessions are allowed to be established. This session class is the default for sessions of the root user that would otherwise qualify for the user class, see above. (Added in v256.) + + + greeter + Similar to user but for sessions that are spawned by a display manager ephemerally and which prompt the user for login credentials. + + + lock-screen + Similar to user but for sessions that are spawned by a display manager ephemerally and which show a lock screen that can be used to unlock locked user accounts or sessions. + + + background + Used for background sessions, such as those invoked by cron and similar tools. This is the default class for sessions for which no TTY or X display is known at session registration time. + + + background-light + Similar to background, but sessions of this class will not pull in the user@.service of the user, and thus possibly have no services of the user running. (Added in v256.) + + + manager + The user@.service service of the user is registered under this session class. (Added in v256.) + + + manager-early + Similar to manager, but for the root user. Compare with the user vs. user-early situation. (Added in v256.) + + + +
diff --git a/man/sd_session_is_active.xml b/man/sd_session_is_active.xml index e69ef59fd0e..747fab4c68f 100644 --- a/man/sd_session_is_active.xml +++ b/man/sd_session_is_active.xml @@ -214,14 +214,13 @@ free3 call after use. - sd_session_get_class() may be used to - determine the class of the session identified by the specified - session identifier. The returned string is one of - user, greeter, - lock-screen, or background - and needs to be freed with the libc - free3 - call after use. + sd_session_get_class() may be used to determine the class of the session + identified by the specified session identifier. The returned string is one of user, + user-early, greeter, lock-screen, + background, background-light, manager or + manager-early and needs to be freed with the libc free3 call after + use. sd_session_get_desktop() may be used to determine the brand of the desktop running on the session From 87dc8bbd86305b5d8e17275f91d4d3f056249aa2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jan 2024 17:14:48 +0100 Subject: [PATCH 09/10] logind: allow taking control of devices only in some session types Let's restrict the logic a bit, so that "manage" session types are not misused. --- src/login/logind-session-dbus.c | 3 +++ src/login/logind-session.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c index d4e656ae85d..03a043fe7d8 100644 --- a/src/login/logind-session-dbus.c +++ b/src/login/logind-session-dbus.c @@ -476,6 +476,9 @@ static int method_take_device(sd_bus_message *message, void *userdata, sd_bus_er if (!DEVICE_MAJOR_VALID(major) || !DEVICE_MINOR_VALID(minor)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Device major/minor is not valid."); + if (!SESSION_CLASS_CAN_TAKE_DEVICE(s->class)) + return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session class doesn't support taking device control."); + if (!session_is_controller(s, sd_bus_message_get_sender(message))) return sd_bus_error_set(error, BUS_ERROR_NOT_IN_CONTROL, "You are not in control of this session"); diff --git a/src/login/logind-session.h b/src/login/logind-session.h index a7d90e997e5..5f3961cf61a 100644 --- a/src/login/logind-session.h +++ b/src/login/logind-session.h @@ -57,6 +57,9 @@ typedef enum SessionClass { /* Which sessions classes should be subject to stop-in-idle */ #define SESSION_CLASS_CAN_STOP_ON_IDLE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY)) +/* Which session classes can take control of devices */ +#define SESSION_CLASS_CAN_TAKE_DEVICE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN)) + typedef enum SessionType { SESSION_UNSPECIFIED, SESSION_TTY, From 68fbd9a09c2c85e2d6223fb92b2fd4b545601706 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jan 2024 17:20:38 +0100 Subject: [PATCH 10/10] logind: also restrict on which session classes one cange the session type --- src/login/logind-session-dbus.c | 3 +++ src/login/logind-session.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c index 03a043fe7d8..7217b814764 100644 --- a/src/login/logind-session-dbus.c +++ b/src/login/logind-session-dbus.c @@ -393,6 +393,9 @@ static int method_set_type(sd_bus_message *message, void *userdata, sd_bus_error return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid session type '%s'", t); + if (!SESSION_CLASS_CAN_CHANGE_TYPE(s->class)) + return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session class doesn't support changing type."); + if (!session_is_controller(s, sd_bus_message_get_sender(message))) return sd_bus_error_set(error, BUS_ERROR_NOT_IN_CONTROL, "You must be in control of this session to set type"); diff --git a/src/login/logind-session.h b/src/login/logind-session.h index 5f3961cf61a..6a0fb543035 100644 --- a/src/login/logind-session.h +++ b/src/login/logind-session.h @@ -60,6 +60,9 @@ typedef enum SessionClass { /* Which session classes can take control of devices */ #define SESSION_CLASS_CAN_TAKE_DEVICE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN)) +/* Which session classes allow changing session types */ +#define SESSION_CLASS_CAN_CHANGE_TYPE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN)) + typedef enum SessionType { SESSION_UNSPECIFIED, SESSION_TTY,