Skip to content

Commit

Permalink
openrc-pam: Add pam module to autolaunch openrc --user
Browse files Browse the repository at this point in the history
the module sets up XDG_RUNTIME_DIR if it's already not set, keeps a lock
file in RC_SVCDIR/users/<username>/session_count, and then starts or
stops openrc --user.

Signed-off-by: Anna (navi) Figueiredo Gomes <[email protected]>
  • Loading branch information
navi-desu committed May 24, 2024
1 parent 49bd7f0 commit 9b136cc
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 49 deletions.
5 changes: 4 additions & 1 deletion init.d/bootmisc.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 0 additions & 48 deletions src/librc/librc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
11 changes: 11 additions & 0 deletions src/openrc-pam/meson.build
Original file line number Diff line number Diff line change
@@ -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
293 changes: 293 additions & 0 deletions src/openrc-pam/openrc-pam.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
#include <librc.h>
#include <pwd.h>
#include <grp.h>
#include <security/pam_modules.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/file.h>
#include <syslog.h>
#include <unistd.h>

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

0 comments on commit 9b136cc

Please sign in to comment.