pam_systemd/pam_systemd_home: various fixes (#36505)

This commit is contained in:
Yu Watanabe
2025-02-27 12:37:05 +09:00
committed by GitHub
10 changed files with 234 additions and 105 deletions

5
TODO
View File

@@ -140,6 +140,11 @@ Features:
them under various conditions: 1. if tpm2 is available or not available;
2. if sb is on or off; 3. if we are netbooted or not; …
* logind: invoke a service manager for "area" logins too. i.e. instantiate
user@.service also for logins where XDG_AREA is set, in per-area fashion, and
ref count it properly. Benefit: graphical logins should start working with
the area logic.
* repart: introduce concept of "ghost" partitions, that we setup in almost all
ways like other partitions, but do not actually register in the actual gpt
table, but only tell the kernel about via BLKPG ioctl. These partitions are

View File

@@ -3,14 +3,13 @@
# You really want to adjust this to your local distribution. If you use this
# unmodified you are not building systems safely and securely.
-auth [success=done authtok_err=bad perm_denied=bad maxtries=bad default=ignore] pam_systemd_home.so
auth sufficient pam_unix.so
-auth sufficient pam_systemd_home.so
auth required pam_deny.so
account required pam_nologin.so
-account sufficient pam_systemd_home.so
account sufficient pam_unix.so
account required pam_permit.so
-account [success=done authtok_expired=bad new_authtok_reqd=bad maxtries=bad acct_expired=bad default=ignore] pam_systemd_home.so
account required pam_unix.so
-password sufficient pam_systemd_home.so
password sufficient pam_unix.so sha512 shadow try_first_pass

View File

@@ -476,14 +476,13 @@ pam_set_data(handle, "systemd.runtime_max_sec", (void *)"3600", cleanup);
<filename>systemd-logind.service</filename>:</para>
<programlisting>#%PAM-1.0
-auth [success=done authtok_err=bad perm_denied=bad maxtries=bad default=ignore] pam_systemd_home.so
auth sufficient pam_unix.so
-auth sufficient pam_systemd_home.so
auth required pam_deny.so
account required pam_nologin.so
-account sufficient pam_systemd_home.so
account sufficient pam_unix.so
account required pam_permit.so
-account [success=done authtok_expired=bad new_authtok_reqd=bad maxtries=bad acct_expired=bad default=ignore] pam_systemd_home.so
account required pam_unix.so
-password sufficient pam_systemd_home.so
password sufficient pam_unix.so sha512 shadow try_first_pass

View File

@@ -195,14 +195,13 @@ lennart@zeta$ cp -av /etc/skel ~/Areas/versuch1</programlisting>
<filename>systemd-homed.service</filename> to log in:</para>
<programlisting>#%PAM-1.0
<command>-auth [success=done authtok_err=bad perm_denied=bad maxtries=bad default=ignore] pam_systemd_home.so</command>
auth sufficient pam_unix.so
<command>-auth sufficient pam_systemd_home.so</command>
auth required pam_deny.so
account required pam_nologin.so
<command>-account sufficient pam_systemd_home.so</command>
account sufficient pam_unix.so
account required pam_permit.so
<command>-account [success=done authtok_expired=bad new_authtok_reqd=bad maxtries=bad acct_expired=bad default=ignore] pam_systemd_home.so</command>
account required pam_unix.so
<command>-password sufficient pam_systemd_home.so</command>
password sufficient pam_unix.so sha512 shadow try_first_pass

View File

@@ -77,6 +77,11 @@ int rmdir_parents(const char *path, const char *stop) {
int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
int r;
assert(olddirfd >= 0 || olddirfd == AT_FDCWD);
assert(oldpath);
assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
assert(newpath);
/* Try the ideal approach first */
if (renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE) >= 0)
return 0;

View File

@@ -553,24 +553,24 @@ static int acquire_home(
/* This acquires a reference to a home directory in the following ways:
*
* 1. If please_authenticate is false, it tries to call RefHome() first — which
* will get us a reference to the home without authentication (which will work for homes that are
* not encrypted, or that already are activated). If this works, we are done. Yay!
* 1. If ACQUIRE_MUST_AUTHENTICATE is not set, it tries to call RefHome() first — which will get us a
* reference to the home without authentication (which will work for homes that are not encrypted,
* or that already are activated). If this works, we are done. Yay!
*
* 2. Otherwise, we'll call AcquireHome() — which will try to activate the home getting us a
* reference. If this works, we are done. Yay!
*
* 3. if ref_anyway, we'll call RefHomeUnrestricted() — which will give us a reference in any case
* (even if the activation failed!).
* 3. if ACQUIRE_REF_ANYWAY is set, we'll call RefHomeUnrestricted() — which will give us a reference
* in any case (even if the activation failed!).
*
* The idea is that please_authenticate is set to false for the PAM session hooks (since for those
* authentication doesn't matter), and true for the PAM authentication hooks (since for those
* authentication is essential). And ref_anyway should be set if we are pretty sure that we can later
* activate the home directory via our fallback shell logic, and hence are OK if we can't activate
* things here. Usecase for that are SSH logins where SSH does the authentication and thus only the
* session hooks are called. But from the session hooks SSH doesn't allow asking questions, hence we
* simply allow the login attempt to continue but then invoke our fallback shell that will prompt the
* user for the missing unlock credentials, and then chainload the real shell.
* The idea is that ACQUIRE_MUST_AUTHENTICATE is off for the PAM session hooks (since for those
* authentication doesn't matter), and on for the PAM authentication hooks (since for those
* authentication is essential). And ACQUIRE_REF_ANYWAY should be set if we are pretty sure that we
* can later activate the home directory via our fallback shell logic, and hence are OK if we can't
* activate things here. Usecase for that are SSH logins where SSH does the authentication and thus
* only the session hooks are called. But from the session hooks SSH doesn't allow asking questions,
* hence we simply allow the login attempt to continue but then invoke our fallback shell that will
* prompt the user for the missing unlock credentials, and then chainload the real shell.
*/
r = pam_get_user(handle, &username, NULL);

View File

@@ -54,6 +54,7 @@
#include "stdio-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "tmpfile-util.h"
#include "user-util.h"
#include "userdb.h"
@@ -370,14 +371,14 @@ static int export_legacy_dbus_address(
pam_handle_t *handle,
const char *runtime) {
const char *s;
_cleanup_free_ char *t = NULL;
int r = PAM_BUF_ERR;
int r;
assert(handle);
/* We need to export $DBUS_SESSION_BUS_ADDRESS because various applications will not connect
* correctly to the bus without it. This setting matches what dbus.socket does for the user
* session using 'systemctl --user set-environment'. We want to have the same configuration
* in processes started from the PAM session.
* correctly to the bus without it. This setting matches what dbus.socket does for the user session
* using 'systemctl --user set-environment'. We want to have the same configuration in processes
* started from the PAM session.
*
* The setting of the address is guarded by the access() check because it is also possible to compile
* dbus without --enable-user-session, in which case this socket is not used, and
@@ -386,14 +387,22 @@ static int export_legacy_dbus_address(
* expect the socket to be present by the time we do this check, so we can just as well check once
* here. */
s = strjoina(runtime, "/bus");
if (access(s, F_OK) < 0)
if (!runtime)
return PAM_SUCCESS;
const char *s = strjoina(runtime, "/bus");
if (access(s, F_OK) < 0) {
if (errno != ENOENT)
pam_syslog_errno(handle, LOG_WARNING, errno, "Failed to check if %s/bus exists, ignoring: %m", runtime);
return PAM_SUCCESS;
}
_cleanup_free_ char *t = NULL;
if (asprintf(&t, DEFAULT_USER_BUS_ADDRESS_FMT, runtime) < 0)
return pam_log_oom(handle);
r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", t, 0);
r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", t, /* readonly= */ false);
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set bus variable: @PAMERR@");
@@ -577,12 +586,26 @@ static int update_environment(pam_handle_t *handle, const char *key, const char
assert(handle);
assert(key);
/* Updates the environment, but only if there's actually a value set. Also, log about errors */
/* Updates the environment, and removes environment variables if value is NULL or empty. Also, log
* about errors. */
if (isempty(value)) {
/* Unset the variable if set. Note that pam_putenv() would log nastily behind our back if we
* call it without the variable actually being set. Hence we check explicitly if it's set
* before. */
if (!pam_getenv(handle, key))
return PAM_SUCCESS;
r = pam_putenv(handle, key);
if (!IN_SET(r, PAM_SUCCESS, PAM_BAD_ITEM))
return pam_syslog_pam_error(handle, LOG_WARNING, r,
"Failed to unset %s environment variable: @PAMERR@", key);
if (isempty(value))
return PAM_SUCCESS;
}
r = pam_misc_setenv(handle, key, value, 0);
r = pam_misc_setenv(handle, key, value, /* readonly= */ false);
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r,
"Failed to set environment variable %s: @PAMERR@", key);
@@ -810,27 +833,6 @@ static int apply_user_record_settings(
return PAM_SUCCESS;
}
static int configure_runtime_directory(
pam_handle_t *handle,
UserRecord *ur,
const char *rt) {
int r;
assert(handle);
assert(ur);
assert(rt);
if (!validate_runtime_directory(handle, rt, ur->uid))
return PAM_SUCCESS;
r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set runtime dir: @PAMERR@");
return export_legacy_dbus_address(handle, rt);
}
static uint64_t pick_default_capability_ambient_set(
UserRecord *ur,
const char *service,
@@ -1078,7 +1080,8 @@ static int register_session(
SessionContext *c,
UserRecord *ur,
bool debug,
char **ret_seat) {
char **ret_seat,
char **ret_runtime_dir) {
int r;
@@ -1086,18 +1089,19 @@ static int register_session(
assert(c);
assert(ur);
assert(ret_seat);
assert(ret_runtime_dir);
/* We don't register session class none with logind */
if (streq(c->class, "none")) {
pam_debug_syslog(handle, debug, "Skipping logind registration for session class none.");
*ret_seat = NULL;
*ret_seat = *ret_runtime_dir = NULL;
return PAM_SUCCESS;
}
/* Make most of this a NOP on non-logind systems */
if (!logind_running()) {
pam_debug_syslog(handle, debug, "Skipping logind registration as logind is not running.");
*ret_seat = NULL;
*ret_seat = *ret_runtime_dir = NULL;
return PAM_SUCCESS;
}
@@ -1170,7 +1174,7 @@ static int register_session(
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;
*ret_seat = *ret_runtime_dir = NULL;
return PAM_SUCCESS;
}
if (error_id)
@@ -1267,7 +1271,7 @@ static int register_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));
*ret_seat = NULL;
*ret_seat = *ret_runtime_dir = NULL;
return PAM_SUCCESS;
}
@@ -1303,16 +1307,6 @@ static int register_session(
if (r != PAM_SUCCESS)
return r;
if (original_uid == ur->uid) {
/* Don't set $XDG_RUNTIME_DIR if the user we now authenticated for does not match the
* original user of the session. We do this in order not to result in privileged apps
* clobbering the runtime directory unnecessarily. */
r = configure_runtime_directory(handle, ur, runtime_path);
if (r != PAM_SUCCESS)
return r;
}
/* Most likely we got the session/type/class from environment variables, but might have gotten the data
* 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. */
@@ -1357,17 +1351,24 @@ static int register_session(
TAKE_FD(fd);
}
/* Don't set $XDG_RUNTIME_DIR if the user we now authenticated for does not match the
* original user of the session. We do this in order not to result in privileged apps
* clobbering the runtime directory unnecessarily. */
_cleanup_free_ char *rt = NULL;
if (original_uid == ur->uid && validate_runtime_directory(handle, runtime_path, ur->uid))
if (strdup_to(&rt, runtime_path) < 0)
return pam_log_oom(handle);
/* 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);
}
_cleanup_free_ char *rs = NULL;
if (strdup_to(&rs, real_seat) < 0)
return pam_log_oom(handle);
c->seat = *ret_seat = rs;
c->vtnr = real_vtnr;
c->seat = *ret_seat = TAKE_PTR(rs);
*ret_runtime_dir = TAKE_PTR(rt);
return PAM_SUCCESS;
}
@@ -1392,9 +1393,103 @@ static int import_shell_credentials(pam_handle_t *handle) {
return PAM_SUCCESS;
}
static int update_home_env(
static int mkdir_chown_open_directory(
int parent_fd,
const char *name,
uid_t uid,
gid_t gid,
mode_t mode) {
_cleanup_free_ char *t = NULL;
int r;
assert(parent_fd >= 0);
assert(name);
assert(uid_is_valid(uid));
assert(gid_is_valid(gid));
assert(mode != MODE_INVALID);
for (unsigned attempt = 0;; attempt++) {
_cleanup_close_ int fd = openat(parent_fd, name, O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
if (fd >= 0)
return TAKE_FD(fd);
if (errno != ENOENT)
return -errno;
/* Let's create the directory under a temporary name first, since we want to make sure that
* once it appears under the right name it has the right ownership */
r = tempfn_random(name, /* extra= */ NULL, &t);
if (r < 0)
return r;
fd = open_mkdir_at(parent_fd, t, O_CLOEXEC|O_EXCL, 0700); /* Use restrictive mode until ownership is in order */
if (fd < 0)
return fd;
r = RET_NERRNO(fchown(fd, uid, gid));
if (r < 0)
goto fail;
r = RET_NERRNO(fchmod(fd, mode));
if (r < 0)
goto fail;
r = rename_noreplace(parent_fd, t, parent_fd, name);
if (r >= 0)
return TAKE_FD(fd);
if (r != -EEXIST || attempt >= 5)
goto fail;
/* Maybe some other login attempt created the directory at the same time? Let's retry */
(void) unlinkat(parent_fd, t, AT_REMOVEDIR);
t = mfree(t);
}
fail:
(void) unlinkat(parent_fd, ASSERT_PTR(t), AT_REMOVEDIR);
return r;
}
static int make_area_runtime_directory(
pam_handle_t *handle,
UserRecord *ur,
const char *runtime_directory,
const char *area,
char **ret) {
assert(handle);
assert(ur);
assert(runtime_directory);
assert(area);
assert(ret);
/* Let's be careful with creating these directories, the runtime directory is owned by the user after all,
* and they might play symlink games with us. */
_cleanup_close_ int fd = open(runtime_directory, O_CLOEXEC|O_PATH|O_DIRECTORY);
if (fd < 0)
return pam_syslog_errno(handle, LOG_ERR, errno, "Unable to open runtime directory '%s': %m", runtime_directory);
_cleanup_close_ int fd_areas = mkdir_chown_open_directory(fd, "Areas", ur->uid, user_record_gid(ur), 0755);
if (fd_areas < 0)
return pam_syslog_errno(handle, LOG_ERR, fd_areas, "Unable to create 'Areas' directory below '%s': %m", runtime_directory);
_cleanup_close_ int fd_area = mkdir_chown_open_directory(fd_areas, area, ur->uid, user_record_gid(ur), 0755);
if (fd_area < 0)
return pam_syslog_errno(handle, LOG_ERR, fd_area, "Unable to create '%s' directory below '%s/Areas': %m", area, runtime_directory);
char *j = path_join(runtime_directory, "Areas", area);
if (!j)
return pam_log_oom(handle);
*ret = j;
return 0;
}
static int setup_environment(
pam_handle_t *handle,
UserRecord *ur,
const char *runtime_directory,
const char *area,
bool debug) {
@@ -1408,7 +1503,7 @@ static int update_home_env(
/* If an empty area string is specified, this means an explicit: do not use the area logic, normalize this here */
area = empty_to_null(area);
_cleanup_free_ char *ha = NULL;
_cleanup_free_ char *ha = NULL, *area_copy = NULL;
if (area) {
_cleanup_free_ char *j = path_join(h, "Areas", area);
if (!j)
@@ -1436,27 +1531,47 @@ static int update_home_env(
pam_info(handle, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area);
area = NULL;
} else {
pam_debug_syslog(handle, debug, "Area '%s' selected, setting $HOME to '%s': %m", area, ha);
/* All good, now make a copy of the area string, since we quite likely are
* going to invalidate it (if it points into the environment block), via the
* update_environment() call below */
area_copy = strdup(area);
if (!area_copy)
return pam_log_oom(handle);
pam_debug_syslog(handle, debug, "Area '%s' selected, setting $HOME to '%s'.", area, ha);
h = ha;
area = area_copy;
}
}
}
if (area) {
r = update_environment(handle, "XDG_AREA", area);
r = update_environment(handle, "XDG_AREA", area);
if (r != PAM_SUCCESS)
return r;
r = update_environment(handle, "HOME", h);
if (r != PAM_SUCCESS)
return r;
_cleanup_free_ char *per_area_runtime_directory = NULL;
if (runtime_directory && area) {
/* Also create a per-area subdirectory for $XDG_RUNTIME_DIR, so that each area has their own
* set of runtime services. We follow the same directory structure as for $HOME. Note that we
* do not define any form of automatic clean-up for the per-aera subdirs beyond the regular
* clean-up of the whole $XDG_RUNTIME_DIRECTORY hierarchy when the user finally logs out. */
r = make_area_runtime_directory(handle, ur, runtime_directory, area, &per_area_runtime_directory);
if (r != PAM_SUCCESS)
return r;
} else if (pam_getenv(handle, "XDG_AREA")) {
/* Unset the $XDG_AREA variable if set. Note that pam_putenv() would log nastily behind our
* back if we call it without $XDG_AREA actually being set. Hence we check explicitly if it's
* set before. */
r = pam_putenv(handle, "XDG_AREA");
if (!IN_SET(r, PAM_SUCCESS, PAM_BAD_ITEM))
pam_syslog_pam_error(handle, LOG_WARNING, r,
"Failed to unset XDG_AREA environment variable, ignoring: @PAMERR@");
runtime_directory = per_area_runtime_directory;
}
return update_environment(handle, "HOME", h);
r = update_environment(handle, "XDG_RUNTIME_DIR", runtime_directory);
if (r != PAM_SUCCESS)
return r;
return export_legacy_dbus_address(handle, runtime_directory);
}
_public_ PAM_EXTERN int pam_sm_open_session(
@@ -1522,8 +1637,8 @@ _public_ PAM_EXTERN int pam_sm_open_session(
session_context_mangle(handle, &c, ur, debug);
_cleanup_free_ char *seat_buffer = NULL;
r = register_session(handle, &c, ur, debug, &seat_buffer);
_cleanup_free_ char *seat_buffer = NULL, *runtime_dir = NULL;
r = register_session(handle, &c, ur, debug, &seat_buffer, &runtime_dir);
if (r != PAM_SUCCESS)
return r;
@@ -1531,7 +1646,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
if (r != PAM_SUCCESS)
return r;
r = update_home_env(handle, ur, c.area, debug);
r = setup_environment(handle, ur, runtime_dir, c.area, debug);
if (r != PAM_SUCCESS)
return r;

View File

@@ -4,10 +4,9 @@
# Used by systemd --user instances.
{% if ENABLE_HOMED %}
-account sufficient pam_systemd_home.so
-account [success=done authtok_expired=bad new_authtok_reqd=bad maxtries=bad acct_expired=bad default=ignore] pam_systemd_home.so
{% endif %}
account sufficient pam_unix.so no_pass_expiry
account required pam_permit.so
account required pam_unix.so no_pass_expiry
{% if HAVE_SELINUX %}
session required pam_selinux.so close

View File

@@ -4,7 +4,7 @@
# Used by run0 sessions
{% if ENABLE_HOMED %}
-account sufficient pam_systemd_home.so
-account [success=done authtok_expired=bad new_authtok_reqd=bad maxtries=bad acct_expired=bad default=ignore] pam_systemd_home.so
{% endif %}
account required pam_unix.so

View File

@@ -576,12 +576,11 @@ if command -v ssh &>/dev/null && command -v sshd &>/dev/null && ! [[ -v ASAN_OPT
if [[ -f "$dir/pam.d/sshd" ]]; then
mv "$dir/pam.d/sshd" "$dir/pam.d/sshd.bak"
cat >"$dir/pam.d/sshd" <<EOF
auth [success=done authtok_err=bad perm_denied=bad maxtries=bad default=ignore] pam_systemd_home.so
auth sufficient pam_unix.so nullok
auth sufficient pam_systemd_home.so debug
auth required pam_deny.so
account sufficient pam_systemd_home.so debug
account sufficient pam_unix.so
account required pam_permit.so
account [success=done authtok_expired=bad new_authtok_reqd=bad maxtries=bad acct_expired=bad default=ignore] pam_systemd_home.so
account required pam_unix.so
session optional pam_systemd_home.so debug
session optional pam_systemd.so
session required pam_unix.so
@@ -680,14 +679,18 @@ run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest ln -s
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest sh -c 'echo $HOME')" = "/home/subareatest"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest sh -c 'echo x$XDG_AREA')" = "x"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest sh -c 'echo $XDG_RUNTIME_DIR')" = "/run/user/$(id -u subareatest)"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a furb sh -c 'echo $HOME')" = "/home/subareatest/Areas/furb"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a furb sh -c 'echo $XDG_AREA')" = "furb"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a furb sh -c 'echo $XDG_RUNTIME_DIR')" = "/run/user/$(id -u subareatest)/Areas/furb"
PASSWORD=quux homectl update subareatest --default-area=molb
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest sh -c 'echo $HOME')" = "/home/subareatest/Areas/molb"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest sh -c 'echo $XDG_AREA')" = "molb"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest sh -c 'echo $XDG_RUNTIME_DIR')" = "/run/user/$(id -u subareatest)/Areas/molb"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a furb sh -c 'echo $HOME')" = "/home/subareatest/Areas/furb"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a furb sh -c 'echo $XDG_AREA')" = "furb"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a furb sh -c 'echo $XDG_RUNTIME_DIR')" = "/run/user/$(id -u subareatest)/Areas/furb"
# Install a PK rule that allows 'subareatest' user to invoke run0 without password, just for testing
cat > /usr/share/polkit-1/rules.d/subareatest.rules <<'EOF'
@@ -702,17 +705,22 @@ EOF
# Test "recursive" operation
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a furb run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a molb sh -c 'echo $HOME')" = "/home/subareatest/Areas/molb"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a furb run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a molb sh -c 'echo $XDG_AREA')" = "molb"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a furb run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a molb sh -c 'echo $XDG_RUNTIME_DIR')" = "/run/user/$(id -u subareatest)/Areas/molb"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a furb run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a molb run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a furb sh -c 'echo $HOME')" = "/home/subareatest/Areas/furb"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a furb run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a molb run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a furb sh -c 'echo $XDG_AREA')" = "furb"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a furb run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a molb run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a furb sh -c 'echo $XDG_RUNTIME_DIR')" = "/run/user/$(id -u subareatest)/Areas/furb"
# Test symlinked area
mkdir -p /home/srub
chown subareatest:subareatest /home/srub
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a srub sh -c 'echo $HOME')" = "/home/srub"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a srub sh -c 'echo $XDG_AREA')" = "srub"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a srub sh -c 'echo $XDG_RUNTIME_DIR')" = "/run/user/$(id -u subareatest)/Areas/srub"
# Verify that login into an area not owned by target user will be redirected to main area
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a root sh -c 'echo $HOME')" = "/home/subareatest"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a root sh -c 'echo x$XDG_AREA')" = "x"
test "$(run0 --property=SetCredential=pam.authtok.systemd-run0:quux -u subareatest -a root sh -c 'echo $XDG_RUNTIME_DIR')" = "/run/user/$(id -u subareatest)"
systemctl stop user@"$(id -u subareatest)".service