From 7e7a297e2e3ee4c7c29446fe7e0d18b7acc3e24a Mon Sep 17 00:00:00 2001 From: javalsai Date: Mon, 19 Jan 2026 21:17:45 +0100 Subject: [PATCH] feat: support Xorg & better auth logic (#80) Co-authored-by: grialion <48643945+grialion@users.noreply.github.com> --- .clang-tidy | 34 +-- Makefile | 13 +- README.md | 2 +- include/auth.h | 5 +- include/config.h | 22 +- include/desktop_exec.h | 12 +- include/keys.h | 4 +- include/macros.h | 9 +- include/pam.h | 33 +++ include/sessions.h | 58 ++++- include/signal_handler.h | 8 + include/ui.h | 4 +- include/ui_state.h | 4 +- include/util.h | 14 +- src/auth.c | 515 +++++++++++++++++++++------------------ src/chvt.c | 7 +- src/config.c | 8 +- src/desktop.c | 3 +- src/desktop_exec.c | 59 +++++ src/efield.c | 4 - src/launch_state.c | 10 +- src/main.c | 4 + src/pam.c | 192 +++++++++++++++ src/sessions.c | 39 ++- src/signal_handler.c | 29 +++ src/ui.c | 59 +++-- src/ui_state.c | 12 +- src/util.c | 30 ++- 28 files changed, 842 insertions(+), 351 deletions(-) create mode 100644 include/pam.h create mode 100644 include/signal_handler.h create mode 100644 src/pam.c create mode 100644 src/signal_handler.c diff --git a/.clang-tidy b/.clang-tidy index 9fa2b22..39c2af4 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -16,32 +16,34 @@ 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-naming.EnumCase + value: "CamelCase" + - key: readability-identifier-naming.FunctionCase + value: "lower_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 25ad129..0f6460d 100644 --- a/Makefile +++ b/Makefile @@ -9,17 +9,18 @@ ODIR=dist PREFIX=/usr CC?=gcc -CFLAGS?=-O3 -Wall +CFLAGS?=-O3 -Wall -Wextra -fdata-sections -ffunction-sections # C PreProcessor flags, not C Plus Plus CPPFLAGS?= ALLFLAGS=$(CFLAGS) $(CPPFLAGS) -I$(IDIR) +LDFLAGS?=-Wl,--gc-sections 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 signal_handler.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 signal_handler.o OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ)) INFO_GIT_REV?=$$(git describe --long --tags --always || echo '?') @@ -41,7 +42,7 @@ $(ODIR)/%.o: $(CDIR)/%.c $(DEPS) $(CC) -c -o $@ $< $(ALLFLAGS) lidm: $(OBJ) - $(CC) -o $@ $^ $(ALLFLAGS) $(LIBS) + $(CC) -o $@ $^ $(ALLFLAGS) $(LIBS) $(LDFLAGS) clean: rm -f $(ODIR)/*.o lidm @@ -137,8 +138,8 @@ pre-commit: prettier -c "**/*.md" git ls-files "*.sh" "*/PKGBUILD" | xargs shellcheck --shell=bash clang-format -i $$(git ls-files "*.c" "*.h") - git ls-files -z "*.h" | \ - parallel -j$$(nproc) -q0 --no-notice --will-cite --tty clang-tidy --quiet |& \ + git ls-files -z "*.c" "*.h" | \ + parallel -j$$(nproc) -q0 --no-notice --will-cite --tty clang-tidy -warnings-as-errors=\* --quiet |& \ grep -v "warnings generated." || true print-version: diff --git a/README.md b/README.md index fc700d3..ecbea96 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ kmscon -l --vt /dev/tty7 --font-name "Cascadia Code" -- /usr/bin/lidm - Simple as C, meant to depend only on standard libc and basic unix system headers. - Fully customizable: ALL strings, colors (with its ANSI keys) and most behavior. -- Xorg[\*](https://github.com/javalsai/lidm/issues/79) and wayland sessions, while supporting the default user shell (if enabled in config) +- Experimental Xorg support[\*](https://github.com/javalsai/lidm/pull/80?#issuecomment-3764073217) and wayland sessions, while supporting the default user shell (if enabled in config) - Init agnostinc (systemd, dinit, runit, openrc and s6). - Supports [fido yubikeys](./docs/yubikey.md) (via pam_u2f). diff --git a/include/auth.h b/include/auth.h index 2464a86..3401e24 100644 --- a/include/auth.h +++ b/include/auth.h @@ -6,7 +6,8 @@ #include "config.h" #include "sessions.h" -bool launch(char* user, char* passwd, struct session session, void (*cb)(void), - struct config* config); +bool launch(char* NNULLABLE user, char* NNULLABLE passwd, + struct session session, void (*NULLABLE cb)(void), + struct config* NNULLABLE config); #endif diff --git a/include/config.h b/include/config.h index f06d3e6..bb7e5f0 100644 --- a/include/config.h +++ b/include/config.h @@ -8,7 +8,7 @@ #include "macros.h" #include "util.h" -enum introspection_type { +enum IntrospectionType { STRING, BOOL, NUMBER, @@ -26,7 +26,7 @@ static const char* NNULLABLE const INTROS_TYS_NAMES[] = { struct introspection_item { char* NNULLABLE name; size_t offset; - enum introspection_type typ; + enum IntrospectionType typ; }; #define INTROS_ITEM(key, table, ty) \ @@ -90,10 +90,10 @@ BUILD(colors, COLORS, TABLE_COLORS); BUILD(chars, CHARS, TABLE_CHARS); #define TABLE_FUNCTIONS(F, name) \ - F(enum keys, poweroff, KEY, F1, name) \ - F(enum keys, reboot, KEY, F2, name) \ - F(enum keys, fido, KEY, NONE, name) \ - F(enum keys, refresh, KEY, F5, name) + F(enum Keys, poweroff, KEY, F1, name) \ + F(enum Keys, reboot, KEY, F2, name) \ + F(enum Keys, fido, KEY, NONE, name) \ + F(enum Keys, refresh, KEY, F5, name) BUILD(functions, FUNCTIONS, TABLE_FUNCTIONS); @@ -152,15 +152,15 @@ struct introspection_table { static const struct introspection_table CONFIG_INSTROSPECTION[] = { {"colors", offsetof(struct config, colors), INTROS_TABLE_COLORS, - sizeof(INTROS_TABLE_COLORS) / sizeof(INTROS_TABLE_COLORS[0])}, + LEN(INTROS_TABLE_COLORS)}, {"chars", offsetof(struct config, chars), INTROS_TABLE_CHARS, - sizeof(INTROS_TABLE_CHARS) / sizeof(INTROS_TABLE_CHARS[0])}, + LEN(INTROS_TABLE_CHARS)}, {"functions", offsetof(struct config, functions), INTROS_TABLE_FUNCTIONS, - sizeof(INTROS_TABLE_FUNCTIONS) / sizeof(INTROS_TABLE_FUNCTIONS[0])}, + LEN(INTROS_TABLE_FUNCTIONS)}, {"strings", offsetof(struct config, strings), INTROS_TABLE_STRINGS, - sizeof(INTROS_TABLE_STRINGS) / sizeof(INTROS_TABLE_STRINGS[0])}, + LEN(INTROS_TABLE_STRINGS)}, {"behavior", offsetof(struct config, behavior), INTROS_TABLE_BEHAVIOR, - sizeof(INTROS_TABLE_BEHAVIOR) / sizeof(INTROS_TABLE_BEHAVIOR[0])}, + LEN(INTROS_TABLE_BEHAVIOR)}, }; //// FUNCTIONS diff --git a/include/desktop_exec.h b/include/desktop_exec.h index 2bc03e6..c0d7a7c 100644 --- a/include/desktop_exec.h +++ b/include/desktop_exec.h @@ -1,7 +1,15 @@ -#ifndef DESKTOP_EXEC_H_ -#define DESKTOP_EXEC_H_ +// TODO: rewrite properly +// NOLINTBEGIN(clang-diagnostic-nullability-completeness) +#ifndef DESKTOP_EXEC_H_ + #define DESKTOP_EXEC_H_ + + #include "macros.h" + +char* NULLABLE search_path(const char* NNULLABLE for_binary); +int execvpe_desktop(char** args, char* NNULLABLE* NNULLABLE envlist); int parse_exec_string(const char* exec_s, int* arg_count, char*** args); void free_parsed_args(int arg_count, char** args); #endif +// NOLINTEND(clang-diagnostic-nullability-completeness) diff --git a/include/keys.h b/include/keys.h index fa084fb..6bb1338 100644 --- a/include/keys.h +++ b/include/keys.h @@ -3,7 +3,7 @@ #include -enum keys { +enum Keys { ESC, F1, F2, @@ -68,7 +68,7 @@ static const char* const KEY_NAMES[] = { }; struct key_mapping { - enum keys key; + enum Keys key; const char* sequences[3]; }; diff --git a/include/macros.h b/include/macros.h index 9915636..7ba3f79 100644 --- a/include/macros.h +++ b/include/macros.h @@ -7,25 +7,25 @@ #endif // Do we just replace the compiler with clang?? -#if defined(__clang__) +#ifdef __clang__ #define NULLABLE _Nullable #else #define NULLABLE #endif -#if defined(__clang__) +#ifdef __clang__ #define NNULLABLE _Nonnull #else #define NNULLABLE #endif -#if defined(__clang__) +#ifdef __clang__ #define UNULLABLE _Null_unspecified #else #define UNULLABLE #endif -#if defined(__clang__) +#ifdef __clang__ #define COMPILER_VERSION __VERSION__ #elif defined(__GNUC__) #define xstr(s) str(s) @@ -36,5 +36,6 @@ #endif #define LEN(X) (sizeof(X) / sizeof((X)[0])) +#define UNUSED(x) ((void)(x)) #endif diff --git a/include/pam.h b/include/pam.h new file mode 100644 index 0000000..80a3a76 --- /dev/null +++ b/include/pam.h @@ -0,0 +1,33 @@ +#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* NULLABLE* NNULLABLE envlist; + const char* NNULLABLE errfn; + }; +}; + +// Doesn't include `source`s +struct pamh_getenv_status pamh_get_complete_env(pam_handle_t* NNULLABLE handle, + struct passwd* NNULLABLE pw, + enum SessionType session_typ); + +void free_envlist(char* NULLABLE* NNULLABLE envlist); +pam_handle_t* NULLABLE get_pamh(char* NNULLABLE user, char* NNULLABLE passwd); + +#endif /* PAM_H */ diff --git a/include/sessions.h b/include/sessions.h index d10eaee..cd4153b 100644 --- a/include/sessions.h +++ b/include/sessions.h @@ -2,21 +2,71 @@ #define SESSIONSH_ #include +#include +#include "desktop_exec.h" #include "macros.h" #include "util.h" -enum session_type { +enum SessionType { XORG, WAYLAND, SHELL, }; +enum ExecType { + EXEC_SHELL, + EXEC_DESKTOP, +}; + +struct desktop_exec { + char* NULLABLE* NNULLABLE args; + int arg_count; +}; + +struct session_exec { + enum ExecType typ; + union { + char* NNULLABLE shell; + struct desktop_exec desktop; + }; +}; + +static inline struct session_exec session_exec_shell(char* NNULLABLE shell) { + return (struct session_exec){ + .typ = EXEC_SHELL, + .shell = shell, + }; +} + +static inline struct session_exec session_exec_desktop( + int arg_count, char* NULLABLE* NNULLABLE args) { + return (struct session_exec){ + .typ = EXEC_DESKTOP, + .desktop = + { + .args = args, + .arg_count = arg_count, + }, + }; +} + +static inline int session_exec_exec(struct session_exec* NNULLABLE exec, + char* NULLABLE* NNULLABLE envlist) { + switch (exec->typ) { + case EXEC_SHELL: + return execle(exec->shell, exec->shell, NULL, envlist); + case EXEC_DESKTOP: + return execvpe_desktop(exec->desktop.args, envlist); + default: + __builtin_unreachable(); + } +} + struct session { char* NNULLABLE name; - char* NNULLABLE exec; - char* NULLABLE tryexec; - enum session_type type; + struct session_exec exec; + enum SessionType type; }; struct Vector get_avaliable_sessions(); diff --git a/include/signal_handler.h b/include/signal_handler.h new file mode 100644 index 0000000..409047f --- /dev/null +++ b/include/signal_handler.h @@ -0,0 +1,8 @@ +#ifndef SIGNALHANDLERH_ +#define SIGNALHANDLERH_ + +// handle SIGTERM by sending SIGTERM to all children, resulting +// in a graceful graphical shutdown +void setup_sigterm(); + +#endif /* SIGNALHANDLERH_ */ diff --git a/include/ui.h b/include/ui.h index e9f15f2..e41e84a 100644 --- a/include/ui.h +++ b/include/ui.h @@ -39,7 +39,7 @@ #define VALUES_SEPR 3 #define VALUE_MAXLEN (BOX_WIDTH - VALUES_COL + 1 - BOX_HMARGIN - 2) -enum input { SESSION, USER, PASSWD }; +enum Input { SESSION, USER, PASSWD }; extern const u_char INPUTS_N; void setup(struct config* config); @@ -49,7 +49,7 @@ void print_errno(const char* /*descr*/); void print_pam_msg(const char* msg, int msg_style); void clear_pam_msg(void); -void ui_update_field(enum input focused_input); +void ui_update_field(enum Input focused_input); void ui_update_ffield(); void ui_update_ofield(struct opts_field* self); void ui_update_cursor_focus(); diff --git a/include/ui_state.h b/include/ui_state.h index 52e6df4..cde6e76 100644 --- a/include/ui_state.h +++ b/include/ui_state.h @@ -6,7 +6,7 @@ #include "macros.h" #include "ui.h" -extern enum input focused_input; +extern enum Input focused_input; extern struct opts_field of_session; extern struct opts_field of_user; @@ -15,7 +15,7 @@ extern struct opts_field of_passwd; extern struct Vector* UNULLABLE gusers; extern struct Vector* UNULLABLE gsessions; -struct opts_field* NNULLABLE get_opts_field(enum input from); +struct opts_field* NNULLABLE get_opts_field(enum Input from); struct opts_field* NNULLABLE get_opts_ffield(); struct user st_user(); diff --git a/include/util.h b/include/util.h index 066a91c..f88130c 100644 --- a/include/util.h +++ b/include/util.h @@ -10,12 +10,18 @@ #include "keys.h" -int find_keyname(enum keys* at, const char* name); -enum keys find_ansi(const char* seq); +int find_keyname(enum Keys* at, const char* name); +struct option_keys { + bool is_some; + enum Keys key; +}; +struct option_keys find_ansi(const char* seq); void read_press(u_char* length, char* out); // non blocking, waits up to tv or interrupt, returns true if actually read bool read_press_nb(u_char* length, char* out, struct timeval* tv); +// UTF8 +// bool utf8_iscont(char byte); size_t utf8len(const char* str); size_t utf8len_until(const char* str, const char* until); @@ -24,12 +30,16 @@ const char* utf8back(const char* str); const char* utf8seek(const char* str); const char* utf8seekn(const char* str, size_t n); +// Vector +// struct Vector { uint32_t length; uint32_t capacity; 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 e18c309..0be732b 100644 --- a/src/auth.c +++ b/src/auth.c @@ -1,114 +1,39 @@ +// TODO: handle `fork() == -1`// TODO: handle `fork() == -1`s + +#include #include #include #include +#include #include #include #include #include #include +#include #include +#include #include "auth.h" #include "config.h" -#include "desktop_exec.h" #include "log.h" +#include "macros.h" +#include "pam.h" #include "sessions.h" #include "ui.h" -#include "unistd.h" #include "util.h" -struct pam_conv_data { - char* password; - void (*display_pam_msg)(const char* msg, int msg_style); -}; +#define XORG_MESSAGE_LENGTH 16 -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; +static void try_source_file(struct Vector* NNULLABLE vec_envlist, + char* NNULLABLE filepath) { + log_printf("sourcing %s\n", filepath); + FILE* file2source = fopen(filepath, "r"); + if (file2source == NULL) { + log_printf("error sourcing %s\n", filepath); + return; } - struct pam_conv_data* conv_data = (struct pam_conv_data*)appdata_ptr; - - for (size_t i = 0; i < num_msg; i++) { - reply[i].resp = NULL; - reply[i].resp_retcode = 0; - - switch (msg[i]->msg_style) { - case PAM_PROMPT_ECHO_OFF: - case PAM_PROMPT_ECHO_ON: - reply[i].resp = strdup(conv_data->password); - if (!reply[i].resp) { - for (size_t j = 0; j < i; j++) - free(reply[j].resp); - free(reply); - return PAM_BUF_ERR; - } - break; - - case PAM_TEXT_INFO: - case PAM_ERROR_MSG: - if (conv_data->display_pam_msg && msg[i]->msg) { - conv_data->display_pam_msg(msg[i]->msg, msg[i]->msg_style); - } - break; - - default: - break; - } - } - *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_data conv_data = {.password = passwd, - .display_pam_msg = print_pam_msg}; - struct pam_conv pamc = {pam_conversation, (void*)&conv_data}; - 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; ssize_t read; @@ -117,13 +42,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++) { + for (ssize_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; } } @@ -133,85 +55,235 @@ 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)); +static 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); + 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); free(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);*/ + +struct child_msg { + char* msg; + int _errno; + bool err; +}; + +/// block until X returns the display number or an error occurs +static bool x_get_display(const int xorg_pipefd[2], int* display) { + char buffer[XORG_MESSAGE_LENGTH]; + bool status; + + close(xorg_pipefd[1]); + ssize_t bytes_read = read(xorg_pipefd[0], buffer, sizeof(buffer) - 1); + buffer[bytes_read] = '\0'; + + if (bytes_read > 0) { + char* endptr; + int val = (int)strtol(buffer, &endptr, 10); + if (endptr == buffer) { + (void)fputs("failed to parse Xorg display response\n", stderr); + status = false; + } else { + *display = val; + status = true; + } + } else if (bytes_read == 0) { + (void)fputs("Xorg pipe closed\n", stderr); + status = false; + } else { + perror("read"); + status = false; } - /*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);*/ + close(xorg_pipefd[0]); + return status; } +/// small helper to push dyn arr +static void push_dyn_arr(void*** arr, void* item) { + struct Vector vec = vec_from_raw(*arr); + vec_push(&vec, item); + *arr = vec_as_raw(vec); +} + +// TODO: properly pass this down +extern int vt; + +static void start_xorg_server(struct passwd* pw, char** NNULLABLE envlist, + int xorg_pipefd[2]) { + close(xorg_pipefd[0]); + if (!pw->pw_dir) _exit(EXIT_FAILURE); + // !!!!!!!!!! ATTENTION: this fails silently, of course add failure msgs but + // for now I can't so be careful + if (vt == -1) _exit(EXIT_FAILURE); + + // pass the pipe so Xorg can write the DISPLAY value in there + char* fd_str; + asprintf(&fd_str, "%d", xorg_pipefd[1]); + if (!fd_str) _exit(EXIT_FAILURE); + + char* vt_path; + asprintf(&vt_path, "vt%d", vt); + if (!vt_path) { + free(fd_str); + _exit(EXIT_FAILURE); + } + + char* xorg_path = search_path("Xorg"); + if (!xorg_path) { + (void)fputs("couldn't find Xorg binary in PATH, sure it's installed?\n", + stderr); + _exit(EXIT_FAILURE); + } + + int exit = execle(xorg_path, xorg_path, "-displayfd", fd_str, vt_path, NULL, + envlist); + perror("exec"); + + free(vt_path); + free(fd_str); + free(xorg_path); + _exit(exit); +} + +// TODO: add error msgs +/// returns on completion +static void launch_with_xorg_server(struct session_exec* NNULLABLE exec, + struct passwd* pw, + char** NNULLABLE envlist) { + int xorg_pipefd[2]; + if (pipe(xorg_pipefd) == -1) _exit(EXIT_FAILURE); + + (void)fflush(NULL); + pid_t xorg_pid = fork(); + if (xorg_pid == 0) { + start_xorg_server(pw, envlist, xorg_pipefd); + } + + int display = 0; + if (!x_get_display(xorg_pipefd, &display)) { + (void)fputs("failed to get X display, aborting\n", stderr); + int status; + waitpid(xorg_pid, &status, 0); + _exit(EXIT_FAILURE); + } + + char* display_env; + asprintf(&display_env, "DISPLAY=:%d", display); + if (!display_env) { + (void)fputs("failure allocating memory for DISPLAY string\n", stderr); + _exit(EXIT_FAILURE); + } + // convert back for convenient push-ing + push_dyn_arr((void***)&envlist, display_env); + if (!envlist) { + (void)fputs("failure allocating memory for DISPLAY env\n", stderr); + _exit(EXIT_FAILURE); + } + + (void)fflush(NULL); + pid_t xorg_session_pid = fork(); + if (xorg_session_pid == 0) { + int exit = session_exec_exec(exec, envlist); + perror("exec error"); + (void)fputs("failure calling session\n", stderr); + _exit(exit); + } + + // looks weird, waiting on -1 should wait on any child and then just check if + // its xorg server or the session and kill the other waiting on it + pid_t pid; + int status; // not even read for now + while ((pid = waitpid(-1, &status, 0)) > 0) { + if (pid == xorg_pid || pid == xorg_session_pid) { + pid_t pid_to_kill = pid ^ xorg_pid ^ xorg_session_pid; + if (pid == xorg_pid) printf("Xorg server died\n"); + if (pid == xorg_session_pid) printf("Xorg session died\n"); + + kill(pid_to_kill, SIGTERM); + waitpid(pid_to_kill, &status, 0); + + break; + } + } +} + +#define SEND_MSG(MSG) \ + { \ + write(pipefd[1], &(MSG), sizeof(struct child_msg)); \ + close(pipefd[1]); \ + } +#define SEND_ERR(MSG) \ + { \ + write(pipefd[1], \ + &(struct child_msg){.msg = (MSG), ._errno = errno, .err = true}, \ + sizeof(struct child_msg)); \ + close(pipefd[1]); \ + _exit(EXIT_FAILURE); \ + } +#define DUMMY_READ() \ + { \ + char _dummy; \ + read(pipefd[0], &_dummy, sizeof(_dummy)); \ + } +inline static void forked(int pipefd[2], struct passwd* pw, + char* NNULLABLE user, + struct session* NNULLABLE session, + char** NNULLABLE envlist) { + if (chdir(pw->pw_dir) == -1) SEND_ERR("chdir"); + if (setgid(pw->pw_gid) == -1) SEND_ERR("setgid"); + if (initgroups(user, pw->pw_gid) == -1) SEND_ERR("initgroups"); + if (setuid(pw->pw_uid) == -1) SEND_ERR("setuid"); + + SEND_MSG((struct child_msg){.err = false}); + DUMMY_READ(); + close(pipefd[0]); + + if (session->type == XORG) { + launch_with_xorg_server(&session->exec, pw, envlist); + _exit(EXIT_SUCCESS); + } else { + int exit = session_exec_exec(&session->exec, envlist); + perror("exec error"); + (void)fputs("failure calling session\n", stderr); + _exit(exit); + } +} +#undef SEND_MSG +#undef SEND_ERR +#undef DUMMY_READ + // NOLINTBEGIN(readability-function-cognitive-complexity) bool launch(char* user, char* passwd, struct session session, void (*cb)(void), struct config* config) { - char** desktop_exec; - int desktop_count; - - if (session.type != SHELL) { - desktop_exec = NULL; - int parse_status = - parse_exec_string(session.exec, &desktop_count, &desktop_exec); - if (parse_status != 0 || desktop_count == 0 || !desktop_exec[0]) { - print_err("failure parsing exec string"); - log_printf("failure parsing exec string '%s': %d\n", - session.exec ? session.exec : "NULL", parse_status); - free_parsed_args(desktop_count, desktop_exec); - return false; - } - } - struct passwd* pw = getpwnam(user); if (pw == NULL) { print_err("could not get user info"); @@ -225,91 +297,58 @@ bool launch(char* user, char* passwd, struct session session, void (*cb)(void), } clear_pam_msg(); - 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, 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; } - *reach_session = 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; + } + + int pipefd[2]; + pipe(pipefd); 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); + if (pid == 0) + forked(pipefd, pw, user, &session, envlist); + else { + struct child_msg msg; + read(pipefd[0], &msg, sizeof(struct child_msg)); + close(pipefd[0]); + if (msg.err) { + errno = msg._errno; + print_errno(msg.msg); + return false; } - 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); - } + if (cb) cb(); + printf("\x1b[0m\x1b[H\x1b[J"); + (void)fflush(stdout); + close(pipefd[1]); - free((void*)envlist); - moarEnv(user, session, pw, config); - - // TODO: chown stdin to user - // does it inherit stdin from parent and - // does parent need to reclaim it after - // this dies? - - if (setgid(pw->pw_gid) == -1) { - print_errno("setgid"); - _exit(EXIT_FAILURE); - } - if (initgroups(user, pw->pw_gid) == -1) { - print_errno("initgroups"); - _exit(EXIT_FAILURE); - } - - if (setuid(pw->pw_uid) == -1) { - perror("setuid"); - _exit(EXIT_FAILURE); - } - - if (cb != NULL) cb(); - - *reach_session = true; - - // TODO: test existence of executable with TryExec - printf("\x1b[0m"); - // NOLINTNEXTLINE(bugprone-branch-clone) - if (session.type == SHELL) { - clear_screen(); - (void)fflush(stdout); - execlp(session.exec, session.exec, NULL); - } else if (session.type == XORG || session.type == WAYLAND) { - clear_screen(); - (void)fflush(stdout); - // NOLINTNEXTLINE - execvp(desktop_exec[0], desktop_exec); - // 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/chvt.c b/src/chvt.c index 513f026..d5c71d8 100644 --- a/src/chvt.c +++ b/src/chvt.c @@ -4,6 +4,7 @@ #include #include "chvt.h" +#include "macros.h" static char* vterms[] = {"/dev/tty", "/dev/tty0", "/dev/vc/0", "/dev/systty", "/dev/console"}; @@ -23,16 +24,16 @@ int chvt_str(char* str) { return chvt((int)i); } +int vt = -1; int chvt(int n) { - // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) (void)fprintf(stderr, "activating vt %d\n", n); + vt = n; // NOLINTNEXTLINE(readability-identifier-length) char c = 0; - for (size_t i = 0; i < sizeof(vterms) / sizeof(vterms[0]); i++) { + for (size_t i = 0; i < LEN(vterms); i++) { int fd = open(vterms[i], O_RDWR); if (fd >= 0 && isatty(fd) && ioctl(fd, KDGKBTYPE, &c) == 0 && c < 3) { if (ioctl(fd, VT_ACTIVATE, n) < 0 || ioctl(fd, VT_WAITACTIVE, n) < 0) { - // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) (void)fprintf(stderr, "Couldn't activate vt %d\n", n); return -1; } diff --git a/src/config.c b/src/config.c index 3c9e7ea..e058bd4 100644 --- a/src/config.c +++ b/src/config.c @@ -117,12 +117,12 @@ union typ_ptr { char** string; long long* number; bool* boolean; - enum keys* key; + enum Keys* key; struct Vector* vec; uintptr_t ptr; }; -struct parser_error parse_key(enum introspection_type typ, union typ_ptr at, - char* key, size_t offset) { +struct parser_error parse_key(enum IntrospectionType typ, union typ_ptr at, + char* key) { char* aux_str = NULL; struct parser_error aux_err; @@ -219,7 +219,7 @@ struct status config_line_handler(void* _config, char* table, char* k, log_printf("[I] parsing [%s.%s] as %s\n", table, k, INTROS_TYS_NAMES[this_intros_key->typ]); - struct parser_error err = parse_key(this_intros_key->typ, k_addr, v, offset); + struct parser_error err = parse_key(this_intros_key->typ, k_addr, v); if (err.msg != NULL) { log_printf("[E] cfg parser, failed to parse [%s.%s] (%s): %s\n", table, k, INTROS_TYS_NAMES[this_intros_key->typ], err.msg); diff --git a/src/desktop.c b/src/desktop.c index 9924397..dce69bd 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -1,4 +1,3 @@ -// NOLINTBEGIN(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,readability-function-cognitive-complexity) #include #include #include @@ -20,6 +19,7 @@ char* trim_str(char* str) { return str; } +// NOLINTNEXTLINE(readability-function-cognitive-complexity) int read_desktop(FILE* fd, void* ctx, struct status (*cb)(void* ctx, char* table, char* key, char* value)) { @@ -82,4 +82,3 @@ int read_desktop(FILE* fd, void* ctx, if (buf != NULL) free(buf); return ret; } -// NOLINTEND(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,readability-function-cognitive-complexity) diff --git a/src/desktop_exec.c b/src/desktop_exec.c index 2774add..c457305 100644 --- a/src/desktop_exec.c +++ b/src/desktop_exec.c @@ -1,14 +1,71 @@ +// TODO: rewrite properly +// NOLINTBEGIN(clang-diagnostic-nullability-completeness) #include +#include #include #include +#include +#include #include "desktop_exec.h" +#include "macros.h" // constants for exec string parsing #define MAX_ARGS 100 // ARG_LENGTH is the initial length of a parsed argument #define ARG_LENGTH 64 +// returns NULL on any error +// otherwise it returns the absolute path of the program that MUST BE FREED +char* NULLABLE search_path(const char* NNULLABLE for_binary) { + if (strchr(for_binary, '/') != NULL) { + // skip absolute paths + return strdup(for_binary); + } + char* path_env = getenv("PATH"); + if (!path_env) return NULL; + char* path = strdup(path_env); + if (!path) return NULL; + + char* tok = strtok(path, ":"); + while (tok) { + char* bin_path; + asprintf(&bin_path, "%s/%s", tok, for_binary); + if (!bin_path) { + free(path); + return NULL; + } + + struct stat stat_buf; + if (stat(bin_path, &stat_buf) == 0) { + // TODO: check exec bit ig + // if(stat_buf.) {} + free(path); + return bin_path; + } + + free(bin_path); + tok = strtok(NULL, ":"); + } + + free(path); + return NULL; +} + +// returns -1 on exec failure and -2 on search failure +int execvpe_desktop(char** args, char* NNULLABLE* NNULLABLE envlist) { + char* new_arg = search_path(args[0]); + if (!new_arg) return -2; + + free(args[0]); + args[0] = new_arg; + + int status = execve(args[0], args, envlist); + free(new_arg); + + return status; +} + // parse Exec=/bin/prog arg1 arg2\ with\ spaces void free_parsed_args(int arg_count, char** args) { if (!args) return; @@ -88,6 +145,7 @@ int push_arg(struct ctx* st) { 3 = syntax Important: call free_parsed_args afterwards to free the passed ***args */ + // NOLINTBEGIN(readability-function-cognitive-complexity) int parse_exec_string(const char* exec_s, int* arg_count, char*** args) { if (!exec_s || !args || !arg_count) return 1; @@ -181,3 +239,4 @@ syntax_err: return 3; } // NOLINTEND(readability-function-cognitive-complexity) +// NOLINTEND(clang-diagnostic-nullability-completeness) diff --git a/src/efield.c b/src/efield.c index 2327c72..3deab03 100644 --- a/src/efield.c +++ b/src/efield.c @@ -4,8 +4,6 @@ #include "ui.h" #include "util.h" -// NOLINTBEGIN(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) - struct editable_field efield_new(char* content) { struct editable_field efield; if (content != NULL) { @@ -80,5 +78,3 @@ bool efield_seek(struct editable_field* self, char seek) { self->pos = (u_char)(ptr - self->content); return ptr != start; } - -// NOLINTEND(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) diff --git a/src/launch_state.c b/src/launch_state.c index ae16661..76c4c92 100644 --- a/src/launch_state.c +++ b/src/launch_state.c @@ -21,15 +21,17 @@ int read_launch_state(struct LaunchState* NNULLABLE state) { }; size_t num = 0; - if (getline(&state->username, &num, state_fd) < 0) goto fail; - state->username[strcspn(state->username, "\n")] = 0; + ssize_t chars = getline(&state->username, &num, state_fd); + if (chars < 0) goto fail; + if (state->username[chars] == '\n') state->username[chars] = 0; num = 0; - if (getline(&state->session_opt, &num, state_fd) < 0) { + chars = getline(&state->session_opt, &num, state_fd); + if (chars < 0) { free(state->session_opt); goto fail; } - state->session_opt[strcspn(state->session_opt, "\n")] = 0; + if (state->session_opt[chars] == '\n') state->session_opt[chars] = 0; (void)fclose(state_fd); return 0; diff --git a/src/main.c b/src/main.c index 4a8aa29..484ac2e 100644 --- a/src/main.c +++ b/src/main.c @@ -12,12 +12,14 @@ #include "log.h" #include "macros.h" #include "sessions.h" +#include "signal_handler.h" #include "ui.h" #include "users.h" #include "util.h" #include "version.h" #define DATESTR_MAXBUFSIZE 0x20 + int main(int argc, char* argv[]) { // Logger char* log_output = getenv("LIDM_LOG"); @@ -85,6 +87,8 @@ int main(int argc, char* argv[]) { struct Vector users = get_human_users(); struct Vector sessions = get_avaliable_sessions(); + setup_sigterm(); + int ret = load(&users, &sessions); if (ret == 0) execl(argv[0], argv[0], NULL); } diff --git a/src/pam.c b/src/pam.c new file mode 100644 index 0000000..21b7568 --- /dev/null +++ b/src/pam.c @@ -0,0 +1,192 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "macros.h" +#include "pam.h" +#include "sessions.h" +#include "ui.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 SessionType 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() \ + { \ + return (struct pamh_getenv_status){.error_flag = PAMH_ERR_ALLOC}; \ + } +#define FAIL(ERR, ERRFN) \ + { \ + return (struct pamh_getenv_status){.error_flag = (ERR), .errfn = (ERRFN)}; \ + } + +struct pamh_getenv_status pamh_get_complete_env(pam_handle_t* handle, + struct passwd* NNULLABLE pw, + enum SessionType session_typ) { + char** raw_envlist = pam_getenvlist(handle); + if (!raw_envlist) FAIL(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)}}; + + char** envlist = merge_envlist(raw_envlist, extra_env, LEN(extra_env)); + if (!envlist) FAIL_ALLOC(); + return (struct pamh_getenv_status){ + .error_flag = PAMH_ERR_NOERR, + .envlist = envlist, + }; +} + +#undef FAIL +#undef FAIL_ALLOC + +/////////////// + +struct pam_conv_data { + char* password; + void (*display_pam_msg)(const char* msg, int msg_style); +}; + +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; + } + + struct pam_conv_data* conv_data = (struct pam_conv_data*)appdata_ptr; + + for (int i = 0; i < num_msg; i++) { + reply[i].resp = NULL; + reply[i].resp_retcode = 0; + + switch (msg[i]->msg_style) { + case PAM_PROMPT_ECHO_OFF: + case PAM_PROMPT_ECHO_ON: + reply[i].resp = strdup(conv_data->password); + if (!reply[i].resp) { + for (int j = 0; j < i; j++) + free(reply[j].resp); + free(reply); + return PAM_BUF_ERR; + } + break; + + case PAM_TEXT_INFO: + case PAM_ERROR_MSG: + if (conv_data->display_pam_msg && msg[i]->msg) { + conv_data->display_pam_msg(msg[i]->msg, msg[i]->msg_style); + } + break; + + default: + break; + } + } + *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_data conv_data = {.password = passwd, + .display_pam_msg = print_pam_msg}; + struct pam_conv pamc = {pam_conversation, (void*)&conv_data}; + 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/sessions.c b/src/sessions.c index 1fa2542..a15f248 100644 --- a/src/sessions.c +++ b/src/sessions.c @@ -8,12 +8,14 @@ #include #include "desktop.h" +#include "desktop_exec.h" #include "log.h" +#include "macros.h" #include "sessions.h" #include "util.h" struct source_dir { - enum session_type type; + enum SessionType type; char* dir; }; @@ -78,9 +80,11 @@ struct status cb(void* _ctx, char* NULLABLE table, char* key, char* value) { // also, always return 0 or we will break parsing and we don't want a bad // desktop file to break all possible sessions -static enum session_type session_type; +static enum SessionType session_type; // NOLINTNEXTLINE(readability-function-cognitive-complexity) static int fn(const char* fpath, const struct stat* sb, int typeflag) { + UNUSED(sb); + // guessing symlink behavior // - FTW_PHYS if set doesn't follow symlinks, so ftw() has no flags and it // follows symlinks, we should never get to handle that @@ -101,22 +105,33 @@ static int fn(const char* fpath, const struct stat* sb, int typeflag) { } int ret = read_desktop(fd, &ctx, &cb); - if (ret < 0) { // any error - log_printf("[E] format error parsing %s", fpath); - return 0; - } + // any error + if (ret < 0) goto err_close; (void)fclose(fd); + // TODO: filter based on tryexec + // https://specifications.freedesktop.org/desktop-entry-spec/latest/recognized-keys.html + free(ctx.tryexec); + // just add this to the list if (ctx.name != NULL && ctx.exec != NULL) { struct session* this_session = malloc(sizeof(struct session)); if (this_session == NULL) return 0; + int arg_count; + char** args; + int parse_status = parse_exec_string(ctx.exec, &arg_count, &args); + if (parse_status != 0 || arg_count == 0 || !args[0]) { + log_printf("[E] parsing exec string '%s': %d\n", ctx.exec, parse_status); + free(this_session); + goto err_parsing; + } + free(ctx.exec); + *this_session = (struct session){ .name = ctx.name, - .exec = ctx.exec, - .tryexec = ctx.tryexec, + .exec = session_exec_desktop(arg_count, args), .type = session_type, }; @@ -124,6 +139,12 @@ static int fn(const char* fpath, const struct stat* sb, int typeflag) { } return 0; + +err_close: + (void)fclose(fd); +err_parsing: + log_printf("[E] format error parsing %s", fpath); + return 0; } // This code is designed to be run purely single threaded @@ -133,7 +154,7 @@ struct Vector get_avaliable_sessions() { vec_reserve(&sessions, LIKELY_BOUND_SESSIONS); cb_sessions = &sessions; - for (size_t i = 0; i < (sizeof(SOURCES) / sizeof(SOURCES[0])); i++) { + for (size_t i = 0; i < LEN(SOURCES); i++) { log_printf("[I] parsing into %s\n", SOURCES[i].dir); session_type = SOURCES[i].type; ftw(SOURCES[i].dir, &fn, 1); diff --git a/src/signal_handler.c b/src/signal_handler.c new file mode 100644 index 0000000..2c9b48a --- /dev/null +++ b/src/signal_handler.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +#include "signal_handler.h" + +static void handle_sigterm(int sig) { + (void)sig; + + (void)signal(SIGTERM, SIG_IGN); + kill(-getpgrp(), SIGTERM); + + int status; + while (waitpid(-1, &status, 0) > 0 || errno == EINTR) { + } + + _exit(0); +} + +void setup_sigterm() { + setpgid(0, 0); + + struct sigaction sa; + sa.sa_handler = handle_sigterm; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGTERM, &sa, NULL); +} diff --git a/src/ui.c b/src/ui.c index 7293fb5..d9fca0b 100644 --- a/src/ui.c +++ b/src/ui.c @@ -26,6 +26,7 @@ #include "keys.h" #include "launch_state.h" #include "log.h" +#include "macros.h" #include "ofield.h" #include "sessions.h" #include "ui.h" @@ -62,9 +63,15 @@ struct config* g_config = NULL; static volatile sig_atomic_t need_resize = 0; static void process_sigwinch(int signal) { + UNUSED(signal); need_resize = 1; } +inline void draw_bg() { + // apply bg color to all screen + printf("\x1b[%sm\x1b[2J", g_config->colors.bg); +} + void setup(struct config* config) { g_config = config; @@ -74,10 +81,9 @@ void setup(struct config* config) { term.c_lflag &= ~(ICANON | ECHO); tcsetattr(STDOUT_FILENO, TCSANOW, &term); - // save cursor pos, save screen, set color and reset screen - // (applying color to all screen) - printf("\x1b[s\x1b[?47h\x1b[%s;%sm\x1b[2J", g_config->colors.bg, - g_config->colors.fg); + // save cursor pos, save screen + printf("\x1b[s\x1b[?47h"); + draw_bg(); (void)atexit(restore_all); (void)signal(SIGINT, signal_handler); @@ -152,7 +158,7 @@ void ui_update_cursor_focus() { (void)printf("\x1b[%d;%dH", line, col); } -void ui_update_field(enum input focused_input) { +void ui_update_field(enum Input focused_input) { if (focused_input == PASSWD) { print_passwd(utf8len(of_passwd.efield.content), false); } else if (focused_input == SESSION) { @@ -171,7 +177,7 @@ void ui_update_ffield() { } void ui_update_ofield(struct opts_field* NNULLABLE self) { - enum input input; + enum Input input; if (self == &of_session) input = SESSION; else if (self == &of_user) @@ -184,6 +190,7 @@ void ui_update_ofield(struct opts_field* NNULLABLE self) { ui_update_field(input); } +/// draw everything void scratch_print_ui() { ioctl(STDOUT_FILENO, TIOCGWINSZ, &window); box_start = (struct uint_point){ @@ -191,15 +198,16 @@ void scratch_print_ui() { .y = ((window.ws_row - BOX_HEIGHT) / 2), // leave more space under }; - if (window.ws_row < BOX_HEIGHT + INNER_BOX_OUT_MARGIN * 2 || - window.ws_col < BOX_WIDTH + INNER_BOX_OUT_MARGIN * 2) { + if (window.ws_row < BOX_HEIGHT + (INNER_BOX_OUT_MARGIN * 2) || + window.ws_col < BOX_WIDTH + (INNER_BOX_OUT_MARGIN * 2)) { printf("\033[2J\033[H"); // Clear screen printf("\x1b[1;31mScreen too small\x1b[0m\n"); printf("\x1b[%s;%sm\x1b[2J", g_config->colors.bg, g_config->colors.fg); return; } - printf("\033[2J\033[H"); // Clear screen + printf("\033[2J\033[H\033c"); // Clear screen + draw_bg(); /// PRINTING // printf box @@ -281,23 +289,24 @@ int load(struct Vector* users, struct Vector* sessions) { (void)fflush(stdout); if (!read_press_nb(&len, seq, &tv)) continue; if (*seq == '\x1b') { - enum keys ansi_code = find_ansi(seq); - if (ansi_code != -1) { - if (ansi_code == ESC) { + struct option_keys ansi_code = find_ansi(seq); + if (ansi_code.is_some) { + enum Keys ansi_key = ansi_code.key; + if (ansi_key == ESC) { esc = 2; - } else if (ansi_code == g_config->functions.refresh) { + } else if (ansi_key == g_config->functions.refresh) { restore_all(); return 0; - } else if (ansi_code == g_config->functions.reboot) { + } else if (ansi_key == g_config->functions.reboot) { restore_all(); reboot(RB_AUTOBOOT); exit(0); - } else if (ansi_code == g_config->functions.poweroff) { + } else if (ansi_key == g_config->functions.poweroff) { restore_all(); reboot(RB_POWER_OFF); exit(0); } else if (g_config->functions.fido != NONE && - ansi_code == g_config->functions.fido) { + ansi_key == g_config->functions.fido) { bool successful_write = write_launch_state((struct LaunchState){ .username = st_user().username, .session_opt = @@ -310,14 +319,16 @@ int load(struct Vector* users, struct Vector* sessions) { &restore_all, g_config)) { print_passwd(utf8len(of_passwd.efield.content), true); ui_update_cursor_focus(); + } else { + scratch_print_ui(); } - } else if (ansi_code == A_UP || ansi_code == A_DOWN) { - st_ch_focus(ansi_code == A_DOWN ? 1 : -1); - } else if (ansi_code == A_RIGHT || ansi_code == A_LEFT) { + } else if (ansi_key == A_UP || ansi_key == A_DOWN) { + st_ch_focus(ansi_key == A_DOWN ? 1 : -1); + } else if (ansi_key == A_RIGHT || ansi_key == A_LEFT) { if (esc) - st_ch_of_opts(ansi_code == A_RIGHT ? 1 : -1); + st_ch_of_opts(ansi_key == A_RIGHT ? 1 : -1); else - st_ch_ef_col(ansi_code == A_RIGHT ? 1 : -1); + st_ch_ef_col(ansi_key == A_RIGHT ? 1 : -1); } } } else { @@ -333,6 +344,8 @@ int load(struct Vector* users, struct Vector* sessions) { &restore_all, g_config)) { print_passwd(utf8len(of_passwd.efield.content), true); ui_update_cursor_focus(); + } else { + scratch_print_ui(); } } else st_kbd_type(seq, g_config->behavior.include_defshell); @@ -368,7 +381,7 @@ void print_head() { // hostname doesn't just change on runtime, // but the length of the time string might static char* NULLABLE hostname = NULL; - static size_t hostname_calcd_size; + static ssize_t hostname_calcd_size; // save the truncated hostname and the length it truncated to, // if said length changes recalculate this (and free previous str) @@ -534,7 +547,7 @@ static void print_footer() { utf8len(g_config->strings.f_refresh) + utf8len(KEY_NAMES[g_config->functions.refresh]); - bsize += 2 * 2 + 3 * 1; + bsize += (2 * 2) + (3 * 1); if (fido_enabled) { bsize += utf8len(g_config->strings.f_fido) + diff --git a/src/ui_state.c b/src/ui_state.c index 18e1b09..44a6c37 100644 --- a/src/ui_state.c +++ b/src/ui_state.c @@ -4,7 +4,7 @@ #include "ui.h" #include "users.h" -enum input focused_input = PASSWD; +enum Input focused_input = PASSWD; struct Vector* gusers; struct Vector* gsessions; @@ -13,7 +13,7 @@ struct opts_field of_session; struct opts_field of_user; struct opts_field of_passwd; -struct opts_field* NNULLABLE get_opts_field(enum input from) { +struct opts_field* NNULLABLE get_opts_field(enum Input from) { if (from == SESSION) return &of_session; if (from == USER) return &of_user; if (from == PASSWD) return &of_passwd; @@ -40,7 +40,8 @@ struct session st_session(bool include_defshell) { if (include_defshell && of_session.current_opt == gsessions->length + 1) { struct session shell_session; shell_session.type = SHELL; - shell_session.exec = shell_session.name = st_user().shell; + shell_session.exec = + session_exec_shell(shell_session.name = st_user().shell); return shell_session; } @@ -49,7 +50,8 @@ struct session st_session(bool include_defshell) { struct session custom_session; custom_session.type = SHELL; - custom_session.name = custom_session.exec = of_session.efield.content; + custom_session.exec = + session_exec_shell(custom_session.name = of_session.efield.content); return custom_session; } @@ -86,7 +88,7 @@ void st_kbd_type(char* text, bool cfg_include_defshell) { start = st_user().username; if (focused_input == SESSION && of_session.current_opt != 0 && st_session(cfg_include_defshell).type == SHELL) - start = st_session(cfg_include_defshell).exec; + start = st_session(cfg_include_defshell).exec.shell; ofield_kbd_type(field, text, start); ui_update_ffield(); diff --git a/src/util.c b/src/util.c index 0b730ad..1b1c53d 100644 --- a/src/util.c +++ b/src/util.c @@ -13,10 +13,10 @@ static int selret_magic(); -int find_keyname(enum keys* at, const char* name) { +int find_keyname(enum Keys* at, const char* name) { for (size_t i = 0; i < LEN(KEY_MAPPINGS); i++) { if (strcmp(KEY_NAMES[i], name) == 0) { - *at = (enum keys)i; + *at = (enum Keys)i; return 0; } } @@ -24,16 +24,19 @@ int find_keyname(enum keys* at, const char* name) { return -1; } -enum keys find_ansi(const char* seq) { +struct option_keys find_ansi(const char* seq) { for (size_t i = 0; i < LEN(KEY_MAPPINGS); i++) { struct key_mapping mapping = KEY_MAPPINGS[i]; for (size_t j = 0; mapping.sequences[j] != NULL; j++) { if (strcmp(mapping.sequences[j], seq) == 0) { - return (enum keys)i; + return (struct option_keys){ + .is_some = true, + .key = (enum Keys)i, + }; } } } - return -1; + return (struct option_keys){.is_some = false}; } void read_press(u_char* length, char* out) { @@ -147,6 +150,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*));