diff --git a/src/login/logind-session.c b/src/login/logind-session.c index 1b8731e2c6..e62a5768da 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -28,6 +28,7 @@ #include "format-util.h" #include "fs-util.h" #include "hashmap.h" +#include "json-util.h" #include "login-util.h" #include "logind.h" #include "logind-dbus.h" @@ -1684,6 +1685,65 @@ bool session_is_auto(const char *name) { return streq_ptr(name, "auto"); } +static int session_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Session *s = ASSERT_PTR(userdata); + + assert(ret); + assert(s->user); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_UNSIGNED("UID", s->user->user_record->uid), + JSON_BUILD_PAIR_PIDREF_NON_NULL("PID", &s->leader), + JSON_BUILD_PAIR_ENUM("Type", session_type_to_string(s->type)), + JSON_BUILD_PAIR_ENUM("Class", session_class_to_string(s->class)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Service", s->service), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Desktop", s->desktop), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Seat", s->seat ? s->seat->id : NULL), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("VTNr", s->vtnr), + JSON_BUILD_PAIR_STRING_NON_EMPTY("TTY", s->tty), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Display", s->display), + SD_JSON_BUILD_PAIR_BOOLEAN("Remote", s->remote), + JSON_BUILD_PAIR_STRING_NON_EMPTY("RemoteHost", s->remote_host), + JSON_BUILD_PAIR_STRING_NON_EMPTY("RemoteUser", s->remote_user), + JSON_BUILD_PAIR_STRV_NON_EMPTY("ExtraDeviceAccess", s->extra_device_access)); +} + +static int session_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Session *s = ASSERT_PTR(userdata); + + assert(ret); + + dual_timestamp idle_ts; + bool idle = session_get_idle_hint(s, &idle_ts); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("Id", s->id), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Scope", s->scope), + SD_JSON_BUILD_PAIR_CONDITION(audit_session_is_valid(s->audit_id), + "Audit", SD_JSON_BUILD_UNSIGNED(s->audit_id)), + JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("Timestamp", &s->timestamp), + JSON_BUILD_PAIR_ENUM("State", session_state_to_string(session_get_state(s))), + SD_JSON_BUILD_PAIR_BOOLEAN("Active", session_is_active(s)), + SD_JSON_BUILD_PAIR_BOOLEAN("IdleHint", idle), + SD_JSON_BUILD_PAIR_CONDITION(idle, "IdleSinceHint", JSON_BUILD_DUAL_TIMESTAMP(&idle_ts)), + SD_JSON_BUILD_PAIR_BOOLEAN("LockedHint", s->locked_hint), + SD_JSON_BUILD_PAIR_BOOLEAN("CanIdle", SESSION_CLASS_CAN_IDLE(s->class)), + SD_JSON_BUILD_PAIR_BOOLEAN("CanLock", SESSION_CLASS_CAN_LOCK(s->class))); +} + +int session_build_json(Session *s, sd_json_variant **ret) { + assert(s); + assert(s->user); + assert(ret); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_CALLBACK("context", session_context_build_json, s), + SD_JSON_BUILD_PAIR_CALLBACK("runtime", session_runtime_build_json, s)); +} + static const char* const session_state_table[_SESSION_STATE_MAX] = { [SESSION_OPENING] = "opening", [SESSION_ONLINE] = "online", diff --git a/src/login/logind-session.h b/src/login/logind-session.h index da8c52510a..b0749134af 100644 --- a/src/login/logind-session.h +++ b/src/login/logind-session.h @@ -219,3 +219,5 @@ int session_send_create_reply(Session *s, const sd_bus_error *error); bool session_is_self(const char *name); bool session_is_auto(const char *name); + +int session_build_json(Session *s, sd_json_variant **ret); diff --git a/src/login/logind-varlink.c b/src/login/logind-varlink.c index 71a445e900..9b58791991 100644 --- a/src/login/logind-varlink.c +++ b/src/login/logind-varlink.c @@ -41,7 +41,8 @@ static int manager_varlink_get_session_by_peer( assert(ret); /* Determines the session of the peer. If the peer is not part of a session, but consult_display is - * true, then will return the display session of the peer's owning user */ + * true, then will return the display session of the peer's owning user. Returns 0 with *ret set to + * NULL if no session could be determined; the caller decides which error to report to the client. */ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; r = varlink_get_peer_pidref(link, &pidref); @@ -70,35 +71,114 @@ static int manager_varlink_get_session_by_peer( } else session = hashmap_get(m->sessions, name); + *ret = session; + return 0; +} + +static int lookup_session_by_name(Manager *m, sd_varlink *link, const char *name, Session **ret) { + int r; + + assert(m); + assert(link); + assert(name); + assert(ret); + + if (session_is_self(name) || session_is_auto(name)) { + Session *peer; + r = manager_varlink_get_session_by_peer(m, link, /* consult_display= */ session_is_auto(name), &peer); + if (r < 0) + return r; + if (!peer) + return -ESRCH; + + *ret = peer; + return 0; + } + + if (!session_id_valid(name)) + return -EINVAL; + + Session *session = hashmap_get(m->sessions, name); if (!session) - return sd_varlink_error(link, "io.systemd.Login.NoSuchSession", /* parameters= */ NULL); + return -ESRCH; *ret = session; return 0; } -static int manager_varlink_get_session_by_name( +static int lookup_session_by_pidref(Manager *m, const PidRef *pidref, Session **ret) { + int r; + + assert(m); + assert(pidref); + assert(ret); + + Session *session; + r = manager_get_session_by_pidref(m, pidref, &session); + if (r < 0) + return log_debug_errno(r, "Failed to look up session by PID " PID_FMT ": %m", pidref->pid); + if (!session) + return -ESRCH; + + *ret = session; + return 0; +} + +static int manager_varlink_get_session_by_name_or_pidref( Manager *m, sd_varlink *link, const char *name, + const PidRef *pidref, Session **ret) { + Session *by_name = NULL, *by_pid = NULL; + int r; + assert(m); assert(link); assert(ret); - /* Resolves a session name to a session object. Supports resolving the special names "self" and "auto". */ + /* Resolves a session by name and/or PID. Supports the special names "self" and "auto" for the name + * argument. If both name and pidref are unset, resolves to the caller's session. If both name and + * pidref are set they must refer to the same session, otherwise -ESRCH is returned. Returns -ESRCH + * on "not found". Caller is expected to turn that into a varlink error, typically via + * sd_varlink_set_sentinel(). Returns negative errno on other failures. */ - if (session_is_self(name)) - return manager_varlink_get_session_by_peer(m, link, /* consult_display= */ false, ret); - if (session_is_auto(name)) - return manager_varlink_get_session_by_peer(m, link, /* consult_display= */ true, ret); + if (name) { + r = lookup_session_by_name(m, link, name, &by_name); + if (r == -EINVAL) + return sd_varlink_error_invalid_parameter_name(link, "Id"); + if (r < 0) + return r; + } - Session *session = hashmap_get(m->sessions, name); - if (!session) - return sd_varlink_error(link, "io.systemd.Login.NoSuchSession", /* parameters= */ NULL); + if (pidref && pidref_is_set(pidref)) { + r = lookup_session_by_pidref(m, pidref, &by_pid); + if (r == -EINVAL) + return sd_varlink_error_invalid_parameter_name(link, "PID"); + if (r < 0) + return r; + } - *ret = session; + if (by_name && by_pid && by_name != by_pid) + return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), + "Search by session id '%s' and PID " PID_FMT " resulted in two different sessions", + name, pidref->pid); + + if (by_name || by_pid) { + *ret = by_name ?: by_pid; + return 0; + } + + /* Neither filter set — fall back to caller's session. */ + Session *by_peer; + r = manager_varlink_get_session_by_peer(m, link, /* consult_display= */ true, &by_peer); + if (r < 0) + return r; + if (!by_peer) + return -ESRCH; + + *ret = by_peer; return 0; } @@ -129,7 +209,7 @@ int session_send_create_reply_varlink(Session *s, const sd_bus_error *error) { SD_JSON_BUILD_PAIR_STRING("RuntimePath", s->user->runtime_path), SD_JSON_BUILD_PAIR_UNSIGNED("UID", s->user->user_record->uid), SD_JSON_BUILD_PAIR_CONDITION(!!s->seat, "Seat", SD_JSON_BUILD_STRING(s->seat ? s->seat->id : NULL)), - SD_JSON_BUILD_PAIR_CONDITION(s->vtnr > 0, "VTNr", SD_JSON_BUILD_UNSIGNED(s->vtnr)), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("VTNr", s->vtnr), JSON_BUILD_PAIR_ENUM("Class", session_class_to_string(s->class)), JSON_BUILD_PAIR_ENUM("Type", session_type_to_string(s->type))); } @@ -304,6 +384,81 @@ fail: return r; } +static int emit_session_reply(sd_varlink *link, Session *session) { + assert(link); + assert(session); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r = session_build_json(session, &v); + if (r < 0) + return r; + + return sd_varlink_reply(link, v); +} + +typedef struct ListSessionsParameters { + const char *id; + PidRef pidref; +} ListSessionsParameters; + +static void list_sessions_parameters_done(ListSessionsParameters *p) { + assert(p); + pidref_done(&p->pidref); +} + +static int vl_method_list_sessions(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + int r; + + static const sd_json_dispatch_field dispatch_table[] = { + { "Id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(ListSessionsParameters, id), 0 }, + { "PID", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_pidref, offsetof(ListSessionsParameters, pidref), 0 }, + {} + }; + + _cleanup_(list_sessions_parameters_done) ListSessionsParameters p = { + .pidref = PIDREF_NULL, + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + /* Unique-key path: Id and/or PID provided. Single reply or NoSuchSession. */ + if (p.id || pidref_is_set(&p.pidref)) { + r = sd_varlink_set_sentinel(link, "io.systemd.Login.NoSuchSession"); + if (r < 0) + return r; + + Session *session; + r = manager_varlink_get_session_by_name_or_pidref(m, link, p.id, &p.pidref, &session); + if (r == -ESRCH) + return 0; /* triggers NoSuchSession sentinel */ + if (r < 0) + return r; + + return emit_session_reply(link, session); + } + + /* Streaming path: no filter. Full list, requires 'more' flag. */ + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) + return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, /* parameters= */ NULL); + + /* Empty hashmap is a valid empty stream, not "not found" — see vl_method_list_inhibitors. */ + r = sd_varlink_set_sentinel(link, /* error_id= */ NULL); + if (r < 0) + return r; + + Session *session; + HASHMAP_FOREACH(session, m->sessions) { + r = emit_session_reply(link, session); + if (r < 0) + return r; + } + + return 0; +} + static int vl_method_release_session(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { Manager *m = ASSERT_PTR(userdata); int r; @@ -322,7 +477,9 @@ static int vl_method_release_session(sd_varlink *link, sd_json_variant *paramete return r; Session *session; - r = manager_varlink_get_session_by_name(m, link, p.id, &session); + r = manager_varlink_get_session_by_name_or_pidref(m, link, p.id, /* pidref= */ NULL, &session); + if (r == -ESRCH) + return sd_varlink_error(link, "io.systemd.Login.NoSuchSession", /* parameters= */ NULL); if (r < 0) return r; @@ -330,6 +487,8 @@ static int vl_method_release_session(sd_varlink *link, sd_json_variant *paramete r = manager_varlink_get_session_by_peer(m, link, /* consult_display= */ false, &peer_session); if (r < 0) return r; + if (!peer_session) + return sd_varlink_error(link, "io.systemd.Login.NoSuchSession", /* parameters= */ NULL); if (session != peer_session) return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, /* parameters= */ NULL); @@ -467,6 +626,7 @@ int manager_varlink_init(Manager *m, int fd) { s, "io.systemd.Login.CreateSession", vl_method_create_session, "io.systemd.Login.ReleaseSession", vl_method_release_session, + "io.systemd.Login.ListSessions", vl_method_list_sessions, "io.systemd.Shutdown.PowerOff", vl_method_power_off, "io.systemd.Shutdown.Reboot", vl_method_reboot, "io.systemd.Shutdown.Halt", vl_method_halt, diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index 1b0adfae9e..b6f7ddd38c 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -1147,7 +1147,7 @@ static int register_session( JSON_BUILD_PAIR_ENUM("Class", 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_UNSIGNED_NON_ZERO("VTNr", 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), diff --git a/src/shared/varlink-io.systemd.Login.c b/src/shared/varlink-io.systemd.Login.c index 9a07155ef2..42cd0aa95a 100644 --- a/src/shared/varlink-io.systemd.Login.c +++ b/src/shared/varlink-io.systemd.Login.c @@ -3,7 +3,7 @@ #include "varlink-idl-common.h" #include "varlink-io.systemd.Login.h" -static SD_VARLINK_DEFINE_ENUM_TYPE( +SD_VARLINK_DEFINE_ENUM_TYPE( SessionType, SD_VARLINK_DEFINE_ENUM_VALUE(unspecified), SD_VARLINK_DEFINE_ENUM_VALUE(tty), @@ -12,7 +12,7 @@ static SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_DEFINE_ENUM_VALUE(mir), SD_VARLINK_DEFINE_ENUM_VALUE(web)); -static SD_VARLINK_DEFINE_ENUM_TYPE( +SD_VARLINK_DEFINE_ENUM_TYPE( SessionClass, SD_VARLINK_FIELD_COMMENT("Regular user sessions"), SD_VARLINK_DEFINE_ENUM_VALUE(user), @@ -37,6 +37,62 @@ static SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_FIELD_COMMENT("The special session of the service manager for the root user"), SD_VARLINK_DEFINE_ENUM_VALUE(manager_early)); +static SD_VARLINK_DEFINE_STRUCT_TYPE( + SessionContext, + SD_VARLINK_FIELD_COMMENT("Numeric UNIX UID of the user owning this session"), + SD_VARLINK_DEFINE_FIELD(UID, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("PID of the session leader"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PID, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The type of the session"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Type, SessionType, 0), + SD_VARLINK_FIELD_COMMENT("The class of the session"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Class, SessionClass, 0), + SD_VARLINK_FIELD_COMMENT("PAM service name"), + SD_VARLINK_DEFINE_FIELD(Service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Desktop identifier"), + SD_VARLINK_DEFINE_FIELD(Desktop, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Seat this session is assigned to"), + SD_VARLINK_DEFINE_FIELD(Seat, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Virtual terminal number of the session, if applicable"), + SD_VARLINK_DEFINE_FIELD(VTNr, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("TTY device of the session"), + SD_VARLINK_DEFINE_FIELD(TTY, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("X11 display of the session"), + SD_VARLINK_DEFINE_FIELD(Display, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Whether this is a remote session"), + SD_VARLINK_DEFINE_FIELD(Remote, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Remote host, if this is a remote session"), + SD_VARLINK_DEFINE_FIELD(RemoteHost, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Remote user, if this is a remote session"), + SD_VARLINK_DEFINE_FIELD(RemoteUser, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Device paths this session has special access to"), + SD_VARLINK_DEFINE_FIELD(ExtraDeviceAccess, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + SessionRuntime, + SD_VARLINK_FIELD_COMMENT("The session identifier"), + SD_VARLINK_DEFINE_FIELD(Id, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("systemd scope unit name"), + SD_VARLINK_DEFINE_FIELD(Scope, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Audit session ID"), + SD_VARLINK_DEFINE_FIELD(Audit, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The session timestamps"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timestamp, Timestamp, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Current state of the session"), + SD_VARLINK_DEFINE_FIELD(State, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Whether the session is active (i.e. in the foreground)"), + SD_VARLINK_DEFINE_FIELD(Active, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Whether the session is idle"), + SD_VARLINK_DEFINE_FIELD(IdleHint, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Timestamp when the session went idle, only present when IdleHint is true"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(IdleSinceHint, Timestamp, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Whether the session is currently locked"), + SD_VARLINK_DEFINE_FIELD(LockedHint, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Whether this session class supports idle tracking"), + SD_VARLINK_DEFINE_FIELD(CanIdle, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Whether this session class supports locking"), + SD_VARLINK_DEFINE_FIELD(CanLock, SD_VARLINK_BOOL, 0)); + static SD_VARLINK_DEFINE_METHOD( CreateSession, SD_VARLINK_FIELD_COMMENT("Numeric UNIX UID of the user this session shall be owned by"), @@ -83,6 +139,18 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_FIELD_COMMENT("The assigned session class"), SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(Class, SessionClass, 0)); +static SD_VARLINK_DEFINE_METHOD_FULL( + ListSessions, + SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("If non-null, the identifier string of a session to look up. Supports the special names 'self' and 'auto' which resolve to the caller's session."), + SD_VARLINK_DEFINE_INPUT(Id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If non-null, return the session containing the process with this PID. If both Id and PID are specified they must reference the same session, otherwise NoSuchSession is returned."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(PID, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Configuration of the session"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(context, SessionContext, 0), + SD_VARLINK_FIELD_COMMENT("Runtime information of the session"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(runtime, SessionRuntime, 0)); + static SD_VARLINK_DEFINE_METHOD( ReleaseSession, SD_VARLINK_FIELD_COMMENT("The identifier string of the session to release. If unspecified or 'self', will return the callers session."), @@ -102,14 +170,22 @@ SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_INTERFACE_COMMENT("APIs for managing login sessions."), SD_VARLINK_SYMBOL_COMMENT("Process identifier"), &vl_type_ProcessId, + SD_VARLINK_SYMBOL_COMMENT("Dual timestamp"), + &vl_type_Timestamp, SD_VARLINK_SYMBOL_COMMENT("Various types of sessions"), &vl_type_SessionType, SD_VARLINK_SYMBOL_COMMENT("Various classes of sessions"), &vl_type_SessionClass, + SD_VARLINK_SYMBOL_COMMENT("Configuration aspects of a session (suitable for reuse as creation input)"), + &vl_type_SessionContext, + SD_VARLINK_SYMBOL_COMMENT("Runtime state and dynamic information of a session"), + &vl_type_SessionRuntime, SD_VARLINK_SYMBOL_COMMENT("Allocates a new session."), &vl_method_CreateSession, - SD_VARLINK_SYMBOL_COMMENT("Releases an existing session. Currently, will be refuses unless originating from the session to release itself."), + SD_VARLINK_SYMBOL_COMMENT("Releases an existing session. Currently, will be refused unless originating from the session to release itself."), &vl_method_ReleaseSession, + SD_VARLINK_SYMBOL_COMMENT("Lists current sessions. If an ID or PID filter is provided, returns the single matching session; otherwise streams all current sessions (requires the 'more' flag)."), + &vl_method_ListSessions, SD_VARLINK_SYMBOL_COMMENT("No session by this name found"), &vl_error_NoSuchSession, SD_VARLINK_SYMBOL_COMMENT("No seat by this name found"), diff --git a/src/shared/varlink-io.systemd.Login.h b/src/shared/varlink-io.systemd.Login.h index 5453cdfb25..52a2ddd974 100644 --- a/src/shared/varlink-io.systemd.Login.h +++ b/src/shared/varlink-io.systemd.Login.h @@ -3,4 +3,7 @@ #include "sd-varlink-idl.h" +extern const sd_varlink_symbol vl_type_SessionType; +extern const sd_varlink_symbol vl_type_SessionClass; + extern const sd_varlink_interface vl_interface_io_systemd_Login; diff --git a/src/test/meson.build b/src/test/meson.build index 0c8dba64d5..8a25cc03dc 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -494,6 +494,10 @@ executables += [ 'sources' : files('test-varlink-idl-machine.c'), 'objects' : ['systemd-machined'], }, + test_template + { + 'sources' : files('test-varlink-idl-login.c'), + 'objects' : ['systemd-logind'], + }, test_template + { 'sources' : files('test-watchdog.c'), 'type' : 'unsafe', diff --git a/src/test/test-varlink-idl-login.c b/src/test/test-varlink-idl-login.c new file mode 100644 index 0000000000..7dff80178f --- /dev/null +++ b/src/test/test-varlink-idl-login.c @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "logind-session.h" +#include "tests.h" +#include "test-varlink-idl-util.h" +#include "varlink-io.systemd.Login.h" + +TEST(login_enums_idl) { + TEST_IDL_ENUM(SessionType, session_type, vl_type_SessionType); + /* SessionClass has SESSION_NONE ("none") as an internal sentinel that is intentionally not exposed + * via Varlink. Only validate the IDL→C direction. */ + TEST_IDL_ENUM_FROM_STRING(SessionClass, session_class, vl_type_SessionClass); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/test/units/TEST-35-LOGIN.sh b/test/units/TEST-35-LOGIN.sh index 58b3bcca07..74e83c79d4 100755 --- a/test/units/TEST-35-LOGIN.sh +++ b/test/units/TEST-35-LOGIN.sh @@ -797,8 +797,113 @@ EOF systemctl stop "$RUN0UNIT3" } +teardown_varlink() ( + set +ex + + cleanup_session + + return 0 +) + testcase_varlink() { - varlinkctl introspect /run/systemd/io.systemd.Login + local session uid session_out + + if [[ ! -c /dev/tty2 ]]; then + echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}." + return + fi + + trap teardown_varlink RETURN + + local VARLINK_SOCKET="/run/systemd/io.systemd.Login" + + : "--- Introspect ---" + varlinkctl introspect "$VARLINK_SOCKET" + varlinkctl introspect "$VARLINK_SOCKET" | grep "method ListSessions" >/dev/null + + : "--- Setup test session ---" + create_session + session=$(loginctl --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $1 }') + uid=$(id -ru logind-test-user) + local leader_pid + leader_pid=$(loginctl show-session "$session" -p Leader --value) + loginctl activate "$session" + + : "--- ListSessions: Id filter (single reply) ---" + session_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions "{\"Id\":\"$session\"}") + echo "$session_out" | jq -e ".runtime.Id == \"$session\"" >/dev/null + echo "$session_out" | jq -e ".context.UID == $uid" >/dev/null + echo "$session_out" | jq -e ".context.PID.pid == $leader_pid" >/dev/null + echo "$session_out" | jq -e '.context.TTY == "tty2"' >/dev/null + echo "$session_out" | jq -e '.context.Remote == false' >/dev/null + echo "$session_out" | jq -e '.context.Type == "tty"' >/dev/null + echo "$session_out" | jq -e '.context.Class == "user"' >/dev/null + echo "$session_out" | jq -e '.runtime.CanIdle == true' >/dev/null + echo "$session_out" | jq -e '.runtime.CanLock == true' >/dev/null + echo "$session_out" | jq -e '.runtime.State == "active"' >/dev/null + echo "$session_out" | jq -e '.runtime.Active == true' >/dev/null + # ExtraDeviceAccess may be absent (empty) but must be an array if present. + echo "$session_out" | jq -e '(.context | has("ExtraDeviceAccess") | not) or (.context.ExtraDeviceAccess | type == "array")' >/dev/null + + # nonexistent session id + (! varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions '{"Id":"nonexistent"}') + + : "--- ListSessions: PID filter (single reply) ---" + # Look up the session containing the session leader's PID. + local pid_out + pid_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions "{\"PID\":{\"pid\":$leader_pid}}") + echo "$pid_out" | jq -e ".runtime.Id == \"$session\"" >/dev/null + + : "--- ListSessions: Id+PID consistency check ---" + # Same session referenced two ways: must succeed. + local ok_out mismatch_err + ok_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions \ + "{\"Id\":\"$session\",\"PID\":{\"pid\":$leader_pid}}") + echo "$ok_out" | jq -e ".runtime.Id == \"$session\"" >/dev/null + # Mismatched Id and PID (PID 1 is not in $session): must fail with NoSuchSession. + mismatch_err=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions \ + "{\"Id\":\"$session\",\"PID\":{\"pid\":1}}" 2>&1 || true) + echo "$mismatch_err" | grep NoSuchSession >/dev/null + + : "--- ListSessions: streaming path ---" + # varlinkctl --more emits RFC 7464 JSON-seq (each record prefixed with RS/0x1e), so jq --seq is required. + varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListSessions '{}' \ + | jq --seq -e --arg s "$session" 'select(.runtime.Id == $s)' >/dev/null + test "$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListSessions '{}' | wc -l)" -ge 2 + # without --more and no filter: must fail with ExpectedMore (not assert()) so logind stays running. + local list_err + list_err=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions '{}' 2>&1 || true) + # varlinkctl rewrites SD_VARLINK_ERROR_EXPECTED_MORE to the friendly description + # below (see src/varlinkctl/varlinkctl.c), so match on that substring rather than + # the raw error id. + echo "$list_err" | grep "'more' flag" >/dev/null + systemctl is-active systemd-logind.service >/dev/null + + : "--- ReleaseSession: NULL ID resolves to caller's session ---" + # A caller with a logind session calling ReleaseSession '{}' (no ID) must + # release its own session. We spawn the call inside a transient unit with + # PAM so pam_systemd creates a real session for the caller. + local PAMSERVICE_REL="pamserv-rel$RANDOM" + cat >/etc/pam.d/"$PAMSERVICE_REL" <&1 || true) + echo "$no_sess_rel" | grep NoSuchSession >/dev/null } testcase_restart() {