diff --git a/init.d/meson.build b/init.d/meson.build index ab4b27f41..bfcbc73e3 100644 --- a/init.d/meson.build +++ b/init.d/meson.build @@ -16,6 +16,7 @@ init_common = [ 'sysctl.in', 'runsvdir.in', 's6-svscan.in', + 'users.in', ] if get_option('newnet') diff --git a/init.d/users.in b/init.d/users.in new file mode 100644 index 000000000..a164a83a8 --- /dev/null +++ b/init.d/users.in @@ -0,0 +1,32 @@ +#!@SBINDIR@/openrc-run +# Copyright (c) 2017 The OpenRC Authors. +# See the Authors file at the top-level directory of this distribution and +# https://github.com/OpenRC/openrc/blob/HEAD/AUTHORS +# +# This file is part of OpenRC. It is subject to the license terms in +# the LICENSE file found in the top-level directory of this +# distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE +# This file may not be copied, modified, propagated, or distributed +# except according to the terms contained in the LICENSE file. + +description="Starts and stops openrc --user for system users." + +start() { + mkdir "$RC_SVCDIR"/users || return 1 + for user in ${rc_users}; do + ebegin "Starting user session for $user" + openrc-user $user start + echo "-1" > "@SYSCONFDIR@/users/$user" + eend $? + done + return 0 +} + +stop() { + for user in "$RC_SVCDIR"/users/*; do + ebegin "Stopping user session for $user" + openrc-user $user stop + eend $? + done + return 0 +} diff --git a/sh/meson.build b/sh/meson.build index 981340b0a..ec5dc8bee 100644 --- a/sh/meson.build +++ b/sh/meson.build @@ -23,6 +23,7 @@ sh_config = [ scripts_config = [ 'gendepends.sh.in', 'openrc-run.sh.in', + 'openrc-user.sh.in', ] if os == 'Linux' diff --git a/sh/openrc-user.sh.in b/sh/openrc-user.sh.in new file mode 100644 index 000000000..7d9b55c0e --- /dev/null +++ b/sh/openrc-user.sh.in @@ -0,0 +1,22 @@ +#!@SHELL@ + +. @LIBEXECDIR@/sh/functions.sh + +_sysconf="${XDG_CONFIG_HOME:-${HOME}/.config}/openrc" + +for config in "@SYSCONFDIR@/rc.conf" "$_sysconf/rc.conf"; do + if [ -e "$config" ] && ! . "$config"; then + eerror "openrc-user: Failed loading $config" + exit 1 + fi +done + +case $1 in + start) _runlevel="${rc_user_runlevel:-default}";; + stop) _runlevel="${rc_user_shutdown_runlevel:-none}";; + *) eerror "no argument given to $0" && exit 1 +esac + +mkdir -p "$_sysconf/runlevels/$_runlevel" + +openrc --user "$_runlevel" diff --git a/src/meson.build b/src/meson.build index 76f6d8a14..1dce3c0fb 100644 --- a/src/meson.build +++ b/src/meson.build @@ -18,6 +18,7 @@ subdir('openrc') subdir('openrc-init') subdir('openrc-run') subdir('openrc-shutdown') +subdir('openrc-user') subdir('poweroff') subdir('rc-abort') subdir('rc-depend') diff --git a/src/openrc-user/meson.build b/src/openrc-user/meson.build new file mode 100644 index 000000000..715ca1186 --- /dev/null +++ b/src/openrc-user/meson.build @@ -0,0 +1,7 @@ +executable('openrc-user', ['openrc-user.c'], + c_args: [cc_pam_flags], + dependencies: pam_dep, + include_directories: [incdir, einfo_incdir, rc_incdir], + link_with: [libeinfo, librc], + install: true, + install_dir: rc_bindir) diff --git a/src/openrc-user/openrc-user.c b/src/openrc-user/openrc-user.c new file mode 100644 index 000000000..afe94d6ac --- /dev/null +++ b/src/openrc-user/openrc-user.c @@ -0,0 +1,233 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_PAM +#include + +static int +openrc_conv(int num_msg, const struct pam_message **msgm, struct pam_response **response, void *appdata_ptr) +{ + (void) response; + (void) appdata_ptr; + + for (int i = 0; i < num_msg; i++) { + switch (msgm[i]->msg_style) { + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + if (msgm[i]->msg != NULL) + elog(LOG_ERR, "pam_message: %s", msgm[i]->msg); + break; + default: + break; + } + } + return 0; +} + +static struct pam_conv conv = { openrc_conv, NULL }; +#endif + +#include "helpers.h" +#include "rc.h" + +const struct passwd *user; +char *pidfile; + +static bool do_setup(const char *username) { + char *logname; + int nullfd; + + xasprintf(&logname, "openrc-user[%s]", username); + setenv("EINFO_LOG", logname, true); + free(logname); + + if (!(user = getpwnam(username))) { + elog(LOG_ERR, "getpwnam failed: %s", strerror(errno)); + return false; + } + + nullfd = open("/dev/null", O_RDWR); + dup2(nullfd, STDIN_FILENO); + close(nullfd); + + return true; +} + +static bool check_pidfile(bool start) { + struct stat buf; + FILE *file; + + xasprintf(&pidfile, "%s/users/%s", rc_svcdir(), user->pw_name); + stat(pidfile, &buf); + if (stat(pidfile, &buf) == 0 && buf.st_size > 0 && (file = fopen(pidfile, "r"))) { + pid_t pid = 0; + if (fscanf(file, "%d", &pid) != 1 || kill(pid, start ? SIGUSR1 : SIGUSR2) == -1) { + elog(LOG_ERR, "Failed to signal pid %d, resetting.", pid); + exit(1); + } + return true; + } + + if (!(file = fopen(pidfile, "w"))) { + elog(LOG_ERR, "Failed to open pidfile %s: %s", pidfile, strerror(errno)); + exit(1); + } + + fprintf(file, "%d\n", getpid()); + fclose(file); + + return false; +} + +static void do_openrc(bool start) { + pid_t child; + char *cmd; + + switch ((child = fork())) { + case 0: + if (setgid(user->pw_gid) == -1 || setuid(user->pw_uid) == -1) { + elog(LOG_ERR, "Failed to drop permissions to user."); + exit(1); + } + + setenv("HOME", user->pw_dir, true); + setenv("SHELL", user->pw_shell, true); + + xasprintf(&cmd, "%s %s", RC_LIBEXECDIR "/sh/openrc-user.sh", start ? "start" : "stop"); + execl(user->pw_shell, "-", "-c", cmd, NULL); + + elog(LOG_ERR, "Failed to execl '%s - -c %s': %s.", user->pw_shell, cmd, strerror(errno)); + exit(1); + case -1: + exit(1); + default: + break; + } + + waitpid(child, NULL, 0); +} + +static void do_wait(void) { + size_t logins = 1; + sigset_t set; + int sig; + + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + sigaddset(&set, SIGUSR2); + sigaddset(&set, SIGTERM); + + sigprocmask(SIG_SETMASK, &set, NULL); + + while (logins > 0) { + sigwait(&set, &sig); + switch (sig) { + case SIGUSR1: + logins++; + break; + case SIGUSR2: + logins--; + break; + case SIGTERM: + case SIGKILL: + return; + default: + break; + } + } + + return; +} + +#ifdef HAVE_PAM +static pam_handle_t *open_session(void) { + pam_handle_t *pamh = NULL; + bool pam_session = false; + int rc; + + if ((rc = pam_start("openrc-user", user->pw_name, &conv, &pamh)) != PAM_SUCCESS) + elog(LOG_ERR, "Failed to start pam: %s", pam_strerror(pamh, rc)); + else if ((rc = pam_open_session(pamh, PAM_SILENT)) != PAM_SUCCESS) + elog(LOG_ERR, "Failed to open session: %s", pam_strerror(pamh, rc)); + else + pam_session = true; + + for (char **env = pam_getenvlist(pamh); env && *env; env++) { + if (strchr(*env, '=')) + putenv(xstrdup(*env)); + else + unsetenv(*env); + } + + if (!pam_session && pamh) { + pam_end(pamh, rc); + return NULL; + } + + return pamh; +} +#endif + +int main(int argc, char **argv) { + bool start; + +#ifdef HAVE_PAM + pam_handle_t *pamh = NULL; +#endif + + if (argc < 3) { + fprintf(stderr, "%s: Not enough arguments.\n", argv[0]); + return 1; + } + + if (strcmp(argv[2], "start") == 0) { + start = true; + } else if (strcmp(argv[2], "stop") == 0) { + start = false; + } else { + fprintf(stderr, "%s: Invalid argument %s.\n", argv[0], argv[2]); + return 1; + } + + if (!do_setup(argv[1])) + return 1; + + if (check_pidfile(start)) + return 0; + + /* we need to initgroups before pam is setup. */ + if (initgroups(user->pw_name, user->pw_gid) == -1) + return 1; + +#ifdef HAVE_PAM + pamh = open_session(); +#endif + + do_openrc(true); + do_wait(); + do_openrc(false); + +#ifdef HAVE_PAM + if (pamh) { + int rc; + if ((rc = pam_close_session(pamh, PAM_SILENT)) != PAM_SUCCESS) + elog(LOG_ERR, "Failed to close session: %s", pam_strerror(pamh, rc)); + if ((rc = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS) + elog(LOG_ERR, "Failed to delete user's credentials: %s", pam_strerror(pamh, rc)); + pam_end(pamh, rc); + } +#endif + + unlink(pidfile); + free(pidfile); + return 0; +}