From b56aa196712677d48cda389316da3c38cc260942 Mon Sep 17 00:00:00 2001 From: javalsai Date: Wed, 27 Aug 2025 19:31:46 +0200 Subject: [PATCH] chore: modularize PAM also helps with sanitizing the code flow when spawning the child, more code though --- .clang-tidy | 30 +++---- Makefile | 4 +- include/pam.h | 34 +++++++ include/util.h | 2 + src/auth.c | 234 ++++++++++++++----------------------------------- src/pam.c | 167 +++++++++++++++++++++++++++++++++++ src/util.c | 17 ++++ 7 files changed, 303 insertions(+), 185 deletions(-) create mode 100644 include/pam.h create mode 100644 src/pam.c diff --git a/.clang-tidy b/.clang-tidy index 9fa2b22..ab01ac3 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -16,32 +16,30 @@ Checks: > readability-*, -readability-braces-around-statements, -WarningsAsErrors: '' -HeaderFilterRegex: '.*' +WarningsAsErrors: "" +HeaderFilterRegex: ".*" FormatStyle: file CheckOptions: - - key: readability-magic-numbers.IgnoredIntegerValues - value: '0;1;2;3;10;255' - # - key: readability-magic-numbers.IgnoredValues - # value: '0;1;2;3;10;255' - - key: readability-identifier-naming.VariableCase - value: lower_case - - key: readability-identifier-naming.ConstantParameterCase - value: UPPER_CASE + - key: readability-magic-numbers.IgnoredIntegerValues + value: "0;1;2;3;10;255" + - key: readability-identifier-naming.VariableCase + value: lower_case + - key: readability-identifier-naming.ConstantParameterCase + value: UPPER_CASE - key: readability-identifier-naming.ConstantCase value: "UPPER_CASE" - key: readability-identifier-length.VariableThreshold - value: '2' + value: "2" - key: readability-identifier-length.ParameterThreshold - value: '2' + value: "2" - key: readability-identifier-length.LocalConstantThreshold - value: '2' + value: "2" - key: readability-identifier-length.MemberThreshold - value: '2' + value: "2" - key: readability-identifier-length.MinimumParameterNameLength - value: '2' + value: "2" - key: readability-identifier-length.MinimumVariableNameLength - value: '2' + value: "2" diff --git a/Makefile b/Makefile index 185e987..9556882 100644 --- a/Makefile +++ b/Makefile @@ -16,10 +16,10 @@ ALLFLAGS=$(CFLAGS) $(CPPFLAGS) -I$(IDIR) LIBS=-lpam -_DEPS = version.h log.h util.h ui.h ui_state.h config.h desktop.h desktop_exec.h auth.h ofield.h efield.h keys.h users.h sessions.h chvt.h macros.h launch_state.h +_DEPS = version.h log.h util.h ui.h ui_state.h config.h pam.h desktop.h desktop_exec.h auth.h ofield.h efield.h keys.h users.h sessions.h chvt.h macros.h launch_state.h DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS)) -_OBJ = main.o log.o util.o ui.o ui_state.o config.o desktop.o desktop_exec.o auth.o ofield.o efield.o users.o sessions.o chvt.o launch_state.o +_OBJ = main.o log.o util.o ui.o ui_state.o config.o pam.o desktop.o desktop_exec.o auth.o ofield.o efield.o users.o sessions.o chvt.o launch_state.o OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ)) INFO_GIT_REV?=$$(git describe --long --tags --always || echo '?') diff --git a/include/pam.h b/include/pam.h new file mode 100644 index 0000000..dc03299 --- /dev/null +++ b/include/pam.h @@ -0,0 +1,34 @@ +#ifndef PAM_H +#define PAM_H + +#include +#include +#include +#include + +#include "macros.h" +#include "sessions.h" + +#define PAMH_ERR_NOERR 0 +#define PAMH_ERR_ALLOC 1 +#define PAMH_ERR_ERRNO 2 +#define PAMH_ERR_NOERRNO 3 + +struct pamh_getenv_status { + char error_flag; + union { + char** envlist; + const char* errfn; + }; +}; + +// Doesn't include `source`s +struct pamh_getenv_status pamh_get_complete_env(pam_handle_t* handle, + char* NNULLABLE user, + struct passwd* NNULLABLE pw, + enum session_type session_typ); + +void free_envlist(char** NNULLABLE envlist); +pam_handle_t* NULLABLE get_pamh(char* NNULLABLE user, char* NNULLABLE passwd); + +#endif /* PAM_H */ diff --git a/include/util.h b/include/util.h index 066a91c..233e563 100644 --- a/include/util.h +++ b/include/util.h @@ -30,6 +30,8 @@ struct Vector { void** pages; }; +struct Vector vec_from_raw(void** raw); +void** vec_as_raw(struct Vector self); extern const struct Vector VEC_NEW; int vec_resize(struct Vector* self, size_t size); int vec_reserve(struct Vector* self, size_t size); diff --git a/src/auth.c b/src/auth.c index 0545d93..c076eb4 100644 --- a/src/auth.c +++ b/src/auth.c @@ -7,79 +7,24 @@ #include #include #include +#include #include "auth.h" #include "config.h" #include "desktop_exec.h" #include "log.h" +#include "pam.h" #include "sessions.h" #include "ui.h" -#include "unistd.h" #include "util.h" -int pam_conversation(int num_msg, const struct pam_message** msg, - struct pam_response** resp, void* appdata_ptr) { - struct pam_response* reply = - (struct pam_response*)malloc(sizeof(struct pam_response) * num_msg); - if (!reply) { - return PAM_BUF_ERR; +void try_source_file(struct Vector* NNULLABLE vec_envlist, char* filepath) { + log_printf("sourcing %s\n", filepath); + FILE* file2source = fopen(filepath, "r"); + if (file2source == NULL) { + log_printf("error sourcing %s\n", filepath); + return; } - for (size_t i = 0; i < num_msg; i++) { - reply[i].resp = NULL; - reply[i].resp_retcode = 0; - if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF || - msg[i]->msg_style == PAM_PROMPT_ECHO_ON) { - char* input = (char*)appdata_ptr; - reply[i].resp = strdup(input); - } - } - *resp = reply; - return PAM_SUCCESS; -} - -#ifndef PAM_SERVICE_FALLBACK - #define PAM_SERVICE_FALLBACK "login" -#endif - -#define CHECK_PAM_RET(call) \ - ret = (call); \ - if (ret != PAM_SUCCESS) { \ - pam_end(pamh, ret); \ - return NULL; \ - } - -void clear_screen() { - printf("\x1b[H\x1b[J"); -} - -pam_handle_t* get_pamh(char* user, char* passwd) { - pam_handle_t* pamh = NULL; - struct pam_conv pamc = {pam_conversation, (void*)passwd}; - int ret; - - char* pam_service_override = getenv("LIDM_PAM_SERVICE"); - char* pam_service_name = - pam_service_override ? pam_service_override : PAM_SERVICE_FALLBACK; - - CHECK_PAM_RET(pam_start(pam_service_name, user, &pamc, &pamh)) - CHECK_PAM_RET(pam_authenticate(pamh, 0)) - CHECK_PAM_RET(pam_acct_mgmt(pamh, 0)) - CHECK_PAM_RET(pam_setcred(pamh, PAM_ESTABLISH_CRED)) - CHECK_PAM_RET(pam_open_session(pamh, 0)) - CHECK_PAM_RET(pam_setcred(pamh, PAM_REINITIALIZE_CRED)) - - return pamh; -} -#undef CHECK_PAM_RET - -void* shmalloc(size_t size) { - return mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, - -1, 0); -} - -void sourceFileTry(char* file) { - FILE* file2source = fopen(file, "r"); - if (file2source == NULL) return; char* line = NULL; size_t len = 0; @@ -89,13 +34,10 @@ void sourceFileTry(char* file) { if (read == 0 || (read > 0 && *line == '#')) continue; if (line[read - 1] == '\n') line[read - 1] = '\0'; - /* printf("Retrieved line of length %zu:\n", read); */ - /* printf("%s\n", line); */ for (size_t i = 1; i < read; i++) { if (line[i] == '=') { - /* printf("FOUND '='!\n"); */ - line[i] = '\0'; - setenv(line, &line[i + 1], 1); + vec_push(vec_envlist, (void*)line); + line = NULL; break; } } @@ -105,66 +47,40 @@ void sourceFileTry(char* file) { (void)fclose(file2source); } -void moarEnv(char* user, struct session session, struct passwd* pw, - struct config* config) { - if (chdir(pw->pw_dir) == -1) print_errno("can't chdir to user home"); - - setenv("HOME", pw->pw_dir, true); - setenv("USER", pw->pw_name, true); - setenv("SHELL", pw->pw_shell, true); - // TERM - setenv("LOGNAME", pw->pw_name, true); - // MAIL? - - // PATH? - - char* xdg_session_type = "unknown"; - if (session.type == SHELL) xdg_session_type = "tty"; - if (session.type == XORG) xdg_session_type = "x11"; - if (session.type == WAYLAND) xdg_session_type = "wayland"; - setenv("XDG_SESSION_TYPE", xdg_session_type, true); - - printf("\n\n\n\n\x1b[1m"); - for (size_t i = 0; i < config->behavior.source.length; i++) { - /* printf("DEBUG(source)!!!! %d %s\n", i, (char*)vec_get(&behavior->source, - * i)); */ - sourceFileTry((char*)vec_get(&config->behavior.source, i)); +void source_paths(struct Vector* NNULLABLE vec_envlist, + struct Vector* NNULLABLE abs_source, + const char* NULLABLE user_home, + struct Vector* NNULLABLE user_source) { + for (size_t i = 0; i < abs_source->length; i++) { + char* path = vec_get(abs_source, i); + try_source_file(vec_envlist, path); } - /* printf("\n"); */ - if (pw->pw_dir) { - const char* home = pw->pw_dir; - size_t home_len = strlen(home); - - for (size_t i = 0; i < config->behavior.user_source.length; i++) { - const char* filename = (char*)vec_get(&config->behavior.user_source, i); - size_t filename_len = strlen(filename); - - size_t path_len = home_len + 1 + filename_len + 1; // nullbyte and slash - char* path = malloc(path_len); - if (!path) continue; // can't bother - - memcpy(path, home, home_len); - path[home_len] = '/'; // assume pw_dir doesn't start with '/' :P - memcpy(&path[home_len + 1], filename, filename_len); - path[path_len - 1] = '\0'; - - sourceFileTry(path); - free(path); + if (user_home) + for (size_t i = 0; i < user_source->length; i++) { + char* path = NULL; + asprintf(&path, "%s/%s", user_home, (char*)vec_get(user_source, i)); + if (!path) { + log_puts("alloc failure on user source\n"); + continue; + } + try_source_file(vec_envlist, path); } + else { + log_puts("user has no home\n"); } - - /*char *buf;*/ - /*size_t bsize = snprintf(NULL, 0, "/run/user/%d", pw->pw_uid) + 1;*/ - /*buf = malloc(bsize);*/ - /*snprintf(buf, bsize, "/run/user/%d", pw->pw_uid);*/ - /*setenv("XDG_RUNTIME_DIR", buf, true);*/ - /*setenv("XDG_SESSION_CLASS", "user", true);*/ - /*setenv("XDG_SESSION_ID", "1", true);*/ - /*setenv("XDG_SESSION_DESKTOP", , true);*/ - /*setenv("XDG_SEAT", "seat0", true);*/ } +/*char *buf;*/ +/*size_t bsize = snprintf(NULL, 0, "/run/user/%d", pw->pw_uid) + 1;*/ +/*buf = malloc(bsize);*/ +/*snprintf(buf, bsize, "/run/user/%d", pw->pw_uid);*/ +/*setenv("XDG_RUNTIME_DIR", buf, true);*/ +/*setenv("XDG_SESSION_CLASS", "user", true);*/ +/*setenv("XDG_SESSION_ID", "1", true);*/ +/*setenv("XDG_SESSION_DESKTOP", , true);*/ +/*setenv("XDG_SEAT", "seat0", true);*/ + // NOLINTBEGIN(readability-function-cognitive-complexity) bool launch(char* user, char* passwd, struct session session, void (*cb)(void), struct config* config) { @@ -196,41 +112,31 @@ bool launch(char* user, char* passwd, struct session session, void (*cb)(void), return false; } - bool* reach_session = shmalloc(sizeof(bool)); - if (reach_session == NULL) { - perror("error allocating shared memory"); + struct pamh_getenv_status env_ret = + pamh_get_complete_env(pamh, user, pw, session.type); + if (env_ret.error_flag != PAMH_ERR_NOERR) { + if (env_ret.error_flag == PAMH_ERR_ALLOC) { + print_err("allocator error"); + } else if (env_ret.error_flag == PAMH_ERR_ERRNO) { + print_errno(env_ret.errfn); + } else if (env_ret.error_flag == PAMH_ERR_NOERRNO) { + print_err(env_ret.errfn); + } + return false; + } + + struct Vector envlist_vec = vec_from_raw((void**)env_ret.envlist); + source_paths(&envlist_vec, &config->behavior.source, pw->pw_dir, + &config->behavior.user_source); + char** envlist = (char**)vec_as_raw(envlist_vec); + if (!envlist) { + print_err("vec alloc error"); return false; } - *reach_session = false; uint pid = fork(); if (pid == 0) { // child - char* term = NULL; - char* getterm = getenv("TERM"); - // TODO: handle malloc error - if (getterm != NULL) term = strdup(getterm); - if (clearenv() != 0) { - print_errno("clearenv"); - _exit(EXIT_FAILURE); - } - - char** envlist = pam_getenvlist(pamh); - if (envlist == NULL) { - print_errno("pam_getenvlist"); - _exit(EXIT_FAILURE); - } - for (size_t i = 0; envlist[i] != NULL; i++) { - putenv(envlist[i]); - } - // FIXME: path hotfix - putenv("PATH=/bin:/usr/bin"); - if (term != NULL) { - setenv("TERM", term, true); - free(term); - } - - free((void*)envlist); - moarEnv(user, session, pw, config); + if (chdir(pw->pw_dir) == -1) print_errno("can't chdir to user home"); // TODO: chown stdin to user // does it inherit stdin from parent and @@ -251,36 +157,30 @@ bool launch(char* user, char* passwd, struct session session, void (*cb)(void), _exit(EXIT_FAILURE); } - if (cb != NULL) cb(); + if (cb) cb(); - *reach_session = true; - - // TODO: test existence of executable with TryExec - printf("\x1b[0m"); - // NOLINTNEXTLINE(bugprone-branch-clone) + printf("\x1b[0m\x1b[H\x1b[J"); + (void)fflush(stdout); if (session.type == SHELL) { - clear_screen(); - (void)fflush(stdout); - execlp(session.exec, session.exec, NULL); + execle(session.exec, session.exec, NULL, envlist); } else if (session.type == XORG || session.type == WAYLAND) { - clear_screen(); - (void)fflush(stdout); + // TODO: test existence of executable with TryExec // NOLINTNEXTLINE - execvp(desktop_exec[0], desktop_exec); + execve(desktop_exec[0], desktop_exec, envlist); // NOLINTNEXTLINE free_parsed_args(desktop_count, desktop_exec); } perror("exec error"); (void)fputs("failure calling session\n", stderr); } else { - pid_t child_pid = (pid_t)pid; - waitpid(child_pid, NULL, 0); + int exit_code; + waitpid((pid_t)pid, &exit_code, 0); pam_setcred(pamh, PAM_DELETE_CRED); pam_close_session(pamh, 0); pam_end(pamh, PAM_SUCCESS); - if (*reach_session == false) return false; + if (exit_code != 0) return false; exit(0); } diff --git a/src/pam.c b/src/pam.c new file mode 100644 index 0000000..8802a1d --- /dev/null +++ b/src/pam.c @@ -0,0 +1,167 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "macros.h" +#include "pam.h" +#include "sessions.h" + +struct envpair { + const char* NNULLABLE name; + char* NULLABLE value; +}; + +char* NULLABLE make_env_kv(const char* NNULLABLE key, char* NNULLABLE value) { + char* buf = NULL; + asprintf(&buf, "%s=%s", key, value); + return buf; +} + +void free_envlist(char** NNULLABLE envlist) { + for (char** ptr = envlist; *ptr; ptr++) + free(*ptr); + free((void*)envlist); +} + +// NULL when allocation failure +// in any case, envlist would be freed after this function +char** NULLABLE merge_envlist(char** NNULLABLE envlist, struct envpair extra[], + size_t extra_len) { + size_t envlist_len = 0; + while (envlist[envlist_len]) + envlist_len++; + + size_t nonnullelems = 0; + for (size_t i = 0; i < extra_len; i++) { + if (extra[i].value) nonnullelems++; + } + + size_t new_envlist_len = envlist_len + nonnullelems + 1; + char** new_envlist = + (char**)realloc((void*)envlist, sizeof(char*) * new_envlist_len); + if (!new_envlist) { + free_envlist(envlist); + return NULL; + } + + // NOLINTNEXTLINE(readability-identifier-length) + size_t k = 0; + for (size_t i = 0; i < extra_len; i++) { + if (!extra[i].value) continue; + char* env_kv = make_env_kv(extra[i].name, extra[i].value); + if (!env_kv) goto free_new_envlist_extra; + new_envlist[envlist_len + k++] = env_kv; + } + + new_envlist[envlist_len + nonnullelems] = NULL; + return new_envlist; + +free_new_envlist_extra: + for (size_t j = 0; j < envlist_len + k; j++) { + free(new_envlist[envlist_len + j]); + } + free((void*)new_envlist); + return NULL; +} + +char* NULLABLE xdg_ssession_type_str(enum session_type typ) { + char* xdg_session_type = NULL; + if (typ == SHELL) xdg_session_type = "tty"; + if (typ == XORG) xdg_session_type = "x11"; + if (typ == WAYLAND) xdg_session_type = "wayland"; + return xdg_session_type; +} + +#define FAIL_ALLOC(status) \ + { \ + (status).error_flag = PAMH_ERR_ALLOC; \ + return (status); \ + } +#define FAIL(status, ERR, ERRFN) \ + { \ + (status).error_flag = (ERR); \ + (status).errfn = (ERRFN); \ + return (status); \ + } + +struct pamh_getenv_status pamh_get_complete_env(pam_handle_t* handle, + char* NNULLABLE user, + struct passwd* NNULLABLE pw, + enum session_type session_typ) { + struct pamh_getenv_status status; + char** envlist = pam_getenvlist(handle); + if (!envlist) FAIL(status, PAMH_ERR_ERRNO, "pam_getenvlist"); + + struct envpair extra_env[] = { + {"TERM", getenv("TERM")}, + {"PATH", getenv("PATH")}, + {"HOME", pw->pw_dir}, + {"USER", pw->pw_name}, + {"SHELL", pw->pw_shell}, + {"LOGNAME", pw->pw_name}, + {"XDG_SESSION_TYPE", xdg_ssession_type_str(session_typ)}}; + + status.error_flag = PAMH_ERR_NOERR; + status.envlist = merge_envlist(envlist, extra_env, LEN(extra_env)); + if (!status.envlist) FAIL_ALLOC(status); + + return status; +} + +#undef FAIL +#undef FAIL_ALLOC + +/////////////// + +int pam_conversation(int num_msg, const struct pam_message** msg, + struct pam_response** resp, void* appdata_ptr) { + struct pam_response* reply = malloc(sizeof(struct pam_response) * num_msg); + if (!reply) { + return PAM_BUF_ERR; + } + for (size_t i = 0; i < num_msg; i++) { + reply[i].resp = NULL; + reply[i].resp_retcode = 0; + if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF || + msg[i]->msg_style == PAM_PROMPT_ECHO_ON) { + char* input = (char*)appdata_ptr; + reply[i].resp = strdup(input); + } + } + *resp = reply; + return PAM_SUCCESS; +} + +#ifndef PAM_SERVICE_FALLBACK + #define PAM_SERVICE_FALLBACK "login" +#endif + +#define CHECK_PAM_RET(call) \ + ret = (call); \ + if (ret != PAM_SUCCESS) { \ + pam_end(pamh, ret); \ + return NULL; \ + } +pam_handle_t* get_pamh(char* user, char* passwd) { + pam_handle_t* pamh = NULL; + struct pam_conv pamc = {pam_conversation, (void*)passwd}; + int ret; + + char* pam_service_override = getenv("LIDM_PAM_SERVICE"); + char* pam_service_name = + pam_service_override ? pam_service_override : PAM_SERVICE_FALLBACK; + + CHECK_PAM_RET(pam_start(pam_service_name, user, &pamc, &pamh)) + CHECK_PAM_RET(pam_authenticate(pamh, 0)) + CHECK_PAM_RET(pam_acct_mgmt(pamh, 0)) + CHECK_PAM_RET(pam_setcred(pamh, PAM_ESTABLISH_CRED)) + CHECK_PAM_RET(pam_open_session(pamh, 0)) + CHECK_PAM_RET(pam_setcred(pamh, PAM_REINITIALIZE_CRED)) + + return pamh; +} +#undef CHECK_PAM_RET diff --git a/src/util.c b/src/util.c index 0b730ad..33a4330 100644 --- a/src/util.c +++ b/src/util.c @@ -147,6 +147,23 @@ const struct Vector VEC_NEW = { .pages = NULL, }; +struct Vector vec_from_raw(void** raw) { + size_t len = 0; + while (raw[len]) + len++; + + return (struct Vector){ + .length = len, + .capacity = len, + .pages = raw, + }; +} + +void** vec_as_raw(struct Vector self) { + if (vec_push(&self, NULL) != 0) return NULL; + return self.pages; +} + int vec_resize(struct Vector* self, size_t size) { void** new_location = (void**)realloc((void*)self->pages, size * sizeof(void*));