diff --git a/init.d/bootmisc.in b/init.d/bootmisc.in index 233dfc55b..a7016eb56 100644 --- a/init.d/bootmisc.in +++ b/init.d/bootmisc.in @@ -171,8 +171,11 @@ start() if [ ! -d /run ]; then extra="/var/run $extra" fi + if [ ! -d /run/user ]; then + extra="/run/user $extra" + fi else - extra=/var/run + extra="/var/run /var/run/user" fi for x in /var/log /tmp $extra; do if ! [ -d $x ]; then diff --git a/src/librc/librc.c b/src/librc/librc.c index 4f04e6a4f..7e29861eb 100644 --- a/src/librc/librc.c +++ b/src/librc/librc.c @@ -68,54 +68,6 @@ static const rc_service_state_name_t rc_service_state_names[] = { { 0, NULL} }; -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/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..81d88fff1 --- /dev/null +++ b/src/openrc-pam/openrc-pam.c @@ -0,0 +1,293 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "einfo.h" +#include "queue.h" + +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_session_count(const char *user) +{ + char *count_str = rc_user_value_get(user, "session_count"); + size_t count; + + if (!count_str || sscanf(count_str, "%lu", &count) == 0) + count = 0; + free(count_str); + return count; +} + +static bool +set_session_count(const char *user, size_t count) +{ + char *value; + bool ret; + + if (count == 0) + return rc_user_value_set(user, "session_count", NULL); + + xasprintf(&value, "%ld", count); + ret = rc_user_value_set(user, "session_count", value); + free(value); + return ret; +} + +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", cmd); + + 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); + + elog(LOG_ERR, "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 void +export_rundir(pam_handle_t *pamh, const char *rundir) +{ + char *env; + xasprintf(&env, "XDG_RUNTIME_DIR=%s", rundir); + pam_putenv(pamh, env); + free(env); +} + +static char * +ensure_xdg_rundir(pam_handle_t *pamh, struct passwd *pw) +{ + const char *env = pam_getenv(pamh, "XDG_RUNTIME_DIR"); + char *rundir; + struct stat sb; + + if (env) { + if (stat(env, &sb) != 0 || !check_rundir(&sb, pw)) { + elog(LOG_ERR, "%s does not belong to uid %d", rundir, pw->pw_uid); + return NULL; + } + return xstrdup(env); + } + + xasprintf(&rundir, "/run/user/%d", pw->pw_uid); + if (stat(rundir, &sb) == 0) { + if (!check_rundir(&sb, pw)) { + elog(LOG_ERR, "%s does not belong to uid %d", rundir, pw->pw_uid); + free(rundir); + return NULL; + } + export_rundir(pamh, rundir); + return rundir; + } + + 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) { + elog(LOG_ERR, "Failed create runtime directory %s: %s", + rundir, strerror(errno)); + free(rundir); + return NULL; + } + + rc_user_value_set(pw->pw_name, "rundir_managed", "yes"); + + export_rundir(pamh, rundir); + return rundir; +} + +static DIR * +try_lock_dir(const char *dir) +{ + DIR *lock; + + lock = opendir(dir); + if (!lock) + return NULL; + + for (size_t tries = 0; tries < 3; tries++) { + if (flock(dirfd(lock), LOCK_EX | LOCK_NB) == 0) { + return lock; + } else if (errno != EWOULDBLOCK) { + closedir(lock); + return NULL; + } + elog(LOG_WARNING, "Failed to lock %s, trying %lu more times.", dir, 3 - tries); + sleep(1); + } + + elog(LOG_ERR, "Failed to lock %s.", dir); + return NULL; +} + +static bool +exec_openrc(pam_handle_t *pamh, struct passwd *pw, const char *runlevel, bool going_down) +{ + char *cmd = NULL; + char *lock_path; + char *rundir; + char *static_usr; + DIR *lock; + bool ret = true; + size_t count; + + /* Check if the user session is running statically */ + static_usr = rc_user_value_get(pw->pw_name, "static"); + if (rc_yesno(static_usr)) { + free(static_usr); + return true; + } + free(static_usr); + + /* Open sysconf dir for the user, and flock it */ + xasprintf(&lock_path, "%s/users/%s", rc_service_dir(), pw->pw_name); + if (mkdir(lock_path, 0755) != 0 && errno != EEXIST) { + elog(LOG_ERR, "mkdir '%s': %s", lock_path, strerror(errno)); + free(lock_path); + return false; + } + + lock = try_lock_dir(lock_path); + free(lock_path); + if (!lock) + return false; + + rundir = ensure_xdg_rundir(pamh, pw); + if (!rundir) { + elog(LOG_ERR, "Failed to create runtime directory"); + ret = false; + goto out; + } + + count = get_session_count(pw->pw_name); + + /* If going down, remove the current session from the count */ + if (going_down) + count--; + + elog(LOG_INFO, "Session count: %lu", count); + /* + * execute the command if the user doesn't have any open sessions + */ + if (count == 0) { + char *managed = rc_user_value_get(pw->pw_name, "rundir_managed"); + + xasprintf(&cmd, "openrc --user %s", runlevel); + if (exec_user_cmd(pamh, pw, cmd) == -1) + ret = false; + + if (going_down && rc_yesno(managed)) + rm_dir(rundir, true); + + free(managed); + } + + /* If not going down, add ourselves to the count */ + if (!going_down) + count++; + + set_session_count(pw->pw_name, count); + +out: + free(cmd); + free(rundir); + closedir(lock); + 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"; + char *log_name; + 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; + + xasprintf(&log_name, "openrc-pam[%s]", pw->pw_name); + setenv("EINFO_LOG", log_name, 1); + free(log_name); + + elog(LOG_INFO, "Opening openrc session"); + + if (!exec_openrc(pamh, pw, runlevel, false)) { + elog(LOG_ERR, "Failed to close session"); + 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"; + char *log_name; + 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; + + xasprintf(&log_name, "openrc-pam [%s]", pw->pw_name); + setenv("EINFO_LOG", log_name, 1); + free(log_name); + + elog(LOG_INFO, "Closing openrc session"); + + if (!exec_openrc(pamh, pw, runlevel, true)) { + elog(LOG_ERR, "Failed to close session"); + ret = PAM_SESSION_ERR; + } + + unsetenv("EINFO_LOG"); + return ret; +} diff --git a/src/shared/misc.c b/src/shared/misc.c index f56bd33a0..15857466c 100644 --- a/src/shared/misc.c +++ b/src/shared/misc.c @@ -574,6 +574,54 @@ ls_dir(const char *dir, int options) return list; } +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 b48418c0a..e6f7d4cab 100644 --- a/src/shared/misc.h +++ b/src/shared/misc.h @@ -71,6 +71,7 @@ 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); RC_STRINGLIST *ls_dir(const char *dir, int options); +bool rm_dir(const char *pathname, bool top); void cloexec_fds_from(int);