diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index ff332e9f04..27b58432b9 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -830,7 +830,8 @@ CapabilityBoundingSet=~CAP_B CAP_C Also note that some sandboxing functionality is generally not available in user services (i.e. services run by the per-user service manager). Specifically, the various settings requiring file system namespacing support (such as ProtectSystem=) are not available, as the underlying kernel functionality is only - accessible to privileged processes. + accessible to privileged processes. However, most namespacing settings, that will not work on their own in user + services, will work when used in conjunction with PrivateUsers=. @@ -1251,6 +1252,13 @@ BindReadOnlyPaths=/var/lib/systemd such as CapabilityBoundingSet= will affect only the latter, and there's no way to acquire additional capabilities in the host's user namespace. Defaults to off. + When this setting is set up by a per-user instance of the service manager, the mapping of the + root user and group to itself is omitted (unless the user manager is root). + Additionally, in the per-user instance manager case, the + user namespace will be set up before most other namespaces. This means that combining + PrivateUsers= with other namespaces will enable use of features not + normally supported by the per-user instances of the service manager. + This setting is particularly useful in conjunction with RootDirectory=/RootImage=, as the need to synchronize the user and group databases in the root directory and on the host is reduced, as the only users and groups who need to be matched @@ -1258,9 +1266,7 @@ BindReadOnlyPaths=/var/lib/systemd Note that the implementation of this setting might be impossible (for example if user namespaces are not available), and the unit should be written in a way that does not solely rely on this setting for - security. - - + security. diff --git a/src/core/execute.c b/src/core/execute.c index 4f96d1f410..2cb2392d16 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -1900,7 +1900,7 @@ static bool exec_needs_mount_namespace( return false; } -static int setup_private_users(uid_t uid, gid_t gid) { +static int setup_private_users(uid_t ouid, gid_t ogid, uid_t uid, gid_t gid) { _cleanup_free_ char *uid_map = NULL, *gid_map = NULL; _cleanup_close_pair_ int errno_pipe[2] = { -1, -1 }; _cleanup_close_ int unshare_ready_fd = -1; @@ -1909,38 +1909,43 @@ static int setup_private_users(uid_t uid, gid_t gid) { ssize_t n; int r; - /* Set up a user namespace and map root to root, the selected UID/GID to itself, and everything else to + /* Set up a user namespace and map the original UID/GID (IDs from before any user or group changes, i.e. + * the IDs from the user or system manager(s)) to itself, the selected UID/GID to itself, and everything else to * nobody. In order to be able to write this mapping we need CAP_SETUID in the original user namespace, which * we however lack after opening the user namespace. To work around this we fork() a temporary child process, * which waits for the parent to create the new user namespace while staying in the original namespace. The * child then writes the UID mapping, under full privileges. The parent waits for the child to finish and - * continues execution normally. */ + * continues execution normally. + * For unprivileged users (i.e. without capabilities), the root to root mapping is excluded. As such, it + * does not need CAP_SETUID to write the single line mapping to itself. */ - if (uid != 0 && uid_is_valid(uid)) { + /* Can only set up multiple mappings with CAP_SETUID. */ + if (have_effective_cap(CAP_SETUID) && uid != ouid && uid_is_valid(uid)) r = asprintf(&uid_map, - "0 0 1\n" /* Map root → root */ + UID_FMT " " UID_FMT " 1\n" /* Map $OUID → $OUID */ UID_FMT " " UID_FMT " 1\n", /* Map $UID → $UID */ - uid, uid); - if (r < 0) - return -ENOMEM; - } else { - uid_map = strdup("0 0 1\n"); /* The case where the above is the same */ - if (!uid_map) - return -ENOMEM; - } + ouid, ouid, uid, uid); + else + r = asprintf(&uid_map, + UID_FMT " " UID_FMT " 1\n", /* Map $OUID → $OUID */ + ouid, ouid); - if (gid != 0 && gid_is_valid(gid)) { + if (r < 0) + return -ENOMEM; + + /* Can only set up multiple mappings with CAP_SETGID. */ + if (have_effective_cap(CAP_SETGID) && gid != ogid && gid_is_valid(gid)) r = asprintf(&gid_map, - "0 0 1\n" /* Map root → root */ + GID_FMT " " GID_FMT " 1\n" /* Map $OGID → $OGID */ GID_FMT " " GID_FMT " 1\n", /* Map $GID → $GID */ - gid, gid); - if (r < 0) - return -ENOMEM; - } else { - gid_map = strdup("0 0 1\n"); /* The case where the above is the same */ - if (!gid_map) - return -ENOMEM; - } + ogid, ogid, gid, gid); + else + r = asprintf(&gid_map, + GID_FMT " " GID_FMT " 1\n", /* Map $OGID -> $OGID */ + ogid, ogid); + + if (r < 0) + return -ENOMEM; /* Create a communication channel so that the parent can tell the child when it finished creating the user * namespace. */ @@ -2983,6 +2988,7 @@ static int exec_child( char **final_argv = NULL; dev_t journal_stream_dev = 0; ino_t journal_stream_ino = 0; + bool userns_set_up = false; bool needs_sandboxing, /* Do we need to set up full sandboxing? (i.e. all namespacing, all MAC stuff, caps, yadda yadda */ needs_setuid, /* Do we need to do the actual setresuid()/setresgid() calls? */ needs_mount_namespace, /* Do we need to set up a mount namespace for this kernel? */ @@ -2997,6 +3003,8 @@ static int exec_child( #if HAVE_APPARMOR bool use_apparmor = false; #endif + uid_t saved_uid = getuid(); + gid_t saved_gid = getgid(); uid_t uid = UID_INVALID; gid_t gid = GID_INVALID; size_t n_fds; @@ -3418,6 +3426,30 @@ static int exec_child( } } + if (needs_sandboxing) { +#if HAVE_SELINUX + if (use_selinux && params->selinux_context_net && socket_fd >= 0) { + r = mac_selinux_get_child_mls_label(socket_fd, command->path, context->selinux_context, &mac_selinux_context_net); + if (r < 0) { + *exit_status = EXIT_SELINUX_CONTEXT; + return log_unit_error_errno(unit, r, "Failed to determine SELinux context: %m"); + } + } +#endif + + /* If we're unprivileged, set up the user namespace first to enable use of the other namespaces. + * Users with CAP_SYS_ADMIN can set up user namespaces last because they will be able to + * set up the all of the other namespaces (i.e. network, mount, UTS) without a user namespace. */ + if (context->private_users && !have_effective_cap(CAP_SYS_ADMIN)) { + userns_set_up = true; + r = setup_private_users(saved_uid, saved_gid, uid, gid); + if (r < 0) { + *exit_status = EXIT_USER; + return log_unit_error_errno(unit, r, "Failed to set up user namespacing for unprivileged user: %m"); + } + } + } + if ((context->private_network || context->network_namespace_path) && runtime && runtime->netns_storage_socket[0] >= 0) { if (ns_type_supported(NAMESPACE_NET)) { @@ -3466,7 +3498,9 @@ static int exec_child( #endif } - /* Drop groups as early as possbile */ + /* Drop groups as early as possible. + * This needs to be done after PrivateDevices=y setup as device nodes should be owned by the host's root. + * For non-root in a userns, devices will be owned by the user/group before the group change, and nobody. */ if (needs_setuid) { r = enforce_groups(gid, supplementary_gids, ngids); if (r < 0) { @@ -3475,23 +3509,17 @@ static int exec_child( } } - if (needs_sandboxing) { -#if HAVE_SELINUX - if (use_selinux && params->selinux_context_net && socket_fd >= 0) { - r = mac_selinux_get_child_mls_label(socket_fd, command->path, context->selinux_context, &mac_selinux_context_net); - if (r < 0) { - *exit_status = EXIT_SELINUX_CONTEXT; - return log_unit_error_errno(unit, r, "Failed to determine SELinux context: %m"); - } - } -#endif + /* If the user namespace was not set up above, try to do it now. + * It's preferred to set up the user namespace later (after all other namespaces) so as not to be + * restricted by rules pertaining to combining user namspaces with other namespaces (e.g. in the + * case of mount namespaces being less privileged when the mount point list is copied from a + * different user namespace). */ - if (context->private_users) { - r = setup_private_users(uid, gid); - if (r < 0) { - *exit_status = EXIT_USER; - return log_unit_error_errno(unit, r, "Failed to set up user namespacing: %m"); - } + if (needs_sandboxing && context->private_users && !userns_set_up) { + r = setup_private_users(saved_uid, saved_gid, uid, gid); + if (r < 0) { + *exit_status = EXIT_USER; + return log_unit_error_errno(unit, r, "Failed to set up user namespacing: %m"); } } diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c index 7ef5b127ad..5dfcb6158a 100644 --- a/src/core/mount-setup.c +++ b/src/core/mount-setup.c @@ -536,7 +536,7 @@ int mount_setup(bool loaded_policy) { /* Also create /run/systemd/inaccessible nodes, so that we always have something to mount inaccessible nodes * from. */ - (void) make_inaccessible_nodes(NULL, UID_INVALID, GID_INVALID); + (void) make_inaccessible_nodes("/run/systemd", UID_INVALID, GID_INVALID); return 0; } diff --git a/src/core/namespace.c b/src/core/namespace.c index 104e96193d..fee4c98096 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -12,6 +12,7 @@ #include "base-filesystem.h" #include "dev-setup.h" #include "fd-util.h" +#include "format-util.h" #include "fs-util.h" #include "label.h" #include "loop-util.h" @@ -905,6 +906,7 @@ static int apply_mount( const char *root_directory, MountEntry *m) { + _cleanup_free_ char *inaccessible = NULL; bool rbind = true, make = false; const char *what; int r; @@ -916,6 +918,8 @@ static int apply_mount( switch (m->mode) { case INACCESSIBLE: { + _cleanup_free_ char *tmp = NULL; + const char *runtime_dir; struct stat target; /* First, get rid of everything that is below if there @@ -930,10 +934,20 @@ static int apply_mount( return log_debug_errno(errno, "Failed to lstat() %s to determine what to mount over it: %m", mount_entry_path(m)); } - what = mode_to_inaccessible_node(target.st_mode); - if (!what) + if (geteuid() == 0) + runtime_dir = "/run/systemd"; + else { + if (asprintf(&tmp, "/run/user/"UID_FMT, geteuid()) < 0) + log_oom(); + + runtime_dir = tmp; + } + + r = mode_to_inaccessible_node(runtime_dir, target.st_mode, &inaccessible); + if (r < 0) return log_debug_errno(SYNTHETIC_ERRNO(ELOOP), "File type not supported for inaccessible mounts. Note that symlinks are not allowed"); + what = inaccessible; break; } diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c index c8a5625605..1f98898b69 100644 --- a/src/login/user-runtime-dir.c +++ b/src/login/user-runtime-dir.c @@ -6,6 +6,7 @@ #include "sd-bus.h" #include "bus-error.h" +#include "dev-setup.h" #include "fs-util.h" #include "format-util.h" #include "label.h" @@ -91,6 +92,8 @@ static int user_mkdir_runtime_path( log_warning_errno(r, "Failed to fix label of \"%s\", ignoring: %m", runtime_path); } + /* Set up inaccessible nodes now so they're available if we decide to use them with user namespaces. */ + (void) make_inaccessible_nodes(runtime_path, uid, gid); return 0; fail: diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index 0fb83a4ff3..f423f62590 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -883,8 +883,7 @@ static int mount_overlay(const char *dest, CustomMount *m) { } static int mount_inaccessible(const char *dest, CustomMount *m) { - _cleanup_free_ char *where = NULL; - const char *source; + _cleanup_free_ char *where = NULL, *source = NULL; struct stat st; int r; @@ -897,7 +896,9 @@ static int mount_inaccessible(const char *dest, CustomMount *m) { return m->graceful ? 0 : r; } - assert_se(source = mode_to_inaccessible_node(st.st_mode)); + r = mode_to_inaccessible_node("/run/systemd", st.st_mode, &source); + if (r < 0) + return m->graceful ? 0 : r; r = mount_verbose(m->graceful ? LOG_DEBUG : LOG_ERR, source, where, NULL, MS_BIND, NULL); if (r < 0) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 27ea592158..9113f6e323 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -3252,6 +3252,7 @@ static int outer_child( int netns_fd) { _cleanup_close_ int fd = -1; + const char *p; pid_t pid; ssize_t l; int r; @@ -3447,7 +3448,9 @@ static int outer_child( return r; (void) dev_setup(directory, arg_uid_shift, arg_uid_shift); - (void) make_inaccessible_nodes(directory, arg_uid_shift, arg_uid_shift); + + p = prefix_roota(directory, "/run/systemd"); + (void) make_inaccessible_nodes(p, arg_uid_shift, arg_uid_shift); r = setup_pts(directory); if (r < 0) diff --git a/src/shared/dev-setup.c b/src/shared/dev-setup.c index 071ff7b30c..4bce8b167b 100644 --- a/src/shared/dev-setup.c +++ b/src/shared/dev-setup.c @@ -61,20 +61,20 @@ int make_inaccessible_nodes(const char *root, uid_t uid, gid_t gid) { const char *name; mode_t mode; } table[] = { - { "/run/systemd", S_IFDIR | 0755 }, - { "/run/systemd/inaccessible", S_IFDIR | 0000 }, - { "/run/systemd/inaccessible/reg", S_IFREG | 0000 }, - { "/run/systemd/inaccessible/dir", S_IFDIR | 0000 }, - { "/run/systemd/inaccessible/fifo", S_IFIFO | 0000 }, - { "/run/systemd/inaccessible/sock", S_IFSOCK | 0000 }, + { "", S_IFDIR | 0755 }, + { "/inaccessible", S_IFDIR | 0000 }, + { "/inaccessible/reg", S_IFREG | 0000 }, + { "/inaccessible/dir", S_IFDIR | 0000 }, + { "/inaccessible/fifo", S_IFIFO | 0000 }, + { "/inaccessible/sock", S_IFSOCK | 0000 }, /* The following two are likely to fail if we lack the privs for it (for example in an userns * environment, if CAP_SYS_MKNOD is missing, or if a device node policy prohibit major/minor of 0 * device nodes to be created). But that's entirely fine. Consumers of these files should carry - * fallback to use a different node then, for example /run/systemd/inaccessible/sock, which is close + * fallback to use a different node then, for example /inaccessible/sock, which is close * enough in behaviour and semantics for most uses. */ - { "/run/systemd/inaccessible/chr", S_IFCHR | 0000 }, - { "/run/systemd/inaccessible/blk", S_IFBLK | 0000 }, + { "/inaccessible/chr", S_IFCHR | 0000 }, + { "/inaccessible/blk", S_IFBLK | 0000 }, }; _cleanup_umask_ mode_t u; diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index d316e81aa2..95d7ea9691 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -339,38 +339,72 @@ int repeat_unmount(const char *path, int flags) { } } -const char* mode_to_inaccessible_node(mode_t mode) { +int mode_to_inaccessible_node(const char *runtime_dir, mode_t mode, char **dest) { /* This function maps a node type to a corresponding inaccessible file node. These nodes are created during * early boot by PID 1. In some cases we lacked the privs to create the character and block devices (maybe * because we run in an userns environment, or miss CAP_SYS_MKNOD, or run with a devices policy that excludes * device nodes with major and minor of 0), but that's fine, in that case we use an AF_UNIX file node instead, * which is not the same, but close enough for most uses. And most importantly, the kernel allows bind mounts * from socket nodes to any non-directory file nodes, and that's the most important thing that matters. */ + _cleanup_free_ char *d = NULL; + const char *node = NULL; + char *tmp; + + assert(dest); switch(mode & S_IFMT) { case S_IFREG: - return "/run/systemd/inaccessible/reg"; + node = "/inaccessible/reg"; + break; case S_IFDIR: - return "/run/systemd/inaccessible/dir"; + node = "/inaccessible/dir"; + break; case S_IFCHR: - if (access("/run/systemd/inaccessible/chr", F_OK) == 0) - return "/run/systemd/inaccessible/chr"; - return "/run/systemd/inaccessible/sock"; + d = path_join(runtime_dir, "/inaccessible/chr"); + if (!d) + return log_oom(); + + if (access(d, F_OK) == 0) { + *dest = TAKE_PTR(d); + return 0; + } + + node = "/inaccessible/sock"; + break; case S_IFBLK: - if (access("/run/systemd/inaccessible/blk", F_OK) == 0) - return "/run/systemd/inaccessible/blk"; - return "/run/systemd/inaccessible/sock"; + d = path_join(runtime_dir, "/inaccessible/blk"); + if (!d) + return log_oom(); + + if (access(d, F_OK) == 0) { + *dest = TAKE_PTR(d); + return 0; + } + + node = "/inaccessible/sock"; + break; case S_IFIFO: - return "/run/systemd/inaccessible/fifo"; + node = "/inaccessible/fifo"; + break; case S_IFSOCK: - return "/run/systemd/inaccessible/sock"; + node = "/inaccessible/sock"; + break; } - return NULL; + + if (!node) + return -EINVAL; + + tmp = path_join(runtime_dir, node); + if (!tmp) + return log_oom(); + + *dest = tmp; + return 0; } #define FLAG(name) (flags & name ? STRINGIFY(name) "|" : "") diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h index 8649fca39b..9a8d073631 100644 --- a/src/shared/mount-util.h +++ b/src/shared/mount-util.h @@ -31,4 +31,4 @@ int mount_option_mangle( unsigned long *ret_mount_flags, char **ret_remaining_options); -const char* mode_to_inaccessible_node(mode_t mode); +int mode_to_inaccessible_node(const char *runtime_dir, mode_t mode, char **dest); diff --git a/src/test/test-dev-setup.c b/src/test/test-dev-setup.c index 9414ea6c3e..d991fe5200 100644 --- a/src/test/test-dev-setup.c +++ b/src/test/test-dev-setup.c @@ -20,7 +20,8 @@ int main(int argc, char *argv[]) { f = prefix_roota(p, "/run"); assert_se(mkdir(f, 0755) >= 0); - assert_se(make_inaccessible_nodes(p, 1, 1) >= 0); + f = prefix_roota(p, "/run/systemd"); + assert_se(make_inaccessible_nodes(f, 1, 1) >= 0); f = prefix_roota(p, "/run/systemd/inaccessible/reg"); assert_se(stat(f, &st) >= 0); diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 9d6aeed776..b3f8cc8434 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -313,6 +313,7 @@ static void test_exec_privatedevices(Manager *m) { test(__func__, m, "exec-privatedevices-yes.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED); test(__func__, m, "exec-privatedevices-no.service", 0, CLD_EXITED); test(__func__, m, "exec-privatedevices-disabled-by-prefix.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED); + test(__func__, m, "exec-privatedevices-yes-with-group.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED); /* We use capsh to test if the capabilities are * properly set, so be sure that it exists */ diff --git a/test/TEST-43-PRIVATEUSER-UNPRIV/Makefile b/test/TEST-43-PRIVATEUSER-UNPRIV/Makefile new file mode 100644 index 0000000000..45e9bfc67c --- /dev/null +++ b/test/TEST-43-PRIVATEUSER-UNPRIV/Makefile @@ -0,0 +1,9 @@ +BUILD_DIR=$(shell ../../tools/find-build-dir.sh) + +all setup run: + @basedir=../.. TEST_BASE_DIR=../ BUILD_DIR=$(BUILD_DIR) ./test.sh --$@ + +clean clean-again: + @basedir=../.. TEST_BASE_DIR=../ BUILD_DIR=$(BUILD_DIR) ./test.sh --clean + +.PHONY: all setup run clean clean-again diff --git a/test/TEST-43-PRIVATEUSER-UNPRIV/test.sh b/test/TEST-43-PRIVATEUSER-UNPRIV/test.sh new file mode 100755 index 0000000000..49d61c6a7f --- /dev/null +++ b/test/TEST-43-PRIVATEUSER-UNPRIV/test.sh @@ -0,0 +1,46 @@ +#!/bin/bash +set -e +TEST_DESCRIPTION="Test PrivateUsers=yes on user manager" +. $TEST_BASE_DIR/test-functions + +test_setup() { + create_empty_image_rootdir + + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + inst_binary stat + + mask_supporting_services + + usermod --root $initdir -d /home/nobody -s /bin/bash nobody + mkdir $initdir/home $initdir/home/nobody + # Ubuntu's equivalent is nogroup + chown nobody:nobody $initdir/home/nobody || chown nobody:nogroup $initdir/home/nobody + + enable_user_manager nobody + + nobody_uid=$(id -u nobody) + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service < /testok + +exit 0 diff --git a/test/meson.build b/test/meson.build index eeb3c5f8f0..237b4db12c 100644 --- a/test/meson.build +++ b/test/meson.build @@ -102,6 +102,7 @@ test_data_files = ''' test-execute/exec-privatedevices-no-capability-mknod.service test-execute/exec-privatedevices-no-capability-sys-rawio.service test-execute/exec-privatedevices-no.service + test-execute/exec-privatedevices-yes-with-group.service test-execute/exec-privatedevices-yes-capability-mknod.service test-execute/exec-privatedevices-yes-capability-sys-rawio.service test-execute/exec-privatedevices-yes.service diff --git a/test/test-execute/exec-privatedevices-yes-with-group.service b/test/test-execute/exec-privatedevices-yes-with-group.service new file mode 100644 index 0000000000..70a7ed24f4 --- /dev/null +++ b/test/test-execute/exec-privatedevices-yes-with-group.service @@ -0,0 +1,16 @@ +[Unit] +Description=Test Group=group is applied after PrivateDevices=yes + +[Service] +PrivateDevices=yes +Group=daemon +Type=oneshot + +# Check the group applied +ExecStart=/bin/sh -x -c 'test "$$(id -n -g)" = "daemon"' + +# Check that the namespace applied +ExecStart=/bin/sh -c 'test ! -c /dev/kmsg' + +# Check that the owning group of a node is not daemon (should be the host root) +ExecStart=/bin/sh -x -c 'test ! "$$(stat -c %%G /dev/stderr)" = "daemon"' diff --git a/test/test-functions b/test/test-functions index 468a36d310..5fffa5b9f8 100644 --- a/test/test-functions +++ b/test/test-functions @@ -787,7 +787,7 @@ install_libnss() { install_dbus() { inst $ROOTLIBDIR/system/dbus.socket - # Newer Fedora versions use dbus-broker by default. Let's install it is available. + # Newer Fedora versions use dbus-broker by default. Let's install it if it's available. if [ -f $ROOTLIBDIR/system/dbus-broker.service ]; then inst $ROOTLIBDIR/system/dbus-broker.service inst_symlink /etc/systemd/system/dbus.service @@ -809,6 +809,31 @@ install_dbus() { done } +install_user_dbus() { + inst $ROOTLIBDIR/user/dbus.socket + inst_symlink /usr/lib/systemd/user/sockets.target.wants/dbus.socket || inst_symlink /etc/systemd/user/sockets.target.wants/dbus.socket + + # Append the After= dependency on dbus in case it isn't already set up + mkdir -p "$initdir/etc/systemd/system/user@.service.d/" + cat <"$initdir/etc/systemd/system/user@.service.d/dbus.conf" +[Unit] +After=dbus.service +EOF + + # Newer Fedora versions use dbus-broker by default. Let's install it if it's available. + if [ -f $ROOTLIBDIR/user/dbus-broker.service ]; then + inst $ROOTLIBDIR/user/dbus-broker.service + inst_symlink /etc/systemd/user/dbus.service + elif [ -f $ROOTLIBDIR/system/dbus-daemon.service ]; then + # Fedora rawhide replaced dbus.service with dbus-daemon.service + inst $ROOTLIBDIR/user/dbus-daemon.service + # Alias symlink + inst_symlink /etc/systemd/user/dbus.service + else + inst $ROOTLIBDIR/user/dbus.service + fi +} + install_pam() { ( if [[ "$LOOKS_LIKE_DEBIAN" ]] && type -p dpkg-architecture &>/dev/null; then @@ -879,6 +904,28 @@ install_terminfo() { dracut_install -o ${_terminfodir}/l/linux } +has_user_dbus_socket() { + if [ -f /usr/lib/systemd/user/dbus.socket ] || [ -f /etc/systemd/user/dbus.socket ]; then + return 0 + else + echo "Per-user instances are not supported. Skipping..." + return 1 + fi +} + +enable_user_manager() { + has_user_dbus_socket || return 0 + + local _userid + [[ $# -gt 0 ]] || set -- nobody + mkdir -p "$initdir/var/lib/systemd/linger" + for _userid; do + touch "$initdir/var/lib/systemd/linger/$_userid" + done + dracut_install su + install_user_dbus +} + setup_testsuite() { cp $TEST_BASE_DIR/testsuite.target $initdir/etc/systemd/system/ cp $TEST_BASE_DIR/end.service $initdir/etc/systemd/system/