Compare commits

...

24 Commits

Author SHA1 Message Date
Lennart Poettering 179f0e203e
Merge 67591fd782 into c946b13575 2024-11-23 00:22:00 +01:00
Lennart Poettering 67591fd782 pam-systemd: talk to logind via varlink
This makes sure we now use Varlink per default as transport for
allocating sessions.

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

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

The latter should be revisited once it is possible to allocate a scope
unit from PID1 via varlink.
2024-11-23 00:21:44 +01:00
Lennart Poettering 759036b42f logind: add basic Varlink API
For now this only covers CreateSession() and ReleaseSession(), i.e. the
two operations pam_systemd cares about.
2024-11-23 00:18:46 +01:00
Lennart Poettering 3ccbb4e518 logind: split create session reply handling in two
This prepares ground so that later on we can reply with either D-Bus or
Varlink depending on the client's request.
2024-11-23 00:18:46 +01:00
Lennart Poettering 70731b1425 logind: also potentially GC the session if we cannot send reply 2024-11-23 00:18:46 +01:00
Lennart Poettering a098cdbefe logind: indicate that 'error' parameter is input by making it const 2024-11-23 00:18:46 +01:00
Lennart Poettering b5dd575cb3 logind: rework session creation logic, to be more reusable for varlink codepaths
This separates the preparatory checks that generate D-Bus errors from
the code that actually allocates the session. This make the logic easier
to follow and prepares ground so that we can reuse the 2nd part later
when exposing session creation via Varlink.
2024-11-23 00:18:46 +01:00
Lennart Poettering c771a195b6 logind: split out logic that finds free session ID into helper call
Just some refactoring to make an overly large function a bit smaller.
2024-11-23 00:18:46 +01:00
Lennart Poettering d30ae4c4d2 logind: normalize parameter to create_session()
We can pass a properly typed Manager object here, no reason to pass it
as void*.
2024-11-23 00:18:46 +01:00
Lennart Poettering 49e778d40a sd-varlink: fix bug when enqueuing messages with fds asynchronously
When determining the poll events to wait for we need to take the queue
of pending messages that carry fds into account. Otherwise we might end
up not waking up if such an fd-carrying message is enqueued
asynchronously (i.e. not from a dispatch callback).
2024-11-23 00:18:46 +01:00
Lennart Poettering 1f377e6f73 sd-varlink: add flag for sd_varlink_server for creating connections with fd passing enabled
Let's add a simple flag that enables fd passing for all connections of a
server. It's much easier to use this than to install a connect handler
which manually enables this for each connection.
2024-11-23 00:18:46 +01:00
Lennart Poettering 69c59f707c terminal-util: modernize vtnr_from_tty() a bit 2024-11-23 00:18:46 +01:00
Lennart Poettering ccfdaa7117 sd-login: make use of getpeerpidref() and cg_pidref_get_*() 2024-11-23 00:18:46 +01:00
Lennart Poettering 32f31fa6bf socket-util: introduce getpeerpidref()
This combines getpeercred() and getpeerpidfd() and returns a PidRef
2024-11-23 00:18:46 +01:00
Lennart Poettering eb87a43e9d cgroup-util: add pidref counterparts for cg_pid_get_session() + cg_pid_get_owner_uid() 2024-11-23 00:18:46 +01:00
Lennart Poettering 05e4039436 tree-wide: use pidref_is_self() at more places 2024-11-23 00:18:46 +01:00
Lennart Poettering b9e124b84c sd-json: add json_dispatch_const_path() helper
The new json_dispatch_const_path() is to json_dispatch_path() what
sd_json_dispatch_const_string() is to sd_json_dispatch_ string(), i.e.
doesn't implicitly strdup() the string, but gives you the pointer into
the JSON structure, and thus requires you to keep it pinned.
2024-11-23 00:18:46 +01:00
Lennart Poettering eb543b192e pam_systemd: introduce pam_get_data_many() helper and make use of it
This is to pam_get_data() what pam_get_item() is to pam_get_item_many().
2024-11-23 00:18:46 +01:00
Lennart Poettering 19159b60c9 pam_systemd: fix error code confusion when prepping D-Bus message
We got confused by the error codes here, and sometimes return PAM errors
where the caller propagated them unconverted as negative errno errors. Fix that.
2024-11-23 00:18:46 +01:00
Lennart Poettering b5f653430e pam_systemd: split pam_sm_open_session() into more digestable blocks
Let's separate four different parts of pam_sm_open_session():

1. Acquiring of our various parameters from pam env, pam data, pam items
2. Mangling of that data to clean it up
3. Registering of the service with logind
4. Importing shell credentials into environment variables
5. Enforcement of user record data

This makes the code a lot more readable, and gets rid of an ugly got
label.

It also corrects things: if step 3 doesnt work because logind is not
around, we'll now still do step 4, which we previously erroneously
skipped.

Besodes that no real code changes.
2024-11-23 00:18:46 +01:00
Lennart Poettering 293efc0a01 pam_systemd: split out setting of shell env vars from credentials and move it later
Let's shorten the code of pam_sm_open_session() a bit, and also make
sure the importing of the env vars from the creds also happens if the
session registration with logind is skipped.
2024-11-23 00:18:46 +01:00
Lennart Poettering a14cc13650 pam_systemd: drop "uid" field from SessionContext
Let's instead just pass over the UserRecord, it's a much more useful
object with lots more information we'll sooner or later need
(preparation for later commits).
2024-11-23 00:18:46 +01:00
Lennart Poettering 796c8d3ecf pam_systemd: drop "pid" field from SessionContext
We never use the field and this is not going to change...

