Compare commits

...

33 Commits

Author SHA1 Message Date
Lennart Poettering 1a4f02badb
Merge 4baee2d732 into c946b13575 2024-11-23 02:40:57 +08:00
Christian Hesse c946b13575 link README.logs from tmpfiles.d/legacy.conf only if available
The file README.logs is installed only if SysVInit support is enabled.
Thus the link should depend on it as well.
2024-11-22 18:33:20 +00:00
Lennart Poettering e39cbb1442 varlink: apparently on old kernels SO_PEERPIDFD returns EINVAL 2024-11-23 03:09:49 +09:00
Marco Tomaschett bc4a027f9c
hwdb: add support for PineTab2 to 60-sensor.hwdb (#35304)
Add accelerometer support for PineTab2
2024-11-23 03:08:06 +09:00
Lennart Poettering d209e197f8
userdbctl: two trivial fixlets (#35296)
Fixes: #35294
2024-11-22 16:06:01 +01:00
Antonio Alvarez Feijoo 9ed090230e tpm2-util: fix parameter name 2024-11-22 16:04:16 +01:00
Lennart Poettering 4baee2d732 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-22 12:22:47 +01:00
Lennart Poettering 81d96882c7 logind: add basic Varlink API
For now this only covers CreateSession() and ReleaseSession(), i.e. the
two operations pam_systemd cares about.
2024-11-22 12:22:47 +01:00
Lennart Poettering b52d094f36 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-22 12:22:47 +01:00
Lennart Poettering 4801535168 logind: also potentially GC the session if we cannot send reply 2024-11-22 12:22:47 +01:00
Lennart Poettering 674d6d5f3f logind: indicate that 'error' parameter is input by making it const 2024-11-22 12:22:47 +01:00
Lennart Poettering 104ea6f13a 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-22 12:22:47 +01:00
Lennart Poettering 8ff54135a8 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-22 12:22:47 +01:00
Lennart Poettering 2a356c976a logind: normalize parameter to create_session()
We can pass a properly typed Manager object here, no reason to pass it
as void*.
2024-11-22 12:22:47 +01:00
Lennart Poettering 756654b230 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-22 12:22:47 +01:00
Lennart Poettering 7900a06e48 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-22 12:22:47 +01:00
Lennart Poettering 2eeb395d2f terminal-util: modernize vtnr_from_tty() a bit 2024-11-22 12:22:47 +01:00
Lennart Poettering 4c0268ffcd sd-login: make use of getpeerpidref() and cg_pidref_get_*() 2024-11-22 12:22:47 +01:00
Lennart Poettering 43b28e046b socket-util: introduce getpeerpidref()
This combines getpeercred() and getpeerpidfd() and returns a PidRef
2024-11-22 12:22:47 +01:00
Lennart Poettering 8437948063 cgroup-util: add pidref counterparts for cg_pid_get_session() + cg_pid_get_owner_uid() 2024-11-22 12:22:47 +01:00
Lennart Poettering 71e6c1643a tree-wide: use pidref_is_self() at more places 2024-11-22 12:22:47 +01:00
Lennart Poettering 8c4474092c 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-22 12:22:47 +01:00
Lennart Poettering a4b0cdbb5f 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-22 12:22:47 +01:00
Lennart Poettering 4be09a13ba 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-22 12:22:47 +01:00
Lennart Poettering a7280afc81 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-22 12:22:47 +01:00
Lennart Poettering 9ab9a982b2 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-22 12:22:47 +01:00
Lennart Poettering 2c073ccdba 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-22 12:22:47 +01:00
Lennart Poettering a6eccef309 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-22 12:22:47 +01:00
Lennart Poettering 1dfb7c16f0 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-22 12:22:47 +01:00
Lennart Poettering 604a4389fd varlink: apparently on old kernels SO_PEERPIDFD returns EINVAL 2024-11-22 12:17:50 +01:00
Lennart Poettering 47c5ca237b userdbctl: respect selected disposition also when showing gid boundaries
Follow-up for: ad5de3222f
2024-11-22 11:28:30 +01:00
Lennart Poettering 7f8a4f12df userdbctl: fix counting
Fixes: #35294
2024-11-22 11:28:28 +01:00
Lennart Poettering e412fc5e04 userbdctl: show 'mapped' user range only inside of userns
Outside of userns the concept makes no sense, there cannot be users
mapped from further outside.
2024-11-22 11:28:17 +01:00
38 changed files with 1587 additions and 590 deletions

View File

@ -953,6 +953,15 @@ sensor:modalias:acpi:MXC6655*:dmi:*:svnDefaultstring*:pnP612F:*
sensor:modalias:acpi:SMO8500*:dmi:*:svnPEAQ:pnPEAQPMMC1010MD99187:*
ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, 1, 0; 0, 0, 1
#########################################
# Pine64
#########################################
# PineTab2
sensor:modalias:of:NaccelerometerT_null_Csilan,sc7a20:*
ACCEL_MOUNT_MATRIX=0, 0, -1; 1, 0, 0; 0, -1, 0
#########################################
# Pipo
#########################################

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

@ -16,7 +16,7 @@ int varlink_get_peer_pidref(sd_varlink *v, PidRef *ret) {
int pidfd = sd_varlink_get_peer_pidfd(v);
if (pidfd < 0) {
if (!ERRNO_IS_NEG_NOT_SUPPORTED(pidfd))
if (!ERRNO_IS_NEG_NOT_SUPPORTED(pidfd) && pidfd != -EINVAL)
return pidfd;
pid_t pid;

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), 0 },
{ "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',
)

View File

@ -18,6 +18,9 @@
#include <sys/types.h>
#include <unistd.h>
#include "sd-bus.h"
#include "sd-varlink.h"
#include "alloc-util.h"
#include "audit-util.h"
#include "bus-common-errors.h"
@ -35,6 +38,7 @@
#include "format-util.h"
#include "fs-util.h"
#include "hostname-util.h"
#include "json-util.h"
#include "locale-util.h"
#include "login-util.h"
#include "macro.h"
@ -390,116 +394,108 @@ static int export_legacy_dbus_address(
}
static int append_session_memory_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
uint64_t val;
int r;
assert(handle);
assert(m);
if (isempty(limit))
return PAM_SUCCESS;
return 0;
if (streq(limit, "infinity")) {
r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", UINT64_MAX);
if (r < 0)
return pam_bus_log_create_error(handle, r);
return PAM_SUCCESS;
}
if (streq(limit, "infinity"))
return sd_bus_message_append(m, "(sv)", "MemoryMax", "t", UINT64_MAX);
r = parse_permyriad(limit);
if (r >= 0) {
r = sd_bus_message_append(m, "(sv)", "MemoryMaxScale", "u", UINT32_SCALE_FROM_PERMYRIAD(r));
if (r < 0)
return pam_bus_log_create_error(handle, r);
return PAM_SUCCESS;
}
if (r < 0) {
uint64_t val;
r = parse_size(limit, 1024, &val);
if (r >= 0) {
r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", val);
if (r < 0)
return pam_bus_log_create_error(handle, r);
return PAM_SUCCESS;
}
if (r < 0) {
pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.memory_max, ignoring: %s", limit);
return PAM_SUCCESS;
}
return sd_bus_message_append(m, "(sv)", "MemoryMax", "t", val);
}
return sd_bus_message_append(m, "(sv)", "MemoryMaxScale", "u", UINT32_SCALE_FROM_PERMYRIAD(r));
}
static int append_session_runtime_max_sec(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
usec_t val;
int r;
assert(handle);
assert(m);
/* No need to parse "infinity" here, it will be set by default later in scope_init() */
if (isempty(limit) || streq(limit, "infinity"))
return PAM_SUCCESS;
return 0;
usec_t val;
r = parse_sec(limit, &val);
if (r >= 0) {
r = sd_bus_message_append(m, "(sv)", "RuntimeMaxUSec", "t", (uint64_t) val);
if (r < 0)
return pam_bus_log_create_error(handle, r);
} else
if (r < 0) {
pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.runtime_max_sec: %s, ignoring.", limit);
return 0;
}
return PAM_SUCCESS;
return sd_bus_message_append(m, "(sv)", "RuntimeMaxUSec", "t", (uint64_t) val);
}
static int append_session_tasks_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
uint64_t val;
int r;
assert(handle);
assert(m);
/* No need to parse "infinity" here, it will be set unconditionally later in manager_start_scope() */
if (isempty(limit) || streq(limit, "infinity"))
return PAM_SUCCESS;
return 0;
uint64_t val;
r = safe_atou64(limit, &val);
if (r >= 0) {
r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", val);
if (r < 0)
return pam_bus_log_create_error(handle, r);
} else
if (r < 0) {
pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.tasks_max, ignoring: %s", limit);
return 0;
}
return PAM_SUCCESS;
return sd_bus_message_append(m, "(sv)", "TasksMax", "t", val);
}
static int append_session_cpu_weight(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
uint64_t val;
int r;
if (isempty(limit))
return PAM_SUCCESS;
assert(handle);
assert(m);
if (isempty(limit))
return 0;
uint64_t val;
r = cg_cpu_weight_parse(limit, &val);
if (r < 0)
if (r < 0) {
pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.cpu_weight, ignoring: %s", limit);
else {
r = sd_bus_message_append(m, "(sv)", "CPUWeight", "t", val);
if (r < 0)
return pam_bus_log_create_error(handle, r);
return 0;
}
return PAM_SUCCESS;
return sd_bus_message_append(m, "(sv)", "CPUWeight", "t", val);
}
static int append_session_io_weight(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
uint64_t val;
int r;
if (isempty(limit))
return PAM_SUCCESS;
assert(handle);
assert(m);
if (isempty(limit))
return 0;
uint64_t val;
r = cg_weight_parse(limit, &val);
if (r < 0)
if (r < 0) {
pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.io_weight, ignoring: %s", limit);
else {
r = sd_bus_message_append(m, "(sv)", "IOWeight", "t", val);
if (r < 0)
return pam_bus_log_create_error(handle, r);
return 0;
}
return PAM_SUCCESS;
return sd_bus_message_append(m, "(sv)", "IOWeight", "t", val);
}
static const char* getenv_harder(pam_handle_t *handle, const char *key, const char *fallback) {
@ -549,6 +545,26 @@ static bool getenv_harder_bool(pam_handle_t *handle, const char *key, bool fallb
return r;
}
static uint32_t getenv_harder_uint32(pam_handle_t *handle, const char *key, uint32_t fallback) {
int r;
assert(handle);
assert(key);
const char *v = getenv_harder(handle, key, NULL);
if (isempty(v))
return fallback;
uint32_t u;
r = safe_atou32(v, &u);
if (r < 0) {
pam_syslog(handle, LOG_ERR, "Unsigned integer environment variable value of '%s' is not valid: %s", key, v);
return fallback;
}
return u;
}
static int update_environment(pam_handle_t *handle, const char *key, const char *value) {
int r;
@ -826,17 +842,15 @@ static uint64_t pick_default_capability_ambient_set(
}
typedef struct SessionContext {
const uid_t uid;
const pid_t pid;
const char *service;
const char *type;
const char *class;
const char *desktop;
const char *seat;
const uint32_t vtnr;
uint32_t vtnr;
const char *tty;
const char *display;
const bool remote;
bool remote;
const char *remote_user;
const char *remote_host;
const char *memory_max;
@ -844,11 +858,13 @@ typedef struct SessionContext {
const char *cpu_weight;
const char *io_weight;
const char *runtime_max_sec;
bool incomplete;
} SessionContext;
static int create_session_message(
sd_bus *bus,
pam_handle_t *handle,
UserRecord *ur,
const SessionContext *context,
bool avoid_pidfd,
sd_bus_message **ret) {
@ -859,6 +875,7 @@ static int create_session_message(
assert(bus);
assert(handle);
assert(ur);
assert(context);
assert(ret);
@ -872,10 +889,11 @@ static int create_session_message(
if (r < 0)
return r;
r = sd_bus_message_append(m,
r = sd_bus_message_append(
m,
pidfd >= 0 ? "uhsssssussbss" : "uusssssussbss",
(uint32_t) context->uid,
pidfd >= 0 ? pidfd : context->pid,
(uint32_t) ur->uid,
pidfd >= 0 ? pidfd : 0,
context->service,
context->type,
context->class,
@ -901,23 +919,23 @@ static int create_session_message(
return r;
r = append_session_memory_max(handle, m, context->memory_max);
if (r != PAM_SUCCESS)
if (r < 0)
return r;
r = append_session_runtime_max_sec(handle, m, context->runtime_max_sec);
if (r != PAM_SUCCESS)
if (r < 0)
return r;
r = append_session_tasks_max(handle, m, context->tasks_max);
if (r != PAM_SUCCESS)
if (r < 0)
return r;
r = append_session_cpu_weight(handle, m, context->cpu_weight);
if (r != PAM_SUCCESS)
if (r < 0)
return r;
r = append_session_io_weight(handle, m, context->io_weight);
if (r != PAM_SUCCESS)
if (r < 0)
return r;
r = sd_bus_message_close_container(m);
@ -928,211 +946,260 @@ static int create_session_message(
return 0;
}
_public_ PAM_EXTERN int pam_sm_open_session(
static void session_context_mangle(
pam_handle_t *handle,
int flags,
int argc, const char **argv) {
/* 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;
const char
*id, *object_path, *runtime_path,
*service = NULL,
*tty = NULL, *display = NULL,
*remote_user = NULL, *remote_host = NULL,
*seat = NULL,
*type = NULL, *class = NULL,
*class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL, *desktop_pam = NULL,
*memory_max = NULL, *tasks_max = NULL, *cpu_weight = NULL, *io_weight = NULL, *runtime_max_sec = NULL;
uint64_t default_capability_bounding_set = UINT64_MAX, default_capability_ambient_set = UINT64_MAX;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
int session_fd = -EBADF, existing, r;
bool debug = false, remote, incomplete;
uint32_t vtnr = 0;
uid_t original_uid;
SessionContext *c,
UserRecord *ur,
bool debug) {
assert(handle);
assert(c);
assert(ur);
pam_log_setup();
if (parse_argv(handle,
argc, argv,
&class_pam,
&type_pam,
&desktop_pam,
&debug,
&default_capability_bounding_set,
&default_capability_ambient_set) < 0)
return PAM_SESSION_ERR;
pam_debug_syslog(handle, debug, "pam-systemd initializing");
r = acquire_user_record(handle, &ur);
if (r != PAM_SUCCESS)
return r;
/* Make most of this a NOP on non-logind systems */
if (!logind_running())
goto success;
r = pam_get_item_many(
handle,
PAM_SERVICE, &service,
PAM_XDISPLAY, &display,
PAM_TTY, &tty,
PAM_RUSER, &remote_user,
PAM_RHOST, &remote_host);
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@");
seat = getenv_harder(handle, "XDG_SEAT", NULL);
cvtnr = getenv_harder(handle, "XDG_VTNR", NULL);
type = getenv_harder(handle, "XDG_SESSION_TYPE", type_pam);
class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam);
desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam);
incomplete = getenv_harder_bool(handle, "XDG_SESSION_INCOMPLETE", false);
if (streq_ptr(service, "systemd-user")) {
if (streq_ptr(c->service, "systemd-user")) {
/* If we detect that we are running in the "systemd-user" PAM stack, then let's patch the class to
* 'manager' if not set, simply for robustness reasons. */
type = "unspecified";
class = IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) ?
c->type = "unspecified";
c->class = IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) ?
"manager-early" : "manager";
tty = NULL;
c->tty = NULL;
} else if (tty && strchr(tty, ':')) {
} else if (c->tty && strchr(c->tty, ':')) {
/* A tty with a colon is usually an X11 display, placed there to show up in utmp. We rearrange things
* and don't pretend that an X display was a tty. */
if (isempty(display))
display = tty;
tty = NULL;
if (isempty(c->display))
c->display = c->tty;
c->tty = NULL;
} else if (streq_ptr(tty, "cron")) {
} else if (streq_ptr(c->tty, "cron")) {
/* cron is setting PAM_TTY to "cron" for some reason (the commit carries no information why, but
* probably because it wants to set it to something as pam_time/pam_access/ require PAM_TTY to be set
* (as they otherwise even try to update it!) but cron doesn't actually allocate a TTY for its forked
* off processes.) */
type = "unspecified";
class = "background";
tty = NULL;
c->type = "unspecified";
c->class = "background";
c->tty = NULL;
} else if (streq_ptr(tty, "ssh")) {
} else if (streq_ptr(c->tty, "ssh")) {
/* ssh has been setting PAM_TTY to "ssh" (for the same reason as cron does this, see above. For further
* details look for "PAM_TTY_KLUDGE" in the openssh sources). */
type = "tty";
class = "user";
tty = NULL; /* This one is particularly sad, as this means that ssh sessions — even though usually
* associated with a pty won't be tracked by their tty in logind. This is because ssh
* does the PAM session registration early for new connections, and registers a pty only
* much later (this is because it doesn't know yet if it needs one at all, as whether to
* register a pty or not is negotiated much later in the protocol). */
c->type = "tty";
c->class = "user";
c->tty = NULL; /* This one is particularly sad, as this means that ssh sessions — even though
* usually associated with a pty won't be tracked by their tty in
* logind. This is because ssh does the PAM session registration early for new
* connections, and registers a pty only much later (this is because it doesn't
* know yet if it needs one at all, as whether to register a pty or not is
* negotiated much later in the protocol). */
} else if (tty)
} else if (c->tty)
/* Chop off leading /dev prefix that some clients specify, but others do not. */
tty = skip_dev_prefix(tty);
c->tty = skip_dev_prefix(c->tty);
/* If this fails vtnr will be 0, that's intended */
if (!isempty(cvtnr))
(void) safe_atou32(cvtnr, &vtnr);
if (!isempty(display) && !vtnr) {
if (isempty(seat))
(void) get_seat_from_display(display, &seat, &vtnr);
else if (streq(seat, "seat0"))
(void) get_seat_from_display(display, NULL, &vtnr);
if (!isempty(c->display) && !c->vtnr) {
if (isempty(c->seat))
(void) get_seat_from_display(c->display, &c->seat, &c->vtnr);
else if (streq(c->seat, "seat0"))
(void) get_seat_from_display(c->display, /* seat= */ NULL, &c->vtnr);
}
if (seat && !streq(seat, "seat0") && vtnr != 0) {
pam_debug_syslog(handle, debug, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
vtnr = 0;
if (c->seat && !streq(c->seat, "seat0") && c->vtnr != 0) {
pam_debug_syslog(handle, debug, "Ignoring vtnr %"PRIu32" for %s which is not seat0", c->vtnr, c->seat);
c->vtnr = 0;
}
if (isempty(type))
type = !isempty(display) ? "x11" :
!isempty(tty) ? "tty" : "unspecified";
if (isempty(c->type))
c->type = !isempty(c->display) ? "x11" :
!isempty(c->tty) ? "tty" : "unspecified";
if (isempty(class))
class = streq(type, "unspecified") ? "background" :
if (isempty(c->class))
c->class = streq(c->type, "unspecified") ? "background" :
((IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) &&
streq(type, "tty")) ? "user-early" : "user");
streq(c->type, "tty")) ? "user-early" : "user");
if (incomplete) {
if (streq(class, "user"))
class = "user-incomplete";
if (c->incomplete) {
if (streq(c->class, "user"))
c->class = "user-incomplete";
else
pam_syslog_pam_error(handle, LOG_WARNING, 0, "PAM session of class '%s' is incomplete, which is not supported, ignoring.", class);
pam_syslog_pam_error(handle, LOG_WARNING, 0, "PAM session of class '%s' is incomplete, which is not supported, ignoring.", c->class);
}
remote = !isempty(remote_host) && !is_localhost(remote_host);
c->remote = !isempty(c->remote_host) && !is_localhost(c->remote_host);
}
r = pam_get_data(handle, "systemd.memory_max", (const void **)&memory_max);
if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.memory_max data: @PAMERR@");
r = pam_get_data(handle, "systemd.tasks_max", (const void **)&tasks_max);
if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.tasks_max data: @PAMERR@");
r = pam_get_data(handle, "systemd.cpu_weight", (const void **)&cpu_weight);
if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.cpu_weight data: @PAMERR@");
r = pam_get_data(handle, "systemd.io_weight", (const void **)&io_weight);
if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.io_weight data: @PAMERR@");
r = pam_get_data(handle, "systemd.runtime_max_sec", (const void **)&runtime_max_sec);
if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.runtime_max_sec data: @PAMERR@");
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,
UserRecord *ur,
bool debug,
char **ret_seat) {
int r;
assert(handle);
assert(c);
assert(ur);
assert(ret_seat);
/* Make most of this a NOP on non-logind systems */
if (!logind_running()) {
*ret_seat = NULL;
return PAM_SUCCESS;
}
pam_debug_syslog(handle, debug,
"Asking logind to create session: "
"uid="UID_FMT" pid="PID_FMT" service=%s type=%s class=%s desktop=%s seat=%s vtnr=%"PRIu32" tty=%s display=%s remote=%s remote_user=%s remote_host=%s",
ur->uid, getpid_cached(),
strempty(c->service),
c->type, c->class, strempty(c->desktop),
strempty(c->seat), c->vtnr, strempty(c->tty), strempty(c->display),
yes_no(c->remote), strempty(c->remote_user), strempty(c->remote_host));
pam_debug_syslog(handle, debug,
"Session limits: "
"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));
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; /* the following variables point into this message, hence pin it for longer */
_cleanup_(sd_json_variant_unrefp) sd_json_variant *vreply = NULL; /* similar */
_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;
bool done = false;
if (can_use_varlink(c)) {
r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.Login");
if (r < 0)
log_debug_errno(r, "Failed to connect to logind via Varlink, falling back to D-Bus: %m");
else {
r = sd_varlink_set_allow_fd_passing_input(vl, true);
if (r < 0)
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to enable input fd passing on Varlink socket: %m");
r = sd_varlink_set_allow_fd_passing_output(vl, true);
if (r < 0)
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to enable output fd passing on Varlink socket: %m");
r = sd_varlink_set_relative_timeout(vl, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC);
if (r < 0)
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to set relative timeout on Varlink socket: %m");
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
r = pidref_set_self(&pidref);
if (r < 0)
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to acquire PID reference on ourselves: %m");
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;
pam_debug_syslog(handle, debug,
"Asking logind to create session: "
"uid="UID_FMT" pid="PID_FMT" service=%s type=%s class=%s desktop=%s seat=%s vtnr=%"PRIu32" tty=%s display=%s remote=%s remote_user=%s remote_host=%s",
ur->uid, getpid_cached(),
strempty(service),
type, class, strempty(desktop),
strempty(seat), vtnr, strempty(tty), strempty(display),
yes_no(remote), strempty(remote_user), strempty(remote_host));
pam_debug_syslog(handle, debug,
"Session limits: "
"memory_max=%s tasks_max=%s cpu_weight=%s io_weight=%s runtime_max_sec=%s",
strna(memory_max), strna(tasks_max), strna(cpu_weight), strna(io_weight), strna(runtime_max_sec));
const SessionContext context = {
.uid = ur->uid,
.pid = 0,
.service = service,
.type = type,
.class = class,
.desktop = desktop,
.seat = seat,
.vtnr = vtnr,
.tty = tty,
.display = display,
.remote = remote,
.remote_user = remote_user,
.remote_host = remote_host,
.memory_max = memory_max,
.tasks_max = tasks_max,
.cpu_weight = cpu_weight,
.io_weight = io_weight,
.runtime_max_sec = runtime_max_sec,
};
r = create_session_message(bus,
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
r = create_session_message(
bus,
handle,
&context,
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 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
sd_bus_error_free(&error);
@ -1142,7 +1209,8 @@ _public_ PAM_EXTERN int pam_sm_open_session(
m = sd_bus_message_unref(m);
r = create_session_message(bus,
handle,
&context,
ur,
c,
/* avoid_pidfd = */ true,
&m);
if (r < 0)
@ -1155,7 +1223,8 @@ _public_ PAM_EXTERN int pam_sm_open_session(
/* We are already in a session, don't do anything */
pam_debug_syslog(handle, debug,
"Not creating session: %s", bus_error_message(&error, r));
goto success;
*ret_seat = NULL;
return PAM_SUCCESS;
}
pam_syslog(handle, LOG_ERR,
@ -1163,23 +1232,25 @@ _public_ PAM_EXTERN int pam_sm_open_session(
return PAM_SESSION_ERR;
}
r = sd_bus_message_read(reply,
r = sd_bus_message_read(
reply,
"soshusub",
&id,
&object_path,
&runtime_path,
&session_fd,
&original_uid,
&seat,
&vtnr,
&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, seat, 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. */
@ -1202,38 +1273,25 @@ _public_ PAM_EXTERN int pam_sm_open_session(
* somewhere else (for example PAM module parameters). Let's now update the environment variables, so that this
* data is inherited into the session processes, and programs can rely on them to be initialized. */
r = update_environment(handle, "XDG_SESSION_TYPE", type);
r = update_environment(handle, "XDG_SESSION_TYPE", c->type);
if (r != PAM_SUCCESS)
return r;
r = update_environment(handle, "XDG_SESSION_CLASS", class);
r = update_environment(handle, "XDG_SESSION_CLASS", c->class);
if (r != PAM_SUCCESS)
return r;
r = update_environment(handle, "XDG_SESSION_DESKTOP", desktop);
r = update_environment(handle, "XDG_SESSION_DESKTOP", c->desktop);
if (r != PAM_SUCCESS)
return r;
r = update_environment(handle, "XDG_SEAT", seat);
r = update_environment(handle, "XDG_SEAT", real_seat);
if (r != PAM_SUCCESS)
return r;
static const char *const propagate[] = {
"shell.prompt.prefix", "SHELL_PROMPT_PREFIX",
"shell.prompt.suffix", "SHELL_PROMPT_SUFFIX",
"shell.welcome", "SHELL_WELCOME",
NULL
};
STRV_FOREACH_PAIR(k, v, propagate) {
r = propagate_credential_to_environment(handle, *k, *v);
if (r != PAM_SUCCESS)
return r;
}
if (vtnr > 0) {
char buf[DECIMAL_STR_MAX(vtnr)];
sprintf(buf, "%u", vtnr);
if (real_vtnr > 0) {
char buf[DECIMAL_STR_MAX(real_vtnr)];
sprintf(buf, "%u", real_vtnr);
r = update_environment(handle, "XDG_VTNR", buf);
if (r != PAM_SUCCESS)
@ -1255,9 +1313,114 @@ _public_ PAM_EXTERN int pam_sm_open_session(
TAKE_FD(fd);
}
success:
/* 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 = 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;
}
static int import_shell_credentials(pam_handle_t *handle) {
static const char *const propagate[] = {
"shell.prompt.prefix", "SHELL_PROMPT_PREFIX",
"shell.prompt.suffix", "SHELL_PROMPT_SUFFIX",
"shell.welcome", "SHELL_WELCOME",
NULL
};
int r;
assert(handle);
STRV_FOREACH_PAIR(k, v, propagate) {
r = propagate_credential_to_environment(handle, *k, *v);
if (r != PAM_SUCCESS)
return r;
}
return PAM_SUCCESS;
}
_public_ PAM_EXTERN int pam_sm_open_session(
pam_handle_t *handle,
int flags,
int argc, const char **argv) {
int r;
assert(handle);
pam_log_setup();
uint64_t default_capability_bounding_set = UINT64_MAX, default_capability_ambient_set = UINT64_MAX;
const char *class_pam = NULL, *type_pam = NULL, *desktop_pam = NULL;
bool debug = false;
if (parse_argv(handle,
argc, argv,
&class_pam,
&type_pam,
&desktop_pam,
&debug,
&default_capability_bounding_set,
&default_capability_ambient_set) < 0)
return PAM_SESSION_ERR;
pam_debug_syslog(handle, debug, "pam-systemd initializing");
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
r = acquire_user_record(handle, &ur);
if (r != PAM_SUCCESS)
return r;
SessionContext c = {};
r = pam_get_item_many(
handle,
PAM_SERVICE, &c.service,
PAM_XDISPLAY, &c.display,
PAM_TTY, &c.tty,
PAM_RUSER, &c.remote_user,
PAM_RHOST, &c.remote_host);
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@");
c.seat = getenv_harder(handle, "XDG_SEAT", NULL);
c.vtnr = getenv_harder_uint32(handle, "XDG_VTNR", 0);
c.type = getenv_harder(handle, "XDG_SESSION_TYPE", type_pam);
c.class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam);
c.desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam);
c.incomplete = getenv_harder_bool(handle, "XDG_SESSION_INCOMPLETE", false);
r = pam_get_data_many(
handle,
"systemd.memory_max", &c.memory_max,
"systemd.tasks_max", &c.tasks_max,
"systemd.cpu_weight", &c.cpu_weight,
"systemd.io_weight", &c.io_weight,
"systemd.runtime_max_sec", &c.runtime_max_sec);
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM data: @PAMERR@");
session_context_mangle(handle, &c, ur, debug);
_cleanup_free_ char *seat_buffer = NULL;
r = register_session(handle, &c, ur, debug, &seat_buffer);
if (r != PAM_SUCCESS)
return r;
r = import_shell_credentials(handle);
if (r != PAM_SUCCESS)
return r;
if (default_capability_ambient_set == UINT64_MAX)
default_capability_ambient_set = pick_default_capability_ambient_set(ur, service, seat);
default_capability_ambient_set = pick_default_capability_ambient_set(ur, c.service, c.seat);
return apply_user_record_settings(handle, ur, debug, default_capability_bounding_set, default_capability_ambient_set);
}
@ -1297,13 +1460,39 @@ _public_ PAM_EXTERN int pam_sm_close_session(
id = pam_getenv(handle, "XDG_SESSION_ID");
if (id && !existing) {
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
bool done = false;
r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.Login");
if (r < 0)
log_debug_errno(r, "Failed to connect to logind via Varlink, falling back to D-Bus: %m");
else {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *vreply = NULL;
const char *error_id = NULL;
r = sd_varlink_callbo(
vl,
"io.systemd.Login.ReleaseSession",
/* ret_reply= */ NULL,
&error_id,
SD_JSON_BUILD_PAIR_STRING("Id", id));
if (r < 0)
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to register session: %s", error_id);
if (error_id)
return pam_syslog_errno(handle, LOG_ERR, sd_varlink_error_to_errno(error_id, vreply),
"Failed to issue ReleaseSession() varlink call: %s", error_id);
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, NULL);
r = pam_acquire_bus_connection(handle, "pam-systemd", debug, &bus, &d);
if (r != PAM_SUCCESS)
return r;
@ -1312,6 +1501,7 @@ _public_ PAM_EXTERN int pam_sm_close_session(
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
* closed it here it would not have any clue when that is completed. Given that one cannot really

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

@ -392,7 +392,7 @@ int tpm2_make_pcr_json_array(uint32_t pcr_mask, sd_json_variant **ret);
int tpm2_parse_pcr_json_array(sd_json_variant *v, uint32_t *ret);
int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const struct iovec blobs[], size_t n_blobs, const struct iovec policy_hash[], size_t n_policy_hash, const struct iovec *salt, const struct iovec *srk, const struct iovec *pcrlock_nv, TPM2Flags flags, sd_json_variant **ret);
int tpm2_parse_luks2_json(sd_json_variant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec **ret_blobs, size_t *ret_n_blobs, struct iovec **ret_policy_hash, size_t *ret_n_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *pcrlock_nv, TPM2Flags *ret_flags);
int tpm2_parse_luks2_json(sd_json_variant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec **ret_blobs, size_t *ret_n_blobs, struct iovec **ret_policy_hash, size_t *ret_n_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *ret_pcrlock_nv, TPM2Flags *ret_flags);
/* Default to PCR 7 only */
#define TPM2_PCR_INDEX_DEFAULT UINT32_C(7)

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

@ -70,6 +70,8 @@ __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_varlink_server_flags_t) {
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

@ -23,6 +23,7 @@
#include "user-util.h"
#include "userdb.h"
#include "verbs.h"
#include "virt.h"
static enum {
OUTPUT_CLASSIC,
@ -139,10 +140,16 @@ static int show_user(UserRecord *ur, Table *table) {
return 0;
}
static bool test_show_mapped(void) {
/* Show mapped user range only in environments where user mapping is a thing. */
return running_in_userns() > 0;
}
static const struct {
uid_t first, last;
const char *name;
UserDisposition disposition;
bool (*test)(void);
} uid_range_table[] = {
{
.first = 1,
@ -175,11 +182,12 @@ static const struct {
.last = MAP_UID_MAX,
.name = "mapped",
.disposition = USER_REGULAR,
.test = test_show_mapped,
},
};
static int table_add_uid_boundaries(Table *table, const UIDRange *p) {
int r;
int r, n_added = 0;
assert(table);
@ -192,6 +200,9 @@ static int table_add_uid_boundaries(Table *table, const UIDRange *p) {
if (!uid_range_covers(p, i->first, i->last - i->first + 1))
continue;
if (i->test && !i->test())
continue;
name = strjoin(special_glyph(SPECIAL_GLYPH_ARROW_DOWN),
" begin ", i->name, " users ",
special_glyph(SPECIAL_GLYPH_ARROW_DOWN));
@ -249,9 +260,11 @@ static int table_add_uid_boundaries(Table *table, const UIDRange *p) {
TABLE_INT, 1); /* sort after any other entry with the same UID */
if (r < 0)
return table_log_add_error(r);
n_added += 2;
}
return ELEMENTSOF(uid_range_table) * 2;
return n_added;
}
static int add_unavailable_uid(Table *table, uid_t start, uid_t end) {
@ -565,16 +578,22 @@ static int show_group(GroupRecord *gr, Table *table) {
}
static int table_add_gid_boundaries(Table *table, const UIDRange *p) {
int r;
int r, n_added = 0;
assert(table);
FOREACH_ELEMENT(i, uid_range_table) {
_cleanup_free_ char *name = NULL, *comment = NULL;
if (!FLAGS_SET(arg_disposition_mask, UINT64_C(1) << i->disposition))
continue;
if (!uid_range_covers(p, i->first, i->last - i->first + 1))
continue;
if (i->test && !i->test())
continue;
name = strjoin(special_glyph(SPECIAL_GLYPH_ARROW_DOWN),
" begin ", i->name, " groups ",
special_glyph(SPECIAL_GLYPH_ARROW_DOWN));
@ -626,9 +645,11 @@ static int table_add_gid_boundaries(Table *table, const UIDRange *p) {
TABLE_INT, 1); /* sort after any other entry with the same GID */
if (r < 0)
return table_log_add_error(r);
n_added += 2;
}
return ELEMENTSOF(uid_range_table) * 2;
return n_added;
}
static int add_unavailable_gid(Table *table, uid_t start, uid_t end) {

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

View File

@ -13,11 +13,12 @@
d /run/lock 0755 root root -
L /var/lock - - - - ../run/lock
{% if HAVE_SYSV_COMPAT %}
{% if CREATE_LOG_DIRS %}
L$ /var/log/README - - - - ../..{{DOC_DIR}}/README.logs
{% endif %}
{% if HAVE_SYSV_COMPAT %}
# /run/lock/subsys is used for serializing SysV service execution, and
# hence without use on SysV-less systems.
d /run/lock/subsys 0755 root root -