diff --git a/Makefile b/Makefile index e689da1..5d34097 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ ODIR=dist PREFIX=/usr CC?=gcc -CFLAGS?=-O3 -Wall -fdata-sections -ffunction-sections +CFLAGS?=-O3 -Wall -Wextra -fdata-sections -ffunction-sections # C PreProcessor flags, not C Plus Plus CPPFLAGS?= ALLFLAGS=$(CFLAGS) $(CPPFLAGS) -I$(IDIR) 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 ffc9657..25dd560 100644 --- a/include/config.h +++ b/include/config.h @@ -150,15 +150,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/macros.h b/include/macros.h index 9915636..5cc3a26 100644 --- a/include/macros.h +++ b/include/macros.h @@ -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 index ccc1604..5210da3 100644 --- a/include/pam.h +++ b/include/pam.h @@ -24,7 +24,6 @@ struct pamh_getenv_status { // 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 SessionType session_typ); diff --git a/include/sessions.h b/include/sessions.h index e7b5210..209642b 100644 --- a/include/sessions.h +++ b/include/sessions.h @@ -2,6 +2,7 @@ #define SESSIONSH_ #include +#include #include "macros.h" #include "util.h" @@ -12,10 +13,58 @@ enum SessionType { SHELL, }; +enum ExecType { + EXEC_SHELL, + EXEC_DESKTOP, +}; + +struct desktop_exec { + char** args; + int arg_count; +}; + +struct session_exec { + enum ExecType typ; + union { + char* shell; + struct desktop_exec desktop; + }; +}; + +static inline struct session_exec session_exec_shell(char* shell) { + return (struct session_exec){ + .typ = EXEC_SHELL, + .shell = shell, + }; +} + +static inline struct session_exec session_exec_desktop(int arg_count, + char** args) { + return (struct session_exec){ + .typ = EXEC_DESKTOP, + .desktop = + { + .args = args, + .arg_count = arg_count, + }, + }; +} + +static inline int session_exec_exec(struct session_exec* exec, + char** NNULLABLE envlist) { + switch (exec->typ) { + case EXEC_SHELL: + return execle(exec->shell, exec->shell, NULL, envlist); + case EXEC_DESKTOP: + return execve(exec->desktop.args[0], exec->desktop.args, envlist); + default: + __builtin_unreachable(); + } +} + struct session { char* NNULLABLE name; - char* NNULLABLE exec; - char* NULLABLE tryexec; + struct session_exec exec; enum SessionType type; }; diff --git a/include/util.h b/include/util.h index 35e049a..f88130c 100644 --- a/include/util.h +++ b/include/util.h @@ -11,11 +11,17 @@ #include "keys.h" int find_keyname(enum Keys* at, const char* name); -enum Keys find_ansi(const char* seq); +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,6 +30,8 @@ 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; diff --git a/src/auth.c b/src/auth.c index 68dded0..8677727 100644 --- a/src/auth.c +++ b/src/auth.c @@ -12,14 +12,14 @@ #include "auth.h" #include "config.h" -#include "desktop_exec.h" #include "log.h" #include "pam.h" #include "sessions.h" #include "ui.h" #include "util.h" -void try_source_file(struct Vector* NNULLABLE vec_envlist, char* filepath) { +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) { @@ -35,7 +35,7 @@ void try_source_file(struct Vector* NNULLABLE vec_envlist, char* filepath) { if (read == 0 || (read > 0 && *line == '#')) continue; if (line[read - 1] == '\n') line[read - 1] = '\0'; - for (size_t i = 1; i < read; i++) { + for (ssize_t i = 1; i < read; i++) { if (line[i] == '=') { vec_push(vec_envlist, (void*)line); line = NULL; @@ -48,7 +48,7 @@ void try_source_file(struct Vector* NNULLABLE vec_envlist, char* filepath) { (void)fclose(file2source); } -void source_paths(struct Vector* NNULLABLE vec_envlist, +static void source_paths(struct Vector* NNULLABLE vec_envlist, struct Vector* NNULLABLE abs_source, const char* NULLABLE user_home, struct Vector* NNULLABLE user_source) { @@ -66,6 +66,7 @@ void source_paths(struct Vector* NNULLABLE vec_envlist, continue; } try_source_file(vec_envlist, path); + free(path); } else { log_puts("user has no home\n"); @@ -88,25 +89,51 @@ struct child_msg { bool err; }; +#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_exec* NNULLABLE exec, + 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"); + + // or maybe Xorg fork should happen here + + SEND_MSG((struct child_msg){.err = false}); + DUMMY_READ(); + close(pipefd[0]); + + int exit = session_exec_exec(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"); @@ -120,7 +147,7 @@ bool launch(char* user, char* passwd, struct session session, void (*cb)(void), } struct pamh_getenv_status env_ret = - pamh_get_complete_env(pamh, user, pw, session.type); + 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"); @@ -141,53 +168,20 @@ bool launch(char* user, char* passwd, struct session session, void (*cb)(void), return false; } + // TODO: start X server here if needed + // e.g. spawn (also after downgrading privs): + // + // `X :0 tty -auth /.Xauthority -nolisten tcp -background none` + // + // Then `DISPLAY=:0 ` + int pipefd[2]; pipe(pipefd); uint pid = fork(); - if (pid == 0) { // child - -#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); \ - } - - 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}); -#undef SEND_MSG -#undef SEND_ERR - char _; - read(pipefd[0], &_, sizeof(_)); - close(pipefd[0]); - - int exit; - if (session.type == SHELL) { - exit = execle(session.exec, session.exec, NULL, envlist); - } else if (session.type == XORG || session.type == WAYLAND) { - // TODO: test existence of executable with TryExec - // NOLINTNEXTLINE - exit = execve(desktop_exec[0], desktop_exec, envlist); - // NOLINTNEXTLINE - free_parsed_args(desktop_count, desktop_exec); - } else - exit = -1; - perror("exec error"); - (void)fputs("failure calling session\n", stderr); - _exit(exit); - } else { + if (pid == 0) + forked(pipefd, pw, user, &session.exec, envlist); + else { struct child_msg msg; read(pipefd[0], &msg, sizeof(struct child_msg)); close(pipefd[0]); diff --git a/src/chvt.c b/src/chvt.c index 513f026..bdb1b8f 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"}; @@ -24,15 +25,13 @@ int chvt_str(char* str) { } int chvt(int n) { - // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) (void)fprintf(stderr, "activating vt %d\n", 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 f9aba1a..e058bd4 100644 --- a/src/config.c +++ b/src/config.c @@ -122,7 +122,7 @@ union typ_ptr { uintptr_t ptr; }; struct parser_error parse_key(enum IntrospectionType typ, union typ_ptr at, - char* key, size_t offset) { + 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..3499323 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 @@ -82,4 +81,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/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/pam.c b/src/pam.c index ddcf976..4af5942 100644 --- a/src/pam.c +++ b/src/pam.c @@ -89,7 +89,6 @@ char* NULLABLE xdg_ssession_type_str(enum SessionType typ) { } struct pamh_getenv_status pamh_get_complete_env(pam_handle_t* handle, - char* NNULLABLE user, struct passwd* NNULLABLE pw, enum SessionType session_typ) { struct pamh_getenv_status status; @@ -123,7 +122,7 @@ int pam_conversation(int num_msg, const struct pam_message** msg, if (!reply) { return PAM_BUF_ERR; } - for (size_t i = 0; i < num_msg; i++) { + for (int i = 0; i < num_msg; i++) { reply[i].resp = NULL; reply[i].resp_retcode = 0; if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF || diff --git a/src/sessions.c b/src/sessions.c index 260c4fb..5707da5 100644 --- a/src/sessions.c +++ b/src/sessions.c @@ -8,7 +8,9 @@ #include #include "desktop.h" +#include "desktop_exec.h" #include "log.h" +#include "macros.h" #include "sessions.h" #include "util.h" @@ -75,6 +77,8 @@ struct status cb(void* _ctx, char* NULLABLE table, char* key, char* value) { 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 @@ -95,22 +99,32 @@ 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); + 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, }; @@ -118,6 +132,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 @@ -127,7 +147,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/ui.c b/src/ui.c index 05bda97..0126366 100644 --- a/src/ui.c +++ b/src/ui.c @@ -25,6 +25,7 @@ #include "keys.h" #include "launch_state.h" #include "log.h" +#include "macros.h" #include "ofield.h" #include "sessions.h" #include "ui.h" @@ -61,6 +62,7 @@ struct config* g_config = NULL; static volatile sig_atomic_t need_resize = 0; static void process_sigwinch(int signal) { + UNUSED(signal); need_resize = 1; } @@ -280,28 +282,29 @@ 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 (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 { diff --git a/src/ui_state.c b/src/ui_state.c index d701a4c..44a6c37 100644 --- a/src/ui_state.c +++ b/src/ui_state.c @@ -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 819f4f1..1b1c53d 100644 --- a/src/util.c +++ b/src/util.c @@ -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) {