This addresses a weird asymmetry, as create_session_message() always
went to the process' own PID when doing pidfds but otherwise (i.e.
without pidfds) would honour the PID specified as function parameter.
2024-11-23 00:18:46 +01:00
Lennart Poettering dea25e026f pam-systemd: normalize parsing of XDG_VTNR
Let's make it more like the parsing of the "incomplete" boolean env var,
to streamline things.
2024-11-23 00:18:46 +01:00
33 changed files with 1549 additions and 583 deletions

View File

@ -1418,6 +1418,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;
@ -1455,6 +1475,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;

View File

@ -280,7 +280,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);

View File

@ -429,7 +429,7 @@ int pidref_wait(const PidRef *pidref, siginfo_t *ret, int options) {
if (pidref_is_remote(pidref))
return -EREMOTE;
if (pidref->pid == 1 || pidref->pid == getpid_cached())
if (pidref->pid == 1 || pidref_is_self(pidref))
return -ECHILD;
siginfo_t si = {};

View File

@ -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,

View File

@ -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,

View File

@ -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) {

View File

@ -157,3 +157,7 @@ int terminal_is_pty_fd(int fd);
int pty_open_peer_racefree(int fd, int mode);
int pty_open_peer(int fd, int mode);
static inline bool vtnr_is_valid(unsigned n) {
return n >= 1 && n <= 63;
}

View File

@ -81,7 +81,7 @@ static void *server(void *p) {
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
assert_se(pidref_set_pidfd_take(&pidref, pidfd) >= 0);
assert_se(pidref.pid == getpid_cached());
assert_se(pidref_is_self(&pidref));
}
const gid_t *gl = NULL;

View File

@ -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;
}

View File

@ -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);

View File

@ -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) {

View File

@ -1698,7 +1698,8 @@ _public_ int sd_varlink_get_events(sd_varlink *v) {
ret |= EPOLLIN;
if (!v->write_disconnected &&
v->output_buffer_size > 0)
(v->output_queue ||
v->output_buffer_size > 0))
ret |= EPOLLOUT;
return ret;
@ -3218,7 +3219,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)
@ -3399,6 +3406,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. */

View File

@ -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,24 @@ 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);
assert(m);
assert(pidref_is_set(leader));
assert(ret_id);
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);
}
/* 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)
return r;
if (leader.pid == 1 || leader.pid == getpid_cached())
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid leader PID");
if (isempty(type))
t = _SESSION_TYPE_INVALID;
log_debug_errno(r, "Failed to read audit session ID of process " PID_FMT ", ignoring: %m", leader->pid);
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 */
if (asprintf(&id, "%"PRIu32, audit_id) < 0)
return -ENOMEM;
@ -1017,12 +854,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 +866,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 +966,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 +1027,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 +1218,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 +1226,6 @@ fail:
if (session)
session_add_to_gc_queue(session);
if (user)
user_add_to_gc_queue(user);
return r;
}
@ -1175,9 +1260,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 +1308,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,

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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",

View File

@ -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");
}

383
src/login/logind-varlink.c Normal file
View File

@ -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, SD_JSON_BUILD_EMPTY_OBJECT);
}
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);
}

View File

@ -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);

View File

@ -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"
@ -153,6 +154,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);
@ -1114,6 +1117,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)

View File

@ -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"
@ -145,6 +146,8 @@ struct Manager {
struct stat efi_loader_entry_one_shot_stat;
CalendarSpec *maintenance_time;
sd_varlink_server *varlink_server;
};
void manager_reset_config(Manager *m);

View File

@ -26,6 +26,7 @@ liblogind_core_sources = files(
'logind-session.c',
'logind-user-dbus.c',
'logind-user.c',
'logind-varlink.c',
'logind-wall.c',
)

File diff suppressed because it is too large Load Diff

View File

@ -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',

View File

@ -253,17 +253,17 @@ int pam_get_item_many_internal(pam_handle_t *handle, ...) {
va_list ap;
int r;
assert(handle);
va_start(ap, handle);
for (;;) {
int item_type = va_arg(ap, int);
if (item_type <= 0) {
r = PAM_SUCCESS;
break;
}
const void **value = ASSERT_PTR(va_arg(ap, const void **));
r = pam_get_item(handle, item_type, value);
if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
break;
@ -273,6 +273,30 @@ int pam_get_item_many_internal(pam_handle_t *handle, ...) {
return r;
}
int pam_get_data_many_internal(pam_handle_t *handle, ...) {
va_list ap;
int r;
assert(handle);
va_start(ap, handle);
for (;;) {
const char *data_name = va_arg(ap, const char *);
if (!data_name) {
r = PAM_SUCCESS;
break;
}
const void **value = ASSERT_PTR(va_arg(ap, const void **));
r = pam_get_data(handle, data_name, value);
if (!IN_SET(r, PAM_NO_MODULE_DATA, PAM_SUCCESS))
break;
}
va_end(ap);
return r;
}
int pam_prompt_graceful(pam_handle_t *handle, int style, char **ret_response, const char *fmt, ...) {
va_list args;
int r;

View File

@ -44,7 +44,9 @@ int pam_get_bus_data(pam_handle_t *handle, const char *module_name, PamBusData *
void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status);
int pam_get_item_many_internal(pam_handle_t *handle, ...);
#define pam_get_item_many(handle, ...) pam_get_item_many_internal(handle, __VA_ARGS__, -1)
int pam_get_data_many_internal(pam_handle_t *handle, ...);
#define pam_get_data_many(handle, ...) pam_get_data_many_internal(handle, __VA_ARGS__, NULL)
int pam_prompt_graceful(pam_handle_t *handle, int style, char **ret_response, const char *fmt, ...) _printf_(4,5);

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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"
@ -193,6 +194,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);
}

View File

@ -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