From 5d43c43ed4a0d03eaac7dfaf058fd57833e20f66 Mon Sep 17 00:00:00 2001 From: "Anna (navi) Figueiredo Gomes" Date: Mon, 22 Apr 2024 00:11:25 +0200 Subject: [PATCH] openrc-pam: Add pam module to autolaunch openrc --user the module sets up XDG_RUNTIME_DIR if it's already not set, then uses utmpx to figure out if the user has any open sessions. If not, starts or stops openrc --user. Signed-off-by: Anna (navi) Figueiredo Gomes --- src/librc/librc.c | 48 -------- src/librc/meson.build | 2 + src/meson.build | 1 + src/openrc-pam/meson.build | 11 ++ src/openrc-pam/openrc-pam.c | 231 ++++++++++++++++++++++++++++++++++++ src/shared/misc.c | 49 ++++++++ src/shared/misc.h | 1 + 7 files changed, 295 insertions(+), 48 deletions(-) create mode 100644 src/openrc-pam/meson.build create mode 100644 src/openrc-pam/openrc-pam.c diff --git a/src/librc/librc.c b/src/librc/librc.c index 00fc39afe..9cf835d8f 100644 --- a/src/librc/librc.c +++ b/src/librc/librc.c @@ -118,54 +118,6 @@ ls_dir(const char *dir, int options) return list; } -static bool -rm_dir(const char *pathname, bool top) -{ - DIR *dp; - struct dirent *d; - char file[PATH_MAX]; - struct stat s; - bool retval = true; - - if ((dp = opendir(pathname)) == NULL) - return false; - - errno = 0; - while (((d = readdir(dp)) != NULL) && errno == 0) { - if (strcmp(d->d_name, ".") != 0 && - strcmp(d->d_name, "..") != 0) - { - snprintf(file, sizeof(file), - "%s/%s", pathname, d->d_name); - if (stat(file, &s) != 0) { - retval = false; - break; - } - if (S_ISDIR(s.st_mode)) { - if (!rm_dir(file, true)) - { - retval = false; - break; - } - } else { - if (unlink(file)) { - retval = false; - break; - } - } - } - } - closedir(dp); - - if (!retval) - return false; - - if (top && rmdir(pathname) != 0) - return false; - - return true; -} - /* Other systems may need this at some point, but for now it's Linux only */ #ifdef __linux__ static bool diff --git a/src/librc/meson.build b/src/librc/meson.build index 8d963208b..3cf7657e2 100644 --- a/src/librc/meson.build +++ b/src/librc/meson.build @@ -18,6 +18,8 @@ librc_sources = [ 'librc-depend.c', 'librc-misc.c', 'librc-stringlist.c', + misc_c, + version_h ] rc_h = configure_file(input : 'rc.h.in', output : 'rc.h', diff --git a/src/meson.build b/src/meson.build index 76f6d8a14..e07e679bc 100644 --- a/src/meson.build +++ b/src/meson.build @@ -16,6 +16,7 @@ subdir('mountinfo') subdir('on_ac_power') subdir('openrc') subdir('openrc-init') +subdir('openrc-pam') subdir('openrc-run') subdir('openrc-shutdown') subdir('poweroff') diff --git a/src/openrc-pam/meson.build b/src/openrc-pam/meson.build new file mode 100644 index 000000000..fa9de471c --- /dev/null +++ b/src/openrc-pam/meson.build @@ -0,0 +1,11 @@ +if get_option('pam') and pam_dep.found() + shared_library('pam_openrc', + ['openrc-pam.c', misc_c, version_h], + c_args : [cc_branding_flags], + dependencies : [pam_dep], + name_prefix : '', + link_with : [libeinfo, librc], + include_directories : [incdir, einfo_incdir, rc_incdir], + install : true, + install_dir : libdir / 'security') +endif diff --git a/src/openrc-pam/openrc-pam.c b/src/openrc-pam/openrc-pam.c new file mode 100644 index 000000000..c04621187 --- /dev/null +++ b/src/openrc-pam/openrc-pam.c @@ -0,0 +1,231 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "einfo.h" +#include "queue.h" + +static inline bool +check_utmp(struct utmpx *utmpx, const char *user) +{ + return utmpx->ut_type == USER_PROCESS + && strncmp(utmpx->ut_user, user, sizeof(utmpx->ut_user)) == 0 + && kill(utmpx->ut_pid, 0) == 0; +} + +static inline bool +check_rundir(struct stat *sb, struct passwd *pw) +{ + return sb->st_uid == pw->pw_uid && sb->st_gid == pw->pw_gid; +} + +static size_t +get_logged_in_count(const char *user) +{ + struct utmpx *utmpx; + size_t counter = 0; + setutxent(); + while ((utmpx = getutxent())) + if (check_utmp(utmpx, user)) + counter++; + return counter; +} + +static int +exec_user_cmd(pam_handle_t *pamh, struct passwd *pw, char *cmd) +{ + int retval; + const char *shellname = basename_c(pw->pw_shell); + char **envron; + + elog(LOG_INFO, "Executing %s for user %s", cmd, pw->pw_name); + + switch (fork()) { + case 0: + initgroups(pw->pw_name, pw->pw_gid); + setgid(pw->pw_gid); + setuid(pw->pw_uid); + + envron = pam_getenvlist(pamh); + execle(pw->pw_shell, shellname, "-c", cmd, NULL, envron); + + eerror("failed to exec \"%s, %s, -c, %s\": %s", + pw->pw_shell, shellname, cmd, strerror(errno)); + return -1; + break; + case -1: + return -1; + break; + } + wait(&retval); + return retval; +} + +static char * +ensure_xdg_rundir(pam_handle_t *pamh, struct passwd *pw) +{ + char *rundir = xstrdup(pam_getenv(pamh, "XDG_RUNTIME_DIR")); + char *tmp; + struct stat sb; + + if (rundir) { + if (stat(rundir, &sb) != 0 || !check_rundir(&sb, pw)) { + free(rundir); + return NULL; + } + return rundir; + } + + if (mkdir("/run/user", 0755) != 0 && errno != EEXIST) + return NULL; + + xasprintf(&rundir, "/run/user/%d", pw->pw_uid); + + if (stat(rundir, &sb) == 0 && !check_rundir(&sb, pw)) { + free(rundir); + return NULL; + } + + elog(LOG_INFO, "Creating runtime directory %s for uid %d", rundir, pw->pw_uid); + + if ((mkdir(rundir, 0700) != 0 && errno != EEXIST) + || chown(rundir, pw->pw_uid, pw->pw_gid) != 0) { + free(rundir); + return NULL; + } + + xasprintf(&tmp, "%s/.openrc-rundir", rundir); + close(creat(tmp, 0500)); + free(tmp); + + xasprintf(&tmp, "XDG_RUNTIME_DIR=%s", rundir); + + pam_putenv(pamh, tmp); + free(tmp); + + return rundir; +} + +static void +clear_xdg_rundir(pam_handle_t *pamh, struct passwd *pw) +{ + char *rundir = xstrdup(pam_getenv(pamh, "XDG_RUNTIME_DIR")); + char *file; + if (!rundir) + xasprintf(&rundir, "/run/user/%d", pw->pw_uid); + + xasprintf(&file, "%s/.openrc-rundir", rundir); + if (exists(file)) { + elog(LOG_INFO, "Removing runtime directory %s for uid %d", rundir, pw->pw_uid); + rm_dir(rundir, true); + } + + free(file); + free(rundir); +} + +static bool +exec_openrc(pam_handle_t *pamh, struct passwd *pw, const char *runlevel, bool going_down) +{ + char *cmd = NULL, *dir, *lock; + bool ret = true; + char *rundir = ensure_xdg_rundir(pamh, pw); + int lock_fd; + + if (!rundir) + return false; + + xasprintf(&lock, "%s/openrc.lock", rundir); + lock_fd = creat(lock, 0600); + free(lock); + + if (lock_fd == -1) + return false; + if (flock(lock_fd, LOCK_EX) != 0) { + close(lock_fd); + return false; + } + + xasprintf(&dir, "%s/openrc", rundir); + free(rundir); + + /* + * execute the command if the user doesn't have any open sessions, + * or has one but svcdir doesn't exist + */ + if (get_logged_in_count(pw->pw_name) == 0 || (!going_down && !exists(dir))) { + xasprintf(&cmd, "openrc --user %s", runlevel); + if (exec_user_cmd(pamh, pw, cmd) == -1) + ret = false; + free(cmd); + if (going_down) + clear_xdg_rundir(pamh, pw); + } + + free(dir); + close(lock_fd); + return ret; +} + +static struct passwd * +get_pw(pam_handle_t *pamh) +{ + const char *username; + if (pam_get_user(pamh, &username, "username:") != PAM_SUCCESS) + return NULL; + return getpwnam(username); +} + +PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { + const char *runlevel = argc > 0 ? runlevel = argv[0] : "default"; + struct passwd *pw; + int ret = PAM_SUCCESS; + (void) flags; + + pw = get_pw(pamh); + if (!pw) + return PAM_SESSION_ERR; + + if (pw->pw_uid == 0) + return PAM_SUCCESS; + + setenv("EINFO_LOG", "openrc-pam", 1); + elog(LOG_INFO, "Opening openrc session for user %s", pw->pw_name); + + if (!exec_openrc(pamh, pw, runlevel, false)) + ret = PAM_SESSION_ERR; + + unsetenv("EINFO_LOG"); + return ret; +} + +PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { + const char *runlevel = argc > 1 ? argv[1] : "none"; + struct passwd *pw; + int ret = PAM_SUCCESS; + (void) flags; + + pw = get_pw(pamh); + if (!pw) + return PAM_SESSION_ERR; + + if (pw->pw_uid == 0) + return PAM_SUCCESS; + + setenv("EINFO_LOG", "openrc-pam", 1); + elog(LOG_INFO, "Closing openrc session for user %s", pw->pw_name); + + if (!exec_openrc(pamh, pw, runlevel, true)) + ret = PAM_SESSION_ERR; + + unsetenv("EINFO_LOG"); + return ret; +} diff --git a/src/shared/misc.c b/src/shared/misc.c index e946c911a..6cae8aa90 100644 --- a/src/shared/misc.c +++ b/src/shared/misc.c @@ -21,6 +21,7 @@ #endif #include +#include #include #include #include @@ -526,6 +527,54 @@ pid_t get_pid(const char *applet,const char *pidfile) return pid; } +bool +rm_dir(const char *pathname, bool top) +{ + DIR *dp; + struct dirent *d; + char file[PATH_MAX]; + struct stat s; + bool retval = true; + + if ((dp = opendir(pathname)) == NULL) + return false; + + errno = 0; + while (((d = readdir(dp)) != NULL) && errno == 0) { + if (strcmp(d->d_name, ".") != 0 && + strcmp(d->d_name, "..") != 0) + { + snprintf(file, sizeof(file), + "%s/%s", pathname, d->d_name); + if (stat(file, &s) != 0) { + retval = false; + break; + } + if (S_ISDIR(s.st_mode)) { + if (!rm_dir(file, true)) + { + retval = false; + break; + } + } else { + if (unlink(file)) { + retval = false; + break; + } + } + } + } + closedir(dp); + + if (!retval) + return false; + + if (top && rmdir(pathname) != 0) + return false; + + return true; +} + #ifndef HAVE_CLOSE_RANGE static inline int close_range(int first RC_UNUSED, int last RC_UNUSED, diff --git a/src/shared/misc.h b/src/shared/misc.h index 9171a8816..d3adb2d94 100644 --- a/src/shared/misc.h +++ b/src/shared/misc.h @@ -67,6 +67,7 @@ RC_SERVICE lookup_service_state(const char *service); void from_time_t(char *time_string, time_t tv); time_t to_time_t(char *timestring); pid_t get_pid(const char *applet, const char *pidfile); +bool rm_dir(const char *pathname, bool top); void cloexec_fds_from(int);