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, 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 <[email protected]>
  • Loading branch information
navi-desu committed May 4, 2024
1 parent d47f20e commit 5d43c43
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 48 deletions.
48 changes: 0 additions & 48 deletions src/librc/librc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/librc/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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',
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
231 changes: 231 additions & 0 deletions src/openrc-pam/openrc-pam.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
#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 <utmpx.h>

#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;
}
49 changes: 49 additions & 0 deletions src/shared/misc.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#endif

#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/shared/misc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down

0 comments on commit 5d43c43

Please sign in to comment.