Skip to content

Commit

Permalink
openrc-user binary
Browse files Browse the repository at this point in the history
  • Loading branch information
navi-desu committed Oct 28, 2024
1 parent 29aefc5 commit cf13a67
Show file tree
Hide file tree
Showing 7 changed files with 297 additions and 0 deletions.
1 change: 1 addition & 0 deletions init.d/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ init_common = [
'sysctl.in',
'runsvdir.in',
's6-svscan.in',
'users.in',
]

if get_option('newnet')
Expand Down
32 changes: 32 additions & 0 deletions init.d/users.in
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions sh/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ sh_config = [
scripts_config = [
'gendepends.sh.in',
'openrc-run.sh.in',
'openrc-user.sh.in',
]

if os == 'Linux'
Expand Down
22 changes: 22 additions & 0 deletions sh/openrc-user.sh.in
Original file line number Diff line number Diff line change
@@ -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"
1 change: 1 addition & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
7 changes: 7 additions & 0 deletions src/openrc-user/meson.build
Original file line number Diff line number Diff line change
@@ -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)
233 changes: 233 additions & 0 deletions src/openrc-user/openrc-user.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
#include <einfo.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <pwd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <syslog.h>
#include <unistd.h>
#include <signal.h>

#ifdef HAVE_PAM
#include <security/pam_appl.h>

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

0 comments on commit cf13a67

Please sign in to comment.