diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index 8247b0c25fc..7ad78290e6c 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -1402,6 +1402,26 @@ int cg_pid_get_session(pid_t pid, char **ret_session) { return cg_path_get_session(cgroup, ret_session); } +int cg_pidref_get_session(const PidRef *pidref, char **ret) { + int r; + + if (!pidref_is_set(pidref)) + return -ESRCH; + + _cleanup_free_ char *session = NULL; + r = cg_pid_get_session(pidref->pid, &session); + if (r < 0) + return r; + + r = pidref_verify(pidref); + if (r < 0) + return r; + + if (ret) + *ret = TAKE_PTR(session); + return 0; +} + int cg_path_get_owner_uid(const char *path, uid_t *ret_uid) { _cleanup_free_ char *slice = NULL; char *start, *end; @@ -1439,6 +1459,27 @@ int cg_pid_get_owner_uid(pid_t pid, uid_t *ret_uid) { return cg_path_get_owner_uid(cgroup, ret_uid); } +int cg_pidref_get_owner_uid(const PidRef *pidref, uid_t *ret) { + int r; + + if (!pidref_is_set(pidref)) + return -ESRCH; + + uid_t uid; + r = cg_pid_get_owner_uid(pidref->pid, &uid); + if (r < 0) + return r; + + r = pidref_verify(pidref); + if (r < 0) + return r; + + if (ret) + *ret = uid; + + return 0; +} + int cg_path_get_slice(const char *p, char **ret_slice) { const char *e = NULL; diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h index d471aee0c3a..bd8a21b5155 100644 --- a/src/basic/cgroup-util.h +++ b/src/basic/cgroup-util.h @@ -286,7 +286,9 @@ int cg_shift_path(const char *cgroup, const char *cached_root, const char **ret_ int cg_pid_get_path_shifted(pid_t pid, const char *cached_root, char **ret_cgroup); int cg_pid_get_session(pid_t pid, char **ret_session); +int cg_pidref_get_session(const PidRef *pidref, char **ret_session); int cg_pid_get_owner_uid(pid_t pid, uid_t *ret_uid); +int cg_pidref_get_owner_uid(const PidRef *pidref, uid_t *ret_uid); int cg_pid_get_unit(pid_t pid, char **ret_unit); int cg_pidref_get_unit(const PidRef *pidref, char **ret); int cg_pid_get_user_unit(pid_t pid, char **ret_unit); diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 94089323b45..45adf3c9506 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -975,6 +975,28 @@ int getpeerpidfd(int fd) { return pidfd; } +int getpeerpidref(int fd, PidRef *ret) { + int r; + + assert(fd >= 0); + assert(ret); + + int pidfd = getpeerpidfd(fd); + if (pidfd < 0) { + if (!ERRNO_IS_NEG_NOT_SUPPORTED(pidfd) && pidfd != -EINVAL) + return pidfd; + + struct ucred ucred; + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return pidref_set_pid(ret, ucred.pid); + } + + return pidref_set_pidfd_consume(ret, pidfd); +} + ssize_t send_many_fds_iov_sa( int transport_fd, int *fds_array, size_t n_fds_array, diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h index 874e559a4b3..e21a4427f0d 100644 --- a/src/basic/socket-util.h +++ b/src/basic/socket-util.h @@ -20,6 +20,7 @@ #include "macro.h" #include "missing_network.h" #include "missing_socket.h" +#include "pidref.h" #include "sparse-endian.h" union sockaddr_union { @@ -154,6 +155,7 @@ int getpeercred(int fd, struct ucred *ucred); int getpeersec(int fd, char **ret); int getpeergroups(int fd, gid_t **ret); int getpeerpidfd(int fd); +int getpeerpidref(int fd, PidRef *ret); ssize_t send_many_fds_iov_sa( int transport_fd, diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index c81001ab677..0afff628a2a 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -761,26 +761,24 @@ bool tty_is_console(const char *tty) { } int vtnr_from_tty(const char *tty) { - int i, r; + int r; assert(tty); tty = skip_dev_prefix(tty); - if (!startswith(tty, "tty") ) + const char *e = startswith(tty, "tty"); + if (!e) return -EINVAL; - if (!ascii_isdigit(tty[3])) - return -EINVAL; - - r = safe_atoi(tty+3, &i); + unsigned u; + r = safe_atou(e, &u); if (r < 0) return r; + if (!vtnr_is_valid(u)) + return -ERANGE; - if (i < 0 || i > 63) - return -EINVAL; - - return i; + return (int) u; } int resolve_dev_console(char **ret) { diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index fc7a22e6a50..7d4c3643bc9 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -164,3 +164,7 @@ static inline bool osc_char_is_valid(char c) { * ECMA-48 5th edition, section 8.3.89 */ return (unsigned char) c >= 32U && (unsigned char) c < 127; } + +static inline bool vtnr_is_valid(unsigned n) { + return n >= 1 && n <= 63; +} diff --git a/src/libsystemd/sd-json/json-util.c b/src/libsystemd/sd-json/json-util.c index 67e50e6c51b..409aedc78cc 100644 --- a/src/libsystemd/sd-json/json-util.c +++ b/src/libsystemd/sd-json/json-util.c @@ -125,14 +125,13 @@ int json_dispatch_in_addr(const char *name, sd_json_variant *variant, sd_json_di return 0; } -int json_dispatch_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { - char **p = ASSERT_PTR(userdata); - const char *path; +int json_dispatch_const_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + const char **p = ASSERT_PTR(userdata), *path; assert(variant); if (sd_json_variant_is_null(variant)) { - *p = mfree(*p); + *p = NULL; return 0; } @@ -145,8 +144,24 @@ int json_dispatch_path(const char *name, sd_json_variant *variant, sd_json_dispa if (!path_is_absolute(path)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an absolute file system path.", strna(name)); - if (free_and_strdup(p, path) < 0) - return json_log_oom(variant, flags); + *p = path; + return 0; +} + +int json_dispatch_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + char **s = ASSERT_PTR(userdata); + const char *p; + int r; + + assert_return(variant, -EINVAL); + + r = json_dispatch_const_path(name, variant, flags, &p); + if (r < 0) + return r; + + r = free_and_strdup(s, p); + if (r < 0) + return json_log(variant, flags, r, "Failed to allocate path string: %m"); return 0; } diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index 808df64d983..58098241d38 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -113,6 +113,7 @@ int json_dispatch_user_group_name(const char *name, sd_json_variant *variant, sd int json_dispatch_const_user_group_name(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_in_addr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); +int json_dispatch_const_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_pidref(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_devnum(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_ifindex(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); diff --git a/src/libsystemd/sd-login/sd-login.c b/src/libsystemd/sd-login/sd-login.c index 3aca5936229..b2c67c9d092 100644 --- a/src/libsystemd/sd-login/sd-login.c +++ b/src/libsystemd/sd-login/sd-login.c @@ -334,46 +334,46 @@ _public_ int sd_pidfd_get_cgroup(int pidfd, char **ret_cgroup) { return 0; } -_public_ int sd_peer_get_session(int fd, char **session) { - struct ucred ucred = UCRED_INVALID; +_public_ int sd_peer_get_session(int fd, char **ret) { int r; assert_return(fd >= 0, -EBADF); - assert_return(session, -EINVAL); + assert_return(ret, -EINVAL); - r = getpeercred(fd, &ucred); + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + r = getpeerpidref(fd, &pidref); if (r < 0) return r; - return cg_pid_get_session(ucred.pid, session); + return cg_pidref_get_session(&pidref, ret); } -_public_ int sd_peer_get_owner_uid(int fd, uid_t *uid) { - struct ucred ucred; +_public_ int sd_peer_get_owner_uid(int fd, uid_t *ret) { int r; assert_return(fd >= 0, -EBADF); - assert_return(uid, -EINVAL); + assert_return(ret, -EINVAL); - r = getpeercred(fd, &ucred); + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + r = getpeerpidref(fd, &pidref); if (r < 0) return r; - return cg_pid_get_owner_uid(ucred.pid, uid); + return cg_pidref_get_owner_uid(&pidref, ret); } -_public_ int sd_peer_get_unit(int fd, char **unit) { - struct ucred ucred; +_public_ int sd_peer_get_unit(int fd, char **ret) { int r; assert_return(fd >= 0, -EBADF); - assert_return(unit, -EINVAL); + assert_return(ret, -EINVAL); - r = getpeercred(fd, &ucred); + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + r = getpeerpidref(fd, &pidref); if (r < 0) return r; - return cg_pid_get_unit(ucred.pid, unit); + return cg_pidref_get_unit(&pidref, ret); } _public_ int sd_peer_get_user_unit(int fd, char **unit) { diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 5568388c372..bad654867a5 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -3215,7 +3215,13 @@ _public_ int sd_varlink_server_new(sd_varlink_server **ret, sd_varlink_server_fl int r; assert_return(ret, -EINVAL); - assert_return((flags & ~(SD_VARLINK_SERVER_ROOT_ONLY|SD_VARLINK_SERVER_MYSELF_ONLY|SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA|SD_VARLINK_SERVER_INPUT_SENSITIVE)) == 0, -EINVAL); + assert_return((flags & ~(SD_VARLINK_SERVER_ROOT_ONLY| + SD_VARLINK_SERVER_MYSELF_ONLY| + SD_VARLINK_SERVER_ACCOUNT_UID| + SD_VARLINK_SERVER_INHERIT_USERDATA| + SD_VARLINK_SERVER_INPUT_SENSITIVE| + SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT| + SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT)) == 0, -EINVAL); s = new(sd_varlink_server, 1); if (!s) @@ -3425,6 +3431,9 @@ _public_ int sd_varlink_server_add_connection_pair( if (asprintf(&desc, "%s-%i-%i", varlink_server_description(server), input_fd, output_fd) >= 0) v->description = TAKE_PTR(desc); + (void) sd_varlink_set_allow_fd_passing_input(v, FLAGS_SET(server->flags, SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT)); + (void) sd_varlink_set_allow_fd_passing_output(v, FLAGS_SET(server->flags, SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT)); + /* Link up the server and the connection, and take reference in both directions. Note that the * reference on the connection is left dangling. It will be dropped when the connection is closed, * which happens in varlink_close(), including in the event loop quit callback. */ diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index dce596eeb37..001b9e28afb 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -85,6 +85,8 @@ static int get_sender_session( const char *name; int r; + assert(m); + /* Acquire the sender's session. This first checks if the sending process is inside a session itself, * and returns that. If not and 'consult_display' is true, this returns the display session of the * owning user of the caller. */ @@ -827,189 +829,25 @@ static int method_list_inhibitors(sd_bus_message *message, void *userdata, sd_bu return sd_bus_send(NULL, reply, NULL); } -static int create_session( - sd_bus_message *message, - void *userdata, - sd_bus_error *error, - uid_t uid, - pid_t leader_pid, - int leader_pidfd, - const char *service, - const char *type, - const char *class, - const char *desktop, - const char *cseat, - uint32_t vtnr, - const char *tty, - const char *display, - int remote, - const char *remote_user, - const char *remote_host, - uint64_t flags) { +static int manager_choose_session_id( + Manager *m, + PidRef *leader, + char **ret_id) { - _cleanup_(pidref_done) PidRef leader = PIDREF_NULL; - Manager *m = ASSERT_PTR(userdata); - _cleanup_free_ char *id = NULL; - Session *session = NULL; - uint32_t audit_id = 0; - User *user = NULL; - Seat *seat = NULL; - SessionType t; - SessionClass c; int r; - assert(message); - - if (!uid_is_valid(uid)) - return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid UID"); - - if (flags != 0) - return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be zero."); - - if (leader_pidfd >= 0) - r = pidref_set_pidfd(&leader, leader_pidfd); - else if (leader_pid == 0) - r = bus_query_sender_pidref(message, &leader); - else { - if (leader_pid < 0) - return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Leader PID is not valid"); - - r = pidref_set_pid(&leader, leader_pid); - } - if (r < 0) - return r; - - if (leader.pid == 1 || pidref_is_self(&leader)) - return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid leader PID"); - - if (isempty(type)) - t = _SESSION_TYPE_INVALID; - else { - t = session_type_from_string(type); - if (t < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Invalid session type %s", type); - } - - if (isempty(class)) - c = _SESSION_CLASS_INVALID; - else { - c = session_class_from_string(class); - if (c < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Invalid session class %s", class); - } - - if (isempty(desktop)) - desktop = NULL; - else { - if (!string_is_safe(desktop)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Invalid desktop string %s", desktop); - } - - if (isempty(cseat)) - seat = NULL; - else { - seat = hashmap_get(m->seats, cseat); - if (!seat) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_SEAT, - "No seat '%s' known", cseat); - } - - if (tty_is_vc(tty)) { - int v; - - if (!seat) - seat = m->seat0; - else if (seat != m->seat0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "TTY %s is virtual console but seat %s is not seat0", tty, seat->id); - - v = vtnr_from_tty(tty); - if (v <= 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Cannot determine VT number from virtual console TTY %s", tty); - - if (vtnr == 0) - vtnr = (uint32_t) v; - else if (vtnr != (uint32_t) v) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Specified TTY and VT number do not match"); - - } else if (tty_is_console(tty)) { - - if (!seat) - seat = m->seat0; - else if (seat != m->seat0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Console TTY specified but seat is not seat0"); - - if (vtnr != 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Console TTY specified but VT number is not 0"); - } - - if (seat) { - if (seat_has_vts(seat)) { - if (vtnr <= 0 || vtnr > 63) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "VT number out of range"); - } else { - if (vtnr != 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Seat has no VTs but VT number not 0"); - } - } - - if (t == _SESSION_TYPE_INVALID) { - if (!isempty(display)) - t = SESSION_X11; - else if (!isempty(tty)) - t = SESSION_TTY; - else - t = SESSION_UNSPECIFIED; - } - - if (c == _SESSION_CLASS_INVALID) { - if (t == SESSION_UNSPECIFIED) - c = SESSION_BACKGROUND; - else - c = SESSION_USER; - } - - /* 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 log_debug_errno( - r, - "Failed to check if process " PID_FMT " is already in a session: %m", - leader.pid); - 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. */ - if (c != SESSION_GREETER && - vtnr > 0 && - vtnr < MALLOC_ELEMENTSOF(m->seat0->positions) && - m->seat0->positions[vtnr] && - m->seat0->positions[vtnr]->class != SESSION_GREETER) - return sd_bus_error_set(error, BUS_ERROR_SESSION_BUSY, "Already occupied by a session"); - - if (hashmap_size(m->sessions) >= m->sessions_max) - return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, - "Maximum number of sessions (%" PRIu64 ") reached, refusing further sessions.", - m->sessions_max); - - (void) audit_session_from_pid(&leader, &audit_id); - if (audit_session_is_valid(audit_id)) { - /* Keep our session IDs and the audit session IDs in sync */ + assert(m); + assert(pidref_is_set(leader)); + assert(ret_id); + /* Try to keep our session IDs and the audit session IDs in sync */ + _cleanup_free_ char *id = NULL; + uint32_t audit_id = AUDIT_SESSION_INVALID; + r = audit_session_from_pid(leader, &audit_id); + if (r < 0) { + if (r != -ENODATA) + log_debug_errno(r, "Failed to read audit session ID of process " PID_FMT ", ignoring: %m", leader->pid); + } else { if (asprintf(&id, "%"PRIu32, audit_id) < 0) return -ENOMEM; @@ -1017,12 +855,11 @@ static int create_session( * not trust the audit data and let's better register a new ID */ if (hashmap_contains(m->sessions, id)) { log_warning("Existing logind session ID %s used by new audit session, ignoring.", id); - audit_id = AUDIT_SESSION_INVALID; id = mfree(id); } } - if (!id) { + if (!id) do { id = mfree(id); @@ -1030,15 +867,97 @@ static int create_session( return -ENOMEM; } while (hashmap_contains(m->sessions, id)); - } /* The generated names should not clash with 'auto' or 'self' */ assert(!SESSION_IS_SELF(id)); assert(!SESSION_IS_AUTO(id)); + *ret_id = TAKE_PTR(id); + return 0; +} + +int manager_create_session( + Manager *m, + uid_t uid, + PidRef *leader, /* consumed */ + const char *service, + SessionType type, + SessionClass class, + const char *desktop, + Seat *seat, + unsigned vtnr, + const char *tty, + const char *display, + bool remote, + const char *remote_user, + const char *remote_host, + Session **ret_session) { + + int r; + + assert(m); + assert(uid_is_valid(uid)); + assert(pidref_is_set(leader)); + assert(ret_session); + + /* Returns: + * -EBUSY → client is already in a session + * -EADDRNOTAVAIL → VT is already taken + * -EUSERS → limit of sessions reached + */ + + if (type == _SESSION_TYPE_INVALID) { + if (!isempty(display)) + type = SESSION_X11; + else if (!isempty(tty)) + type = SESSION_TTY; + else + type = SESSION_UNSPECIFIED; + } + + if (class == _SESSION_CLASS_INVALID) { + if (type == SESSION_UNSPECIFIED) + class = SESSION_BACKGROUND; + else + class = SESSION_USER; + } + + /* 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 log_debug_errno( + r, + "Failed to check if process " PID_FMT " is already in a session: %m", + leader->pid); + if (r > 0) + return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "Client is already in a session."); + + /* 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 (class != SESSION_GREETER && + vtnr > 0 && + vtnr < MALLOC_ELEMENTSOF(m->seat0->positions) && + m->seat0->positions[vtnr] && + m->seat0->positions[vtnr]->class != SESSION_GREETER) + return log_debug_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), "VT already occupied by a session."); + + if (hashmap_size(m->sessions) >= m->sessions_max) + return log_debug_errno(SYNTHETIC_ERRNO(EUSERS), "Maximum number of sessions (%" PRIu64 ") reached, refusing further sessions.", m->sessions_max); + + _cleanup_free_ char *id = NULL; + r = manager_choose_session_id(m, leader, &id); + if (r < 0) + return r; + /* If we are not watching utmp already, try again */ manager_reconnect_utmp(m); + User *user = NULL; + Session *session = NULL; + r = manager_add_user_by_uid(m, uid, &user); if (r < 0) goto fail; @@ -1048,21 +967,21 @@ static int create_session( goto fail; session_set_user(session, user); - r = session_set_leader_consume(session, TAKE_PIDREF(leader)); + r = session_set_leader_consume(session, TAKE_PIDREF(*leader)); if (r < 0) goto fail; - session->original_type = session->type = t; + session->original_type = session->type = type; session->remote = remote; session->vtnr = vtnr; - session->class = c; + session->class = class; /* 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)) + if (SESSION_CLASS_PIN_USER(class)) user->gc_mode = USER_GC_BY_PIN; if (!isempty(tty)) { @@ -1109,14 +1028,184 @@ static int create_session( goto fail; } + *ret_session = session; + return 0; + +fail: + if (session) + session_add_to_gc_queue(session); + + if (user) + user_add_to_gc_queue(user); + + return r; +} + +static int manager_create_session_by_bus( + Manager *m, + sd_bus_message *message, + sd_bus_error *error, + uid_t uid, + pid_t leader_pid, + int leader_pidfd, + const char *service, + const char *type, + const char *class, + const char *desktop, + const char *cseat, + uint32_t vtnr, + const char *tty, + const char *display, + int remote, + const char *remote_user, + const char *remote_host, + uint64_t flags) { + + int r; + + assert(m); + assert(message); + + if (!uid_is_valid(uid)) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid UID"); + + if (flags != 0) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be zero."); + + _cleanup_(pidref_done) PidRef leader = PIDREF_NULL; + if (leader_pidfd >= 0) + r = pidref_set_pidfd(&leader, leader_pidfd); + else if (leader_pid == 0) + r = bus_query_sender_pidref(message, &leader); + else { + if (leader_pid < 0) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Leader PID is not valid"); + + r = pidref_set_pid(&leader, leader_pid); + } + if (r < 0) + return r; + + if (leader.pid == 1 || pidref_is_self(&leader)) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid leader PID"); + + SessionType t; + if (isempty(type)) + t = _SESSION_TYPE_INVALID; + else { + t = session_type_from_string(type); + if (t < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Invalid session type %s", type); + } + + SessionClass c; + if (isempty(class)) + c = _SESSION_CLASS_INVALID; + else { + c = session_class_from_string(class); + if (c < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Invalid session class %s", class); + } + + if (isempty(desktop)) + desktop = NULL; + else { + if (!string_is_safe(desktop)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Invalid desktop string %s", desktop); + } + + Seat *seat = NULL; + if (isempty(cseat)) + seat = NULL; + else { + seat = hashmap_get(m->seats, cseat); + if (!seat) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_SEAT, + "No seat '%s' known", cseat); + } + + if (isempty(tty)) + tty = NULL; + else if (tty_is_vc(tty)) { + int v; + + if (!seat) + seat = m->seat0; + else if (seat != m->seat0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "TTY %s is virtual console but seat %s is not seat0", tty, seat->id); + + v = vtnr_from_tty(tty); + if (v <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Cannot determine VT number from virtual console TTY %s", tty); + + if (vtnr == 0) + vtnr = (uint32_t) v; + else if (vtnr != (uint32_t) v) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Specified TTY and VT number do not match"); + + } else if (tty_is_console(tty)) { + + if (!seat) + seat = m->seat0; + else if (seat != m->seat0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Console TTY specified but seat is not seat0"); + + if (vtnr != 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Console TTY specified but VT number is not 0"); + } + + if (seat) { + if (seat_has_vts(seat)) { + if (!vtnr_is_valid(vtnr)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "VT number out of range"); + } else { + if (vtnr != 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Seat has no VTs but VT number not 0"); + } + } + + Session *session; + r = manager_create_session( + m, + uid, + &leader, + service, + t, + c, + desktop, + seat, + vtnr, + tty, + display, + remote, + remote_user, + remote_host, + &session); + if (r == -EBUSY) + return sd_bus_error_setf(error, BUS_ERROR_SESSION_BUSY, "Already running in a session or user slice"); + if (r == -EADDRNOTAVAIL) + return sd_bus_error_set(error, BUS_ERROR_SESSION_BUSY, "Virtual terminal already occupied by a session"); + if (r == -EUSERS) + return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Maximum number of sessions (%" PRIu64 ") reached, refusing further sessions.", m->sessions_max); + if (r < 0) + return r; + r = sd_bus_message_enter_container(message, 'a', "(sv)"); if (r < 0) goto fail; - r = session_start(session, message, error); if (r < 0) goto fail; - r = sd_bus_message_exit_container(message); if (r < 0) goto fail; @@ -1130,7 +1219,7 @@ static int create_session( * all is complete - or wait again. */ r = session_send_create_reply(session, /* error= */ NULL); if (r < 0) - return r; + goto fail; return 1; @@ -1138,9 +1227,6 @@ fail: if (session) session_add_to_gc_queue(session); - if (user) - user_add_to_gc_queue(user); - return r; } @@ -1175,9 +1261,9 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus if (r < 0) return r; - return create_session( - message, + return manager_create_session_by_bus( userdata, + message, error, uid, leader_pid, @@ -1223,9 +1309,9 @@ static int method_create_session_pidfd(sd_bus_message *message, void *userdata, if (r < 0) return r; - return create_session( - message, + return manager_create_session_by_bus( userdata, + message, error, uid, /* leader_pid = */ 0, diff --git a/src/login/logind-dbus.h b/src/login/logind-dbus.h index 53e6888f5c4..afbf523edec 100644 --- a/src/login/logind-dbus.h +++ b/src/login/logind-dbus.h @@ -47,4 +47,21 @@ int manager_job_is_active(Manager *manager, const char *path, sd_bus_error *erro void manager_load_scheduled_shutdown(Manager *m); +int manager_create_session( + Manager *m, + uid_t uid, + PidRef *leader, + const char *service, + SessionType type, + SessionClass class, + const char *desktop, + Seat *seat, + unsigned vtnr, + const char *tty, + const char *display, + bool remote, + const char *remote_user, + const char *remote_host, + Session **ret_session); + extern const BusObjectImplementation manager_object; diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c index 5300284e5ac..2affc32b16d 100644 --- a/src/login/logind-session-dbus.c +++ b/src/login/logind-session-dbus.c @@ -899,62 +899,40 @@ int session_send_lock_all(Manager *m, bool lock) { return r; } -static bool session_job_pending(Session *s) { - assert(s); - assert(s->user); - - /* Check if we have some jobs enqueued and not finished yet. Each time we get JobRemoved signal about - * relevant units, session_send_create_reply and hence us is called (see match_job_removed). - * Note that we don't care about job result here. */ - - return s->scope_job || - s->user->runtime_dir_job || - (SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class) && s->user->service_manager_job); -} - -int session_send_create_reply(Session *s, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL; - _cleanup_close_ int fifo_fd = -EBADF; - _cleanup_free_ char *p = NULL; - +int session_send_create_reply_bus(Session *s, const sd_bus_error *error) { assert(s); - /* This is called after the session scope and the user service were successfully created, and finishes where - * bus_manager_create_session() left off. */ + /* This is called after the session scope and the user service were successfully created, and + * finishes where manager_create_session() left off. */ - if (!s->create_message) + _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = TAKE_PTR(s->create_message); + if (!c) return 0; - /* If error occurred, return it immediately. Otherwise let's wait for all jobs to finish before - * continuing. */ - if (!sd_bus_error_is_set(error) && session_job_pending(s)) - return 0; - - c = TAKE_PTR(s->create_message); - if (error) + if (sd_bus_error_is_set(error)) return sd_bus_reply_method_error(c, error); - fifo_fd = session_create_fifo(s); + _cleanup_close_ int fifo_fd = session_create_fifo(s); if (fifo_fd < 0) return fifo_fd; /* Update the session state file before we notify the client about the result. */ session_save(s); - p = session_bus_path(s); + _cleanup_free_ char *p = session_bus_path(s); if (!p) return -ENOMEM; - log_debug("Sending reply about created session: " - "id=%s object_path=%s uid=%u runtime_path=%s " + log_debug("Sending D-Bus reply about created session: " + "id=%s object_path=%s uid=" UID_FMT " runtime_path=%s " "session_fd=%d seat=%s vtnr=%u", s->id, p, - (uint32_t) s->user->user_record->uid, + s->user->user_record->uid, s->user->runtime_path, fifo_fd, s->seat ? s->seat->id : "", - (uint32_t) s->vtnr); + s->vtnr); return sd_bus_reply_method_return( c, "soshusub", @@ -968,7 +946,7 @@ int session_send_create_reply(Session *s, sd_bus_error *error) { false); } -int session_send_upgrade_reply(Session *s, sd_bus_error *error) { +int session_send_upgrade_reply(Session *s, const sd_bus_error *error) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL; assert(s); diff --git a/src/login/logind-session-dbus.h b/src/login/logind-session-dbus.h index 37a107ec336..091ad26292f 100644 --- a/src/login/logind-session-dbus.h +++ b/src/login/logind-session-dbus.h @@ -15,8 +15,8 @@ int session_send_changed(Session *s, const char *properties, ...) _sentinel_; int session_send_lock(Session *s, bool lock); int session_send_lock_all(Manager *m, bool lock); -int session_send_create_reply(Session *s, sd_bus_error *error); -int session_send_upgrade_reply(Session *s, sd_bus_error *error); +int session_send_create_reply_bus(Session *s, const sd_bus_error *error); +int session_send_upgrade_reply(Session *s, const sd_bus_error *error); int bus_session_method_activate(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_session_method_lock(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/login/logind-session.c b/src/login/logind-session.c index 351b64b60bb..433c59500a9 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -29,6 +29,7 @@ #include "logind-session-dbus.h" #include "logind-session.h" #include "logind-user-dbus.h" +#include "logind-varlink.h" #include "mkdir-label.h" #include "parse-util.h" #include "path-util.h" @@ -192,6 +193,8 @@ Session* session_free(Session *s) { sd_bus_message_unref(s->create_message); sd_bus_message_unref(s->upgrade_message); + sd_varlink_unref(s->create_link); + free(s->tty); free(s->display); free(s->remote_host); @@ -1648,6 +1651,35 @@ void session_drop_controller(Session *s) { session_restore_vt(s); } +bool session_job_pending(Session *s) { + assert(s); + assert(s->user); + + /* Check if we have some jobs enqueued and not finished yet. Each time we get JobRemoved signal about + * relevant units, session_send_create_reply and hence us is called (see match_job_removed). + * Note that we don't care about job result here. */ + + return s->scope_job || + s->user->runtime_dir_job || + (SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class) && s->user->service_manager_job); +} + +int session_send_create_reply(Session *s, const sd_bus_error *error) { + int r; + + assert(s); + + /* If error occurred, return it immediately. Otherwise let's wait for all jobs to finish before + * continuing. */ + if (!sd_bus_error_is_set(error) && session_job_pending(s)) + return 0; + + r = 0; + RET_GATHER(r, session_send_create_reply_bus(s, error)); + RET_GATHER(r, session_send_create_reply_varlink(s, error)); + return r; +} + 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 2d2cb189fca..9ec19ba53ec 100644 --- a/src/login/logind-session.h +++ b/src/login/logind-session.h @@ -150,6 +150,8 @@ struct Session { sd_bus_message *create_message; /* The D-Bus message used to create the session, which we haven't responded to yet */ sd_bus_message *upgrade_message; /* The D-Bus message used to upgrade the session class user-incomplete → user, which we haven't responded to yet */ + sd_varlink *create_link; /* The Varlink connection used to create session, which we haven't responded to yet */ + /* Set up when a client requested to release the session via the bus */ sd_event_source *timer_event_source; @@ -216,6 +218,10 @@ bool session_is_controller(Session *s, const char *sender); int session_set_controller(Session *s, const char *sender, bool force, bool prepare); void session_drop_controller(Session *s); +bool session_job_pending(Session *s); + +int session_send_create_reply(Session *s, const sd_bus_error *error); + static inline bool SESSION_IS_SELF(const char *name) { return isempty(name) || streq(name, "self"); } diff --git a/src/login/logind-varlink.c b/src/login/logind-varlink.c new file mode 100644 index 00000000000..8f060f67af4 --- /dev/null +++ b/src/login/logind-varlink.c @@ -0,0 +1,383 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "cgroup-util.h" +#include "fd-util.h" +#include "json-util.h" +#include "logind.h" +#include "logind-dbus.h" +#include "logind-session-dbus.h" +#include "logind-varlink.h" +#include "terminal-util.h" +#include "user-util.h" +#include "varlink-io.systemd.Login.h" +#include "varlink-util.h" + +static int manager_varlink_get_session_by_peer( + Manager *m, + sd_varlink *link, + bool consult_display, + Session **ret) { + + int r; + + assert(m); + assert(link); + 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 */ + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + r = varlink_get_peer_pidref(link, &pidref); + if (r < 0) + return log_error_errno(r, "Failed to acquire peer PID: %m"); + + Session *session = NULL; + _cleanup_free_ char *name = NULL; + r = cg_pidref_get_session(&pidref, &name); + if (r < 0) { + if (!consult_display) + log_debug_errno(r, "Failed to acquire session of peer, giving up: %m"); + else { + log_debug_errno(r, "Failed to acquire session of peer, trying to find owner UID: %m"); + + uid_t uid; + r = cg_pidref_get_owner_uid(&pidref, &uid); + if (r < 0) + log_debug_errno(r, "Failed to acquire owning UID of peer, giving up: %m"); + else { + User *user = hashmap_get(m->users, UID_TO_PTR(uid)); + if (user) + session = user->display; + } + } + } else + session = hashmap_get(m->sessions, name); + + if (!session) + return sd_varlink_error(link, "io.systemd.Login.NoSuchSession", /* parameters= */ NULL); + + *ret = session; + return 0; +} + +static int manager_varlink_get_session_by_name( + Manager *m, + sd_varlink *link, + const char *name, + Session **ret) { + + assert(m); + assert(link); + assert(ret); + + /* Resolves a session name to a session object. Supports resolving the special names "self" and "auto". */ + + 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); + + Session *session = hashmap_get(m->sessions, name); + if (!session) + return sd_varlink_error(link, "io.systemd.Login.NoSuchSession", /* parameters= */ NULL); + + *ret = session; + return 0; +} + +int session_send_create_reply_varlink(Session *s, const sd_bus_error *error) { + assert(s); + + /* This is called after the session scope and the user service were successfully created, and + * finishes where manager_create_session() left off. */ + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = TAKE_PTR(s->create_link); + if (!vl) + return 0; + + if (sd_bus_error_is_set(error)) + return sd_varlink_error(vl, "io.systemd.Login.UnitAllocationFailed", /* parameters= */ NULL); + + _cleanup_close_ int fifo_fd = session_create_fifo(s); + if (fifo_fd < 0) + return fifo_fd; + + /* Update the session state file before we notify the client about the result. */ + session_save(s); + + log_debug("Sending Varlink reply about created session: " + "id=%s uid=" UID_FMT " runtime_path=%s " + "session_fd=%d seat=%s vtnr=%u", + s->id, + s->user->user_record->uid, + s->user->runtime_path, + fifo_fd, + s->seat ? s->seat->id : "", + s->vtnr); + + int fifo_fd_idx = sd_varlink_push_fd(vl, fifo_fd); + if (fifo_fd_idx < 0) { + log_error_errno(fifo_fd_idx, "Failed to push FIFO fd to Varlink: %m"); + return sd_varlink_error_errno(vl, fifo_fd_idx); + } + + TAKE_FD(fifo_fd); + + return sd_varlink_replybo( + vl, + SD_JSON_BUILD_PAIR_STRING("Id", s->id), + SD_JSON_BUILD_PAIR_STRING("RuntimePath", s->user->runtime_path), + SD_JSON_BUILD_PAIR_UNSIGNED("SessionFileDescriptor", fifo_fd_idx), + 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))); +} + +static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_session_class, SessionClass, session_class_from_string); +static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_session_type, SessionType, session_type_from_string); + +typedef struct CreateSessionParameters { + uid_t uid; + PidRef pid; + const char *service; + SessionType type; + SessionClass class; + const char *desktop; + const char *seat; + unsigned vtnr; + const char *tty; + const char *display; + int remote; + const char *remote_user; + const char *remote_host; +} CreateSessionParameters; + +static void create_session_parameters_done(CreateSessionParameters *p) { + pidref_done(&p->pid); +} + +static int vl_method_create_session(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[] = { + { "UID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(CreateSessionParameters, uid), SD_JSON_MANDATORY }, + { "PID", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_pidref, offsetof(CreateSessionParameters, pid), SD_JSON_RELAX }, + { "Service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, service), 0 }, + { "Type", SD_JSON_VARIANT_STRING, json_dispatch_session_type, offsetof(CreateSessionParameters, type), SD_JSON_MANDATORY }, + { "Class", SD_JSON_VARIANT_STRING, json_dispatch_session_class, offsetof(CreateSessionParameters, class), SD_JSON_MANDATORY }, + { "Desktop", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, desktop), SD_JSON_STRICT }, + { "Seat", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, seat), 0 }, + { "VTNr", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(CreateSessionParameters, vtnr), 0 }, + { "TTY", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, tty), 0 }, + { "Display", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, display), 0 }, + { "Remote", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(CreateSessionParameters, remote), 0 }, + { "RemoteUser", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, remote_user), 0 }, + { "RemoteHost", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CreateSessionParameters, remote_host), 0 }, + {} + }; + + _cleanup_(create_session_parameters_done) CreateSessionParameters p = { + .uid = UID_INVALID, + .pid = PIDREF_NULL, + .class = _SESSION_CLASS_INVALID, + .type = _SESSION_TYPE_INVALID, + .remote = -1, + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + Seat *seat = NULL; + if (p.seat) { + seat = hashmap_get(m->seats, p.seat); + if (!seat) + return sd_varlink_replyb(link, "io.systemd.Login.NoSuchSeat", /* parameters= */ NULL); + } + + if (p.tty) { + if (tty_is_vc(p.tty)) { + if (!seat) + seat = m->seat0; + else if (seat != m->seat0) + return sd_varlink_error_invalid_parameter_name(link, "Seat"); + + int v = vtnr_from_tty(p.tty); + if (v <= 0) + return sd_varlink_error_invalid_parameter_name(link, "TTY"); + + if (p.vtnr == 0) + p.vtnr = v; + else if (p.vtnr != (unsigned) v) + return sd_varlink_error_invalid_parameter_name(link, "VTNr"); + + } else if (tty_is_console(p.tty)) { + if (!seat) + seat = m->seat0; + else if (seat != m->seat0) + return sd_varlink_error_invalid_parameter_name(link, "Seat"); + + if (p.vtnr != 0) + return sd_varlink_error_invalid_parameter_name(link, "VTNr"); + } + } + + if (seat) { + if (seat_has_vts(seat)) { + if (!vtnr_is_valid(p.vtnr)) + return sd_varlink_error_invalid_parameter_name(link, "VTNr"); + } else { + if (p.vtnr != 0) + return sd_varlink_error_invalid_parameter_name(link, "VTNr"); + } + } + + if (p.remote < 0) + p.remote = p.remote_user || p.remote_host; + + /* Before we continue processing this, let's ensure the peer is privileged */ + uid_t peer_uid; + r = sd_varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return log_debug_errno(r, "Failed to get peer UID: %m"); + if (peer_uid != 0) + return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, /* parameters= */ NULL); + + if (!pidref_is_set(&p.pid)) { + r = varlink_get_peer_pidref(link, &p.pid); + if (r < 0) + return log_debug_errno(r, "Failed to get peer pidref: %m"); + } + + Session *session; + r = manager_create_session( + m, + p.uid, + &p.pid, + p.service, + p.type, + p.class, + p.desktop, + seat, + p.vtnr, + p.tty, + p.display, + p.remote, + p.remote_user, + p.remote_host, + &session); + if (r == -EBUSY) + return sd_varlink_error(link, "io.systemd.Login.AlreadySessionMember", /* parameters= */ NULL); + if (r == -EADDRNOTAVAIL) + return sd_varlink_error(link, "io.systemd.Login.VirtualTerminalAlreadyTaken", /* parameters= */ NULL); + if (r == -EUSERS) + return sd_varlink_error(link, "io.systemd.Login.TooManySessions", /* parameters= */ NULL); + if (r < 0) + return r; + + r = session_start(session, /* properties= */ NULL, /* error= */ NULL); + if (r < 0) + goto fail; + + session->create_link = sd_varlink_ref(link); + + /* Let's check if this is complete now */ + r = session_send_create_reply(session, /* error= */ NULL); + if (r < 0) + goto fail; + + return 1; + +fail: + if (session) + session_add_to_gc_queue(session); + + return r; +} + +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; + + struct { + const char *id; + } p; + + static const sd_json_dispatch_field dispatch_table[] = { + { "Id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, id), SD_JSON_MANDATORY }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + Session *session; + r = manager_varlink_get_session_by_name(m, link, p.id, &session); + if (r < 0) + return r; + + Session *peer_session; + r = manager_varlink_get_session_by_peer(m, link, /* consult_display= */ false, &peer_session); + if (r < 0) + return r; + + if (session != peer_session) + return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, /* parameters= */ NULL); + + r = session_release(session); + if (r < 0) + return r; + + return sd_varlink_replyb(link, NULL); +} + +int manager_varlink_init(Manager *m) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + int r; + + assert(m); + + if (m->varlink_server) + return 0; + + r = sd_varlink_server_new( + &s, + SD_VARLINK_SERVER_ACCOUNT_UID| + SD_VARLINK_SERVER_INHERIT_USERDATA| + SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT); + if (r < 0) + return log_error_errno(r, "Failed to allocate varlink server object: %m"); + + sd_varlink_server_set_userdata(s, m); + + r = sd_varlink_server_add_interface(s, &vl_interface_io_systemd_Login); + if (r < 0) + return log_error_errno(r, "Failed to add Login interface to varlink server: %m"); + + r = sd_varlink_server_bind_method_many( + s, + "io.systemd.Login.CreateSession", vl_method_create_session, + "io.systemd.Login.ReleaseSession", vl_method_release_session); + if (r < 0) + return log_error_errno(r, "Failed to register varlink methods: %m"); + + r = sd_varlink_server_listen_address(s, "/run/systemd/io.systemd.Login", 0666); + if (r < 0) + return log_error_errno(r, "Failed to bind to varlink socket: %m"); + + r = sd_varlink_server_attach_event(s, m->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); + + m->varlink_server = TAKE_PTR(s); + return 0; +} + +void manager_varlink_done(Manager *m) { + assert(m); + + m->varlink_server = sd_varlink_server_unref(m->varlink_server); +} diff --git a/src/login/logind-varlink.h b/src/login/logind-varlink.h new file mode 100644 index 00000000000..bcf2af4fedd --- /dev/null +++ b/src/login/logind-varlink.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-bus.h" + +#include "logind.h" +#include "logind-session.h" + +int manager_varlink_init(Manager *m); +void manager_varlink_done(Manager *m); + +int session_send_create_reply_varlink(Session *s, const sd_bus_error *error); diff --git a/src/login/logind.c b/src/login/logind.c index 186778677b6..ef1952b0cca 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -24,11 +24,12 @@ #include "fd-util.h" #include "format-util.h" #include "fs-util.h" +#include "logind.h" #include "logind-dbus.h" #include "logind-seat-dbus.h" #include "logind-session-dbus.h" #include "logind-user-dbus.h" -#include "logind.h" +#include "logind-varlink.h" #include "main-func.h" #include "mkdir-label.h" #include "parse-util.h" @@ -154,6 +155,8 @@ static Manager* manager_free(Manager *m) { hashmap_free(m->polkit_registry); + manager_varlink_done(m); + sd_bus_flush_close_unref(m->bus); sd_event_unref(m->event); @@ -1115,6 +1118,10 @@ static int manager_startup(Manager *m) { if (r < 0) return r; + r = manager_varlink_init(m); + if (r < 0) + return r; + /* Instantiate magic seat 0 */ r = manager_add_seat(m, "seat0", &m->seat0); if (r < 0) diff --git a/src/login/logind.h b/src/login/logind.h index ce7e76e761b..b19fbb7f3e0 100644 --- a/src/login/logind.h +++ b/src/login/logind.h @@ -7,6 +7,7 @@ #include "sd-bus.h" #include "sd-device.h" #include "sd-event.h" +#include "sd-varlink.h" #include "calendarspec.h" #include "conf-parser.h" @@ -147,6 +148,8 @@ struct Manager { CalendarSpec *maintenance_time; dual_timestamp init_ts; + + sd_varlink_server *varlink_server; }; void manager_reset_config(Manager *m); diff --git a/src/login/meson.build b/src/login/meson.build index 43db03184c5..86879b47237 100644 --- a/src/login/meson.build +++ b/src/login/meson.build @@ -26,6 +26,7 @@ liblogind_core_sources = files( 'logind-session.c', 'logind-user-dbus.c', 'logind-user.c', + 'logind-varlink.c', 'logind-wall.c', ) diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index 9d96c915395..6b89b7582c4 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -18,6 +18,9 @@ #include #include +#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); @@ -1044,13 +1051,10 @@ static int register_session( assert(ret_seat); /* 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()) { + *ret_seat = NULL; + return PAM_SUCCESS; + } pam_debug_syslog(handle, debug, "Asking logind to create session: " @@ -1065,68 +1069,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()."); + bool done = false; + if (can_use_varlink(c)) { - m = sd_bus_message_unref(m); - r = create_session_message(bus, - handle, - ur, - c, - /* avoid_pidfd = */ true, - &m); + 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. */ @@ -1191,18 +1314,17 @@ 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; } static int import_shell_credentials(pam_handle_t *handle) { @@ -1337,20 +1459,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 = pam_acquire_bus_connection(handle, "pam-systemd", debug, &bus, NULL); - if (r != PAM_SUCCESS) - return r; - - r = bus_call_method(bus, bus_login_mgr, "ReleaseSession", &error, NULL, "s", id); + r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.Login"); if (r < 0) - return pam_syslog_pam_error(handle, LOG_ERR, PAM_SESSION_ERR, - "Failed to release session: %s", bus_error_message(&error, r)); + 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); + + done = true; + } + + 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 diff --git a/src/shared/meson.build b/src/shared/meson.build index af9ef74b329..7a79d26b0f3 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -182,6 +182,7 @@ shared_sources = files( 'varlink-io.systemd.Hostname.c', 'varlink-io.systemd.Import.c', 'varlink-io.systemd.Journal.c', + 'varlink-io.systemd.Login.c', 'varlink-io.systemd.Machine.c', 'varlink-io.systemd.MachineImage.c', 'varlink-io.systemd.ManagedOOM.c', diff --git a/src/shared/varlink-io.systemd.Login.c b/src/shared/varlink-io.systemd.Login.c new file mode 100644 index 00000000000..722a2b55b48 --- /dev/null +++ b/src/shared/varlink-io.systemd.Login.c @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bus-polkit.h" +#include "varlink-idl-common.h" +#include "varlink-io.systemd.Login.h" + +static SD_VARLINK_DEFINE_ENUM_TYPE( + SessionType, + SD_VARLINK_DEFINE_ENUM_VALUE(unspecified), + SD_VARLINK_DEFINE_ENUM_VALUE(tty), + SD_VARLINK_DEFINE_ENUM_VALUE(x11), + SD_VARLINK_DEFINE_ENUM_VALUE(wayland), + SD_VARLINK_DEFINE_ENUM_VALUE(mir), + SD_VARLINK_DEFINE_ENUM_VALUE(web)); + +static SD_VARLINK_DEFINE_ENUM_TYPE( + SessionClass, + SD_VARLINK_FIELD_COMMENT("Regular user sessions"), + SD_VARLINK_DEFINE_ENUM_VALUE(user), + SD_VARLINK_FIELD_COMMENT("Session of the root user that shall be open for login from earliest moment on, and not be delayed for /run/nologin"), + SD_VARLINK_DEFINE_ENUM_VALUE(user_early), + SD_VARLINK_FIELD_COMMENT("Regular user session whose home directory is not available right now, but will be later, at which point the session class can be upgraded to 'user'"), + SD_VARLINK_DEFINE_ENUM_VALUE(user_incomplete), + SD_VARLINK_FIELD_COMMENT("Display manager greeter screen used for login"), + SD_VARLINK_DEFINE_ENUM_VALUE(greeter), + SD_VARLINK_FIELD_COMMENT("Similar, but a a lock screen"), + SD_VARLINK_DEFINE_ENUM_VALUE(lock_screen), + SD_VARLINK_FIELD_COMMENT("Background session (that has no TTY, VT, Seat)"), + SD_VARLINK_DEFINE_ENUM_VALUE(background), + SD_VARLINK_FIELD_COMMENT("Similar, but for which no service manager is invoked"), + SD_VARLINK_DEFINE_ENUM_VALUE(background_light), + SD_VARLINK_FIELD_COMMENT("The special session of the service manager"), + SD_VARLINK_DEFINE_ENUM_VALUE(manager), + 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_METHOD( + CreateSession, + SD_VARLINK_FIELD_COMMENT("Numeric UNIX UID of the user this session shall be owned by"), + SD_VARLINK_DEFINE_INPUT(UID, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("Process that shall become the leader of the session. If null defaults to the IPC client."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(PID, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("PAM service name of the program requesting the session"), + SD_VARLINK_DEFINE_INPUT(Service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The type of the session"), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(Type, SessionType, 0), + SD_VARLINK_FIELD_COMMENT("The class of the session"), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(Class, SessionClass, 0), + SD_VARLINK_FIELD_COMMENT("An identifier for the chosen desktop"), + SD_VARLINK_DEFINE_INPUT(Desktop, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The name of the seat to assign this session to"), + SD_VARLINK_DEFINE_INPUT(Seat, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The virtual terminal number to assign this session to"), + SD_VARLINK_DEFINE_INPUT(VTNr, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The TTY device to assign this session to, if applicable"), + SD_VARLINK_DEFINE_INPUT(TTY, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The X11 display for this session"), + SD_VARLINK_DEFINE_INPUT(Display, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true this is a remote session"), + SD_VARLINK_DEFINE_INPUT(Remote, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("User name on the remote site, if known"), + SD_VARLINK_DEFINE_INPUT(RemoteUser, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Host name of the remote host"), + SD_VARLINK_DEFINE_INPUT(RemoteHost, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The identifier string of the session of the user."), + SD_VARLINK_DEFINE_OUTPUT(Id, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The runtime path ($XDG_RUNTIME_DIR) of the user."), + SD_VARLINK_DEFINE_OUTPUT(RuntimePath, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Index into the file descriptor table of this reply with the session tracking fd for this session."), + SD_VARLINK_DEFINE_OUTPUT(SessionFileDescriptor, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("The original UID of this session."), + SD_VARLINK_DEFINE_OUTPUT(UID, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("The seat this session has been assigned to"), + SD_VARLINK_DEFINE_OUTPUT(Seat, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The virtual terminal number the session has been assigned to"), + SD_VARLINK_DEFINE_OUTPUT(VTNr, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + +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."), + SD_VARLINK_DEFINE_INPUT(Id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_ERROR(NoSuchSession); +static SD_VARLINK_DEFINE_ERROR(NoSuchSeat); +static SD_VARLINK_DEFINE_ERROR(AlreadySessionMember); +static SD_VARLINK_DEFINE_ERROR(VirtualTerminalAlreadyTaken); +static SD_VARLINK_DEFINE_ERROR(TooManySessions); +static SD_VARLINK_DEFINE_ERROR(UnitAllocationFailed); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_Login, + "io.systemd.Login", + SD_VARLINK_INTERFACE_COMMENT("APIs for managing login sessions."), + SD_VARLINK_SYMBOL_COMMENT("Process identifier"), + &vl_type_ProcessId, + 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("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."), + &vl_method_ReleaseSession, + SD_VARLINK_SYMBOL_COMMENT("No session by this name found"), + &vl_error_NoSuchSession, + SD_VARLINK_SYMBOL_COMMENT("No seat by this name found"), + &vl_error_NoSuchSeat, + SD_VARLINK_SYMBOL_COMMENT("Process already member of a session"), + &vl_error_AlreadySessionMember, + SD_VARLINK_SYMBOL_COMMENT("The specified virtual terminal (VT) is already taken by another session"), + &vl_error_VirtualTerminalAlreadyTaken, + SD_VARLINK_SYMBOL_COMMENT("Maximum number of sessions reached"), + &vl_error_TooManySessions, + SD_VARLINK_SYMBOL_COMMENT("Failed to allocate a unit for the session"), + &vl_error_UnitAllocationFailed); diff --git a/src/shared/varlink-io.systemd.Login.h b/src/shared/varlink-io.systemd.Login.h new file mode 100644 index 00000000000..5453cdfb25f --- /dev/null +++ b/src/shared/varlink-io.systemd.Login.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +extern const sd_varlink_interface vl_interface_io_systemd_Login; diff --git a/src/systemd/sd-varlink.h b/src/systemd/sd-varlink.h index 816b9b0ebfe..7ddbec59e85 100644 --- a/src/systemd/sd-varlink.h +++ b/src/systemd/sd-varlink.h @@ -65,11 +65,13 @@ __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_method_flags_t) { } sd_varlink_method_flags_t; __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_server_flags_t) { - SD_VARLINK_SERVER_ROOT_ONLY = 1 << 0, /* Only accessible by root */ - SD_VARLINK_SERVER_MYSELF_ONLY = 1 << 1, /* Only accessible by our own UID */ - SD_VARLINK_SERVER_ACCOUNT_UID = 1 << 2, /* Do per user accounting */ - SD_VARLINK_SERVER_INHERIT_USERDATA = 1 << 3, /* Initialize Varlink connection userdata from sd_varlink_server userdata */ - SD_VARLINK_SERVER_INPUT_SENSITIVE = 1 << 4, /* Automatically mark all connection input as sensitive */ + SD_VARLINK_SERVER_ROOT_ONLY = 1 << 0, /* Only accessible by root */ + SD_VARLINK_SERVER_MYSELF_ONLY = 1 << 1, /* Only accessible by our own UID */ + SD_VARLINK_SERVER_ACCOUNT_UID = 1 << 2, /* Do per user accounting */ + SD_VARLINK_SERVER_INHERIT_USERDATA = 1 << 3, /* Initialize Varlink connection userdata from sd_varlink_server userdata */ + SD_VARLINK_SERVER_INPUT_SENSITIVE = 1 << 4, /* Automatically mark all connection input as sensitive */ + SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT = 1 << 5, /* Allow receiving fds over all connections */ + SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT = 1 << 6, /* Allow sending fds over all connections */ _SD_ENUM_FORCE_S64(SD_VARLINK_SERVER) } sd_varlink_server_flags_t; diff --git a/src/test/test-cgroup-util.c b/src/test/test-cgroup-util.c index 1d8f99cc6e5..9fa3ddf9d13 100644 --- a/src/test/test-cgroup-util.c +++ b/src/test/test-cgroup-util.c @@ -212,8 +212,8 @@ TEST(proc) { cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid.pid, &path); cg_pid_get_path_shifted(pid.pid, NULL, &path_shifted); - cg_pid_get_owner_uid(pid.pid, &uid); - cg_pid_get_session(pid.pid, &session); + cg_pidref_get_owner_uid(&pid, &uid); + cg_pidref_get_session(&pid, &session); cg_pid_get_unit(pid.pid, &unit); cg_pid_get_user_unit(pid.pid, &user_unit); cg_pid_get_machine_name(pid.pid, &machine); diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index 182d59bd206..fc319dc2cce 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -14,6 +14,7 @@ #include "varlink-io.systemd.Credentials.h" #include "varlink-io.systemd.Import.h" #include "varlink-io.systemd.Journal.h" +#include "varlink-io.systemd.Login.h" #include "varlink-io.systemd.Machine.h" #include "varlink-io.systemd.MachineImage.h" #include "varlink-io.systemd.ManagedOOM.h" @@ -194,6 +195,8 @@ TEST(parse_format) { print_separator(); test_parse_format_one(&vl_interface_io_systemd_MachineImage); print_separator(); + test_parse_format_one(&vl_interface_io_systemd_Login); + print_separator(); test_parse_format_one(&vl_interface_xyz_test); } diff --git a/test/units/TEST-35-LOGIN.sh b/test/units/TEST-35-LOGIN.sh index 80320e32e17..2ecb3acca68 100755 --- a/test/units/TEST-35-LOGIN.sh +++ b/test/units/TEST-35-LOGIN.sh @@ -739,6 +739,10 @@ EOF systemctl stop user@"$uid".service } +testcase_varlink() { + varlinkctl introspect /run/systemd/io.systemd.Login +} + setup_test_user test_write_dropin run_testcases