From a97960623375078954cf551237fcf26be617bcbe Mon Sep 17 00:00:00 2001 From: "Anna (navi) Figueiredo Gomes" Date: Mon, 19 Jun 2023 23:40:27 -0300 Subject: openrc-pam: add loading user environment. openrc-pam now sets XDG_RUNTIME_DIR in case it's unset. after running openrc --user, it'll look at ${XDG_RUNTIME_DIR}/openrc/env for exported variables by services, and only set those allowed by RC_USER_ENV_WHITELIST and RC_USER_ENV_WHITELIST_D. this is to protect pam from variables being set under user control that could mess with other modules. Signed-off-by: Anna (navi) Figueiredo Gomes --- src/librc/rc.h.in | 3 + src/openrc-pam/openrc-pam.c | 202 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 171 insertions(+), 34 deletions(-) diff --git a/src/librc/rc.h.in b/src/librc/rc.h.in index 8b1c705a..f6700801 100644 --- a/src/librc/rc.h.in +++ b/src/librc/rc.h.in @@ -61,6 +61,9 @@ extern "C" { /*! @name Reserved runlevel names */ #define RC_LEVEL_USERNONE "none" +#define RC_USER_ENV_WHITELIST RC_SYS_USER_CONFDIR "/env_whitelist" +#define RC_USER_ENV_WHITELIST_D RC_SYS_USER_CONFDIR "/env_whitelist.d" + /*! Is openrc being ran in usermode? * @return true if yes, otherwise false */ bool rc_is_user(void); diff --git a/src/openrc-pam/openrc-pam.c b/src/openrc-pam/openrc-pam.c index a9b2e36b..40b9a6dc 100644 --- a/src/openrc-pam/openrc-pam.c +++ b/src/openrc-pam/openrc-pam.c @@ -1,48 +1,192 @@ #include #include #include +#include #include #include #include #include #include +#include #include "einfo.h" +#include "queue.h" + +static void load_envs_from_file(const char *path, RC_STRINGLIST *out) { + FILE *fp = NULL; + char *line = NULL; + size_t n = 0; + char *p = NULL; + + fp = fopen(path, "r"); + if (!fp) { + return; + } + while (getline(&line, &n, fp) != -1) { + if ((p = strchr(line, '\n'))) { + *p = '\0'; + }; + rc_stringlist_addu(out, line); + } + fclose(fp); +} + +static RC_STRINGLIST *load_dir(const char *dir) { + RC_STRINGLIST *list = rc_stringlist_new(); + DIR *dp = NULL; + struct dirent *d = NULL; + char *path; + + if ((dp = opendir(dir)) != NULL) { + while ((d = readdir(dp)) != NULL) { + xasprintf(&path, "%s/%s", dir, d->d_name); + load_envs_from_file(path, list); + } + } + closedir(dp); + + return list; +} + +static void set_user_env(pam_handle_t *pamh) { + RC_STRINGLIST *allowed_env = NULL; + RC_STRINGLIST *user_env = NULL; + RC_STRING *env; + RC_STRING *uenv; + char *p; + char *user_env_path; + + pam_syslog(pamh, LOG_INFO, "Loading allowed envs in %s", RC_USER_ENV_WHITELIST_D); + allowed_env = load_dir(RC_USER_ENV_WHITELIST_D); + + pam_syslog(pamh, LOG_INFO, "Loading allowed envs in %s", RC_USER_ENV_WHITELIST); + load_envs_from_file(RC_USER_ENV_WHITELIST, allowed_env); + + xasprintf(&user_env_path, "%s/openrc/env", pam_getenv(pamh, "XDG_RUNTIME_DIR")); + + pam_syslog(pamh, LOG_INFO, "Loading user envs in %s", user_env_path); + user_env = load_dir(user_env_path); + + TAILQ_FOREACH(env, allowed_env, entries) { + pam_syslog(pamh, LOG_INFO, "allowed env %s", env->value); + TAILQ_FOREACH(uenv, user_env, entries) { + p = strchr(uenv->value, '='); + if (p) { + *p = '\0'; + if (strcmp(env->value, uenv->value) == 0) { + *p = '='; + pam_syslog(pamh, LOG_INFO, "Exporting: %s", uenv->value); + pam_putenv(pamh, uenv->value); + } else { + *p = '='; + } + } + } + } + + pam_syslog(pamh, LOG_INFO, "Finished loading user environment"); + + rc_stringlist_free(allowed_env); + rc_stringlist_free(user_env); + free(user_env_path); +} + +static int +exec_user_cmd(struct passwd *pw, char *cmd, char **envlist) +{ + int retval; + const char *shellname = basename_c(pw->pw_shell); + + switch (fork()) { + case 0: + setgid(pw->pw_gid); + setuid(pw->pw_uid); + + execle(pw->pw_shell, shellname, "-c", cmd, NULL, envlist); + + return -1; + break; + case -1: + return -1; + break; + } + wait(&retval); + return retval; +} + +static char *create_xdg_runtime_dir(struct passwd *pw) { + char *path = NULL; + char *openrc_path = NULL; + + if (mkdir("/run/user", 0755) != 0 && errno != EEXIST) + return NULL; + + xasprintf(&path, "/run/user/%d/", pw->pw_uid); + + if (mkdir(path, 0700) != 0 && errno != EEXIST) { + free(path); + return NULL; + } + + if (chown(path, pw->pw_uid, pw->pw_gid) != 0) { + free(path); + return NULL; + } + + xasprintf(&openrc_path, "%s/%s", path, "openrc"); + + if (mkdir(openrc_path, 0700) != 0 && errno != EEXIST) { + free(openrc_path); + free(path); + return NULL; + } + + if (chown(openrc_path, pw->pw_uid, pw->pw_gid) != 0) { + free(openrc_path); + free(path); + return NULL; + } + + return path; +} static bool exec_openrc(pam_handle_t *pamh, const char *runlevel, bool lock) { char *cmd = NULL; const char *username; struct passwd *pw = NULL; + char *xdg_runtime_dir; + char *xdg_runtime_dir_env; char **envlist; char **env; - envlist = pam_getenvlist(pamh); - if (pam_get_user(pamh, &username, "username:") != PAM_SUCCESS) return false; pw = getpwnam(username); if (!pw) return false; - elog(LOG_INFO, "Executing %s runlevel for user %s", runlevel, username); + /* XDG_RUNTIME_DIR is needed in so many places, that if it's not defined + * we're defining it on the standard location. */ + if (pam_getenv(pamh, "XDG_RUNTIME_DIR") == NULL) { + xdg_runtime_dir = create_xdg_runtime_dir(pw); + if (!xdg_runtime_dir) { + return false; + } - xasprintf(&cmd, "openrc --user %s %s", lock ? "--lock" : "--unlock", runlevel); - switch (fork()) { - case 0: - setgid(pw->pw_gid); - setuid(pw->pw_uid); + xasprintf(&xdg_runtime_dir_env, "XDG_RUNTIME_DIR=%s", xdg_runtime_dir); + pam_putenv(pamh, xdg_runtime_dir_env); + pam_syslog(pamh, LOG_INFO, "exporting: %s", xdg_runtime_dir_env); + free(xdg_runtime_dir); + free(xdg_runtime_dir_env); + } + + envlist = pam_getenvlist(pamh); - execle(pw->pw_shell, "-", "-c", cmd, NULL, envlist); + xasprintf(&cmd, "openrc --user %s %s", lock ? "--lock" : "--unlock", runlevel); + pam_syslog(pamh, LOG_INFO, "Executing %s for user %s", cmd, username); + exec_user_cmd(pw, cmd, envlist); - free(cmd); - return false; - break; - case -1: - free(cmd); - return false; - break; - } - wait(NULL); + set_user_env(pamh); for (env = envlist; *env; env++) free(*env); @@ -55,18 +199,13 @@ PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, cons const char *runlevel = argc > 0 ? runlevel = argv[0] : "default"; (void)flags; - setenv("EINFO_LOG", "openrc-pam", 1); - elog(LOG_INFO, "Opening openrc session"); + pam_syslog(pamh, LOG_INFO, "Opening openrc session"); if (exec_openrc(pamh, runlevel, true)) { - elog(LOG_INFO, "Openrc session opened"); - unsetenv("RC_PAM_STARTING"); - unsetenv("EINFO_LOG"); + pam_syslog(pamh, LOG_INFO, "Openrc session opened"); return PAM_SUCCESS; } else { - elog(LOG_ERR, "Failed to open session"); - unsetenv("RC_PAM_STARTING"); - unsetenv("EINFO_LOG"); + pam_syslog(pamh, LOG_ERR, "Failed to open session"); return PAM_SESSION_ERR; } } @@ -75,18 +214,13 @@ PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, con const char *runlevel = argc > 1 ? argv[1] : "none"; (void)flags; - setenv("EINFO_LOG", "openrc-pam", 1); - elog(LOG_INFO, "Closing openrc session"); + pam_syslog(pamh, LOG_INFO, "Closing openrc session"); if (exec_openrc(pamh, runlevel, false)) { - elog(LOG_INFO, "Openrc session closed"); - unsetenv("RC_PAM_STOPPING"); - unsetenv("EINFO_LOG"); + pam_syslog(pamh, LOG_INFO, "Openrc session closed"); return PAM_SUCCESS; } else { - elog(LOG_ERR, "Failed to close session"); - unsetenv("RC_PAM_STOPPING"); - unsetenv("EINFO_LOG"); + pam_syslog(pamh, LOG_ERR, "Failed to close session"); return PAM_SESSION_ERR; } } -- cgit v1.2.3