diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..219396b --- /dev/null +++ b/src/config.c @@ -0,0 +1,370 @@ +#include "configator.h" + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifndef DEBUG + #define INI_LANG DATADIR "/lang/%s.ini" + #define INI_CONFIG "/etc/4y/config.ini" +#else + #define INI_LANG "../res/lang/%s.ini" + #define INI_CONFIG "../res/config.ini" +#endif + +static void lang_handle(void* data, char** pars, const int pars_count) +{ + if (*((char**)data) != NULL) + { + free (*((char**)data)); + } + + *((char**)data) = strdup(*pars); +} + +static void config_handle_u8(void* data, char** pars, const int pars_count) +{ + if (strcmp(*pars, "") == 0) + { + *((uint8_t*)data) = 0; + } + else + { + *((uint8_t*)data) = atoi(*pars); + } +} + +static void config_handle_u16(void* data, char** pars, const int pars_count) +{ + if (strcmp(*pars, "") == 0) + { + *((uint16_t*)data) = 0; + } + else + { + *((uint16_t*)data) = atoi(*pars); + } +} + +void config_handle_str(void* data, char** pars, const int pars_count) +{ + if (*((char**)data) != NULL) + { + free(*((char**)data)); + } + + *((char**)data) = strdup(*pars); +} + +static void config_handle_char(void* data, char** pars, const int pars_count) +{ + *((char*)data) = **pars; +} + +static void config_handle_bool(void* data, char** pars, const int pars_count) +{ + *((bool*)data) = (strcmp("true", *pars) == 0); +} + +void lang_load() +{ + // must be alphabetically sorted + struct configator_param map_no_section[] = + { + {"capslock", &lang.capslock, lang_handle}, + {"err_alloc", &lang.err_alloc, lang_handle}, + {"err_bounds", &lang.err_bounds, lang_handle}, + {"err_chdir", &lang.err_chdir, lang_handle}, + {"err_console_dev", &lang.err_console_dev, lang_handle}, + {"err_dgn_oob", &lang.err_dgn_oob, lang_handle}, + {"err_domain", &lang.err_domain, lang_handle}, + {"err_hostname", &lang.err_hostname, lang_handle}, + {"err_mlock", &lang.err_mlock, lang_handle}, + {"err_null", &lang.err_null, lang_handle}, + {"err_pam", &lang.err_pam, lang_handle}, + {"err_pam_abort", &lang.err_pam_abort, lang_handle}, + {"err_pam_acct_expired", &lang.err_pam_acct_expired, lang_handle}, + {"err_pam_auth", &lang.err_pam_auth, lang_handle}, + {"err_pam_authinfo_unavail", &lang.err_pam_authinfo_unavail, lang_handle}, + {"err_pam_authok_reqd", &lang.err_pam_authok_reqd, lang_handle}, + {"err_pam_buf", &lang.err_pam_buf, lang_handle}, + {"err_pam_cred_err", &lang.err_pam_cred_err, lang_handle}, + {"err_pam_cred_expired", &lang.err_pam_cred_expired, lang_handle}, + {"err_pam_cred_insufficient", &lang.err_pam_cred_insufficient, lang_handle}, + {"err_pam_cred_unavail", &lang.err_pam_cred_unavail, lang_handle}, + {"err_pam_maxtries", &lang.err_pam_maxtries, lang_handle}, + {"err_pam_perm_denied", &lang.err_pam_perm_denied, lang_handle}, + {"err_pam_session", &lang.err_pam_session, lang_handle}, + {"err_pam_sys", &lang.err_pam_sys, lang_handle}, + {"err_pam_user_unknown", &lang.err_pam_user_unknown, lang_handle}, + {"err_path", &lang.err_path, lang_handle}, + {"err_perm_dir", &lang.err_perm_dir, lang_handle}, + {"err_perm_group", &lang.err_perm_group, lang_handle}, + {"err_perm_user", &lang.err_perm_user, lang_handle}, + {"err_pwnam", &lang.err_pwnam, lang_handle}, + {"err_user_gid", &lang.err_user_gid, lang_handle}, + {"err_user_init", &lang.err_user_init, lang_handle}, + {"err_user_uid", &lang.err_user_uid, lang_handle}, + {"err_xsessions_dir", &lang.err_xsessions_dir, lang_handle}, + {"err_xsessions_open", &lang.err_xsessions_open, lang_handle}, + {"f1", &lang.f1, lang_handle}, + {"f2", &lang.f2, lang_handle}, + {"login", &lang.login, lang_handle}, + {"logout", &lang.logout, lang_handle}, + {"numlock", &lang.numlock, lang_handle}, + {"password", &lang.password, lang_handle}, + {"shell", &lang.shell, lang_handle}, + {"wayland", &lang.wayland, lang_handle}, + {"xinitrc", &lang.xinitrc, lang_handle}, + }; + + uint16_t map_len[] = {45}; + struct configator_param* map[] = + { + map_no_section, + }; + + uint16_t sections_len = 0; + struct configator_param* sections = NULL; + + struct configator lang; + lang.map = map; + lang.map_len = map_len; + lang.sections = sections; + lang.sections_len = sections_len; + + char file[256]; + snprintf(file, 256, INI_LANG, config.lang); + + if (access(file, F_OK) != -1) + { + configator(&lang, file); + } +} + +void config_load(const char *cfg_path) +{ + if (cfg_path == NULL) + { + cfg_path = INI_CONFIG; + } + // must be alphabetically sorted + struct configator_param map_no_section[] = + { + {"animate", &config.animate, config_handle_bool}, + {"animation", &config.animation, config_handle_u8}, + {"asterisk", &config.asterisk, config_handle_char}, + {"bg", &config.bg, config_handle_u8}, + {"blank_box", &config.blank_box, config_handle_bool}, + {"blank_password", &config.blank_password, config_handle_bool}, + {"console_dev", &config.console_dev, config_handle_str}, + {"default_input", &config.default_input, config_handle_u8}, + {"fg", &config.fg, config_handle_u8}, + {"hide_borders", &config.hide_borders, config_handle_bool}, + {"hide_f1_commands", &config.hide_f1_commands, config_handle_bool}, + {"input_len", &config.input_len, config_handle_u8}, + {"lang", &config.lang, config_handle_str}, + {"load", &config.load, config_handle_bool}, + {"margin_box_h", &config.margin_box_h, config_handle_u8}, + {"margin_box_v", &config.margin_box_v, config_handle_u8}, + {"max_desktop_len", &config.max_desktop_len, config_handle_u8}, + {"max_login_len", &config.max_login_len, config_handle_u8}, + {"max_password_len", &config.max_password_len, config_handle_u8}, + {"mcookie_cmd", &config.mcookie_cmd, config_handle_str}, + {"min_refresh_delta", &config.min_refresh_delta, config_handle_u16}, + {"path", &config.path, config_handle_str}, + {"restart_cmd", &config.restart_cmd, config_handle_str}, + {"save", &config.save, config_handle_bool}, + {"save_file", &config.save_file, config_handle_str}, + {"service_name", &config.service_name, config_handle_str}, + {"shutdown_cmd", &config.shutdown_cmd, config_handle_str}, + {"term_reset_cmd", &config.term_reset_cmd, config_handle_str}, + {"tty", &config.tty, config_handle_u8}, + {"wayland_cmd", &config.wayland_cmd, config_handle_str}, + {"wayland_specifier", &config.wayland_specifier, config_handle_bool}, + {"waylandsessions", &config.waylandsessions, config_handle_str}, + {"x_cmd", &config.x_cmd, config_handle_str}, + {"x_cmd_setup", &config.x_cmd_setup, config_handle_str}, + {"xauth_cmd", &config.xauth_cmd, config_handle_str}, + {"xsessions", &config.xsessions, config_handle_str}, + }; + + uint16_t map_len[] = {34}; + struct configator_param* map[] = + { + map_no_section, + }; + + uint16_t sections_len = 0; + struct configator_param* sections = NULL; + + struct configator config; + config.map = map; + config.map_len = map_len; + config.sections = sections; + config.sections_len = sections_len; + + configator(&config, (char *) cfg_path); +} + +void lang_defaults() +{ + lang.capslock = strdup("capslock"); + lang.err_alloc = strdup("failed memory allocation"); + lang.err_bounds = strdup("out-of-bounds index"); + lang.err_chdir = strdup("failed to open home folder"); + lang.err_console_dev = strdup("failed to access console"); + lang.err_dgn_oob = strdup("log message"); + lang.err_domain = strdup("invalid domain"); + lang.err_hostname = strdup("failed to get hostname"); + lang.err_mlock = strdup("failed to lock password memory"); + lang.err_null = strdup("null pointer"); + lang.err_pam = strdup("pam transaction failed"); + lang.err_pam_abort = strdup("pam transaction aborted"); + lang.err_pam_acct_expired = strdup("account expired"); + lang.err_pam_auth = strdup("authentication error"); + lang.err_pam_authinfo_unavail = strdup("failed to get user info"); + lang.err_pam_authok_reqd = strdup("token expired"); + lang.err_pam_buf = strdup("memory buffer error"); + lang.err_pam_cred_err = strdup("failed to set credentials"); + lang.err_pam_cred_expired = strdup("credentials expired"); + lang.err_pam_cred_insufficient = strdup("insufficient credentials"); + lang.err_pam_cred_unavail = strdup("failed to get credentials"); + lang.err_pam_maxtries = strdup("reached maximum tries limit"); + lang.err_pam_perm_denied = strdup("permission denied"); + lang.err_pam_session = strdup("session error"); + lang.err_pam_sys = strdup("system error"); + lang.err_pam_user_unknown = strdup("unknown user"); + lang.err_path = strdup("failed to set path"); + lang.err_perm_dir = strdup("failed to change current directory"); + lang.err_perm_group = strdup("failed to downgrade group permissions"); + lang.err_perm_user = strdup("failed to downgrade user permissions"); + lang.err_pwnam = strdup("failed to get user info"); + lang.err_user_gid = strdup("failed to set user GID"); + lang.err_user_init = strdup("failed to initialize user"); + lang.err_user_uid = strdup("failed to set user UID"); + lang.err_xsessions_dir = strdup("failed to find sessions folder"); + lang.err_xsessions_open = strdup("failed to open sessions folder"); + lang.f1 = strdup("F1 shutdown"); + lang.f2 = strdup("F2 reboot"); + lang.login = strdup("login:"); + lang.logout = strdup("logged out"); + lang.numlock = strdup("numlock"); + lang.password = strdup("password:"); + lang.shell = strdup("shell"); + lang.wayland = strdup("wayland"); + lang.xinitrc = strdup("xinitrc"); +} + +void config_defaults() +{ + config.animate = false; + config.animation = 0; + config.asterisk = '*'; + config.bg = 0; + config.blank_box = true; + config.blank_password = false; + config.console_dev = strdup("/dev/console"); + config.default_input = PASSWORD_INPUT; + config.fg = 9; + config.hide_borders = false; + config.input_len = 34; + config.lang = strdup("en"); + config.load = true; + config.margin_box_h = 2; + config.margin_box_v = 1; + config.max_desktop_len = 100; + config.max_login_len = 255; + config.max_password_len = 255; + config.mcookie_cmd = strdup("/usr/bin/mcookie"); + config.min_refresh_delta = 5; + config.path = strdup("/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin"); + config.restart_cmd = strdup("/sbin/shutdown -r now"); + config.save = true; + config.save_file = strdup("/etc/4y/save"); + config.service_name = strdup("4y"); + config.shutdown_cmd = strdup("/sbin/shutdown -a now"); + config.term_reset_cmd = strdup("/usr/bin/tput reset"); + config.tty = 2; + config.wayland_cmd = strdup(DATADIR "/wsetup.sh"); + config.wayland_specifier = false; + config.waylandsessions = strdup("/usr/share/wayland-sessions"); + config.x_cmd = strdup("/usr/bin/X"); + config.x_cmd_setup = strdup(DATADIR "/xsetup.sh"); + config.xauth_cmd = strdup("/usr/bin/xauth"); + config.xsessions = strdup("/usr/share/xsessions"); +} + +void lang_free() +{ + free(lang.capslock); + free(lang.err_alloc); + free(lang.err_bounds); + free(lang.err_chdir); + free(lang.err_console_dev); + free(lang.err_dgn_oob); + free(lang.err_domain); + free(lang.err_hostname); + free(lang.err_mlock); + free(lang.err_null); + free(lang.err_pam); + free(lang.err_pam_abort); + free(lang.err_pam_acct_expired); + free(lang.err_pam_auth); + free(lang.err_pam_authinfo_unavail); + free(lang.err_pam_authok_reqd); + free(lang.err_pam_buf); + free(lang.err_pam_cred_err); + free(lang.err_pam_cred_expired); + free(lang.err_pam_cred_insufficient); + free(lang.err_pam_cred_unavail); + free(lang.err_pam_maxtries); + free(lang.err_pam_perm_denied); + free(lang.err_pam_session); + free(lang.err_pam_sys); + free(lang.err_pam_user_unknown); + free(lang.err_path); + free(lang.err_perm_dir); + free(lang.err_perm_group); + free(lang.err_perm_user); + free(lang.err_pwnam); + free(lang.err_user_gid); + free(lang.err_user_init); + free(lang.err_user_uid); + free(lang.err_xsessions_dir); + free(lang.err_xsessions_open); + free(lang.f1); + free(lang.f2); + free(lang.login); + free(lang.logout); + free(lang.numlock); + free(lang.password); + free(lang.shell); + free(lang.wayland); + free(lang.xinitrc); +} + +void config_free() +{ + free(config.console_dev); + free(config.lang); + free(config.mcookie_cmd); + free(config.path); + free(config.restart_cmd); + free(config.save_file); + free(config.service_name); + free(config.shutdown_cmd); + free(config.term_reset_cmd); + free(config.wayland_cmd); + free(config.waylandsessions); + free(config.x_cmd); + free(config.x_cmd_setup); + free(config.xauth_cmd); + free(config.xsessions); +} diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..43cab96 --- /dev/null +++ b/src/config.h @@ -0,0 +1,113 @@ +#ifndef H_4y_CONFIG +#define H_4y_CONFIG + +#include +#include + +enum INPUTS { + SESSION_SWITCH, + LOGIN_INPUT, + PASSWORD_INPUT, +}; + +struct lang +{ + char* capslock; + char* err_alloc; + char* err_bounds; + char* err_chdir; + char* err_console_dev; + char* err_dgn_oob; + char* err_domain; + char* err_hostname; + char* err_mlock; + char* err_null; + char* err_pam; + char* err_pam_abort; + char* err_pam_acct_expired; + char* err_pam_auth; + char* err_pam_authinfo_unavail; + char* err_pam_authok_reqd; + char* err_pam_buf; + char* err_pam_cred_err; + char* err_pam_cred_expired; + char* err_pam_cred_insufficient; + char* err_pam_cred_unavail; + char* err_pam_maxtries; + char* err_pam_perm_denied; + char* err_pam_session; + char* err_pam_sys; + char* err_pam_user_unknown; + char* err_path; + char* err_perm_dir; + char* err_perm_group; + char* err_perm_user; + char* err_pwnam; + char* err_user_gid; + char* err_user_init; + char* err_user_uid; + char* err_xsessions_dir; + char* err_xsessions_open; + char* f1; + char* f2; + char* login; + char* logout; + char* numlock; + char* password; + char* shell; + char* wayland; + char* xinitrc; +}; + +struct config +{ + bool animate; + uint8_t animation; + char asterisk; + uint8_t bg; + bool blank_box; + bool blank_password; + char* console_dev; + uint8_t default_input; + uint8_t fg; + bool hide_borders; + bool hide_f1_commands; + uint8_t input_len; + char* lang; + bool load; + uint8_t margin_box_h; + uint8_t margin_box_v; + uint8_t max_desktop_len; + uint8_t max_login_len; + uint8_t max_password_len; + char* mcookie_cmd; + uint16_t min_refresh_delta; + char* path; + char* restart_cmd; + bool save; + char* save_file; + char* service_name; + char* shutdown_cmd; + char* term_reset_cmd; + uint8_t tty; + char* wayland_cmd; + bool wayland_specifier; + char* waylandsessions; + char* x_cmd; + char* x_cmd_setup; + char* xauth_cmd; + char* xsessions; +}; + +extern struct lang lang; +extern struct config config; + +void config_handle_str(void* data, char** pars, const int pars_count); +void lang_load(); +void config_load(const char *cfg_path); +void lang_defaults(); +void config_defaults(); +void lang_free(); +void config_free(); + +#endif diff --git a/src/dragonfail_error.h b/src/dragonfail_error.h new file mode 100644 index 0000000..f5ead2f --- /dev/null +++ b/src/dragonfail_error.h @@ -0,0 +1,27 @@ +#ifndef H_DRAGONFAIL_ERROR +#define H_DRAGONFAIL_ERROR + +enum dgn_error +{ + DGN_OK, // do not remove + + DGN_NULL, + DGN_ALLOC, + DGN_BOUNDS, + DGN_DOMAIN, + DGN_MLOCK, + DGN_XSESSIONS_DIR, + DGN_XSESSIONS_OPEN, + DGN_PATH, + DGN_CHDIR, + DGN_PWNAM, + DGN_USER_INIT, + DGN_USER_GID, + DGN_USER_UID, + DGN_PAM, + DGN_HOSTNAME, + + DGN_SIZE, // do not remove +}; + +#endif diff --git a/src/draw.c b/src/draw.c new file mode 100644 index 0000000..9d16a81 --- /dev/null +++ b/src/draw.c @@ -0,0 +1,893 @@ +#include "dragonfail.h" +#include "termbox.h" + +#include "inputs.h" +#include "utils.h" +#include "config.h" +#include "draw.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__DragonFly__) || defined(__FreeBSD__) + #include +#else // linux + #include +#endif + +#define DOOM_STEPS 13 + +void draw_init(struct term_buf* buf) +{ + buf->width = tb_width(); + buf->height = tb_height(); + hostname(&buf->info_line); + + uint16_t len_login = strlen(lang.login); + uint16_t len_password = strlen(lang.password); + + if (len_login > len_password) + { + buf->labels_max_len = len_login; + } + else + { + buf->labels_max_len = len_password; + } + + buf->box_height = 7 + (2 * config.margin_box_v); + buf->box_width = + (2 * config.margin_box_h) + + (config.input_len + 1) + + buf->labels_max_len; + +#if defined(__linux__) || defined(__FreeBSD__) + buf->box_chars.left_up = 0x250c; + buf->box_chars.left_down = 0x2514; + buf->box_chars.right_up = 0x2510; + buf->box_chars.right_down = 0x2518; + buf->box_chars.top = 0x2500; + buf->box_chars.bot = 0x2500; + buf->box_chars.left = 0x2502; + buf->box_chars.right = 0x2502; +#else + buf->box_chars.left_up = '+'; + buf->box_chars.left_down = '+'; + buf->box_chars.right_up = '+'; + buf->box_chars.right_down= '+'; + buf->box_chars.top = '-'; + buf->box_chars.bot = '-'; + buf->box_chars.left = '|'; + buf->box_chars.right = '|'; +#endif +} + +static void doom_free(struct term_buf* buf); +static void matrix_free(struct term_buf* buf); + +void draw_free(struct term_buf* buf) +{ + if (config.animate) + { + switch (config.animation) + { + case 0: + doom_free(buf); + break; + case 1: + matrix_free(buf); + break; + } + } +} + +void draw_box(struct term_buf* buf) +{ + uint16_t box_x = (buf->width - buf->box_width) / 2; + uint16_t box_y = (buf->height - buf->box_height) / 2; + uint16_t box_x2 = (buf->width + buf->box_width) / 2; + uint16_t box_y2 = (buf->height + buf->box_height) / 2; + buf->box_x = box_x; + buf->box_y = box_y; + + if (!config.hide_borders) + { + // corners + tb_change_cell( + box_x - 1, + box_y - 1, + buf->box_chars.left_up, + config.fg, + config.bg); + tb_change_cell( + box_x2, + box_y - 1, + buf->box_chars.right_up, + config.fg, + config.bg); + tb_change_cell( + box_x - 1, + box_y2, + buf->box_chars.left_down, + config.fg, + config.bg); + tb_change_cell( + box_x2, + box_y2, + buf->box_chars.right_down, + config.fg, + config.bg); + + // top and bottom + struct tb_cell c1 = {buf->box_chars.top, config.fg, config.bg}; + struct tb_cell c2 = {buf->box_chars.bot, config.fg, config.bg}; + + for (uint8_t i = 0; i < buf->box_width; ++i) + { + tb_put_cell( + box_x + i, + box_y - 1, + &c1); + tb_put_cell( + box_x + i, + box_y2, + &c2); + } + + // left and right + c1.ch = buf->box_chars.left; + c2.ch = buf->box_chars.right; + + for (uint8_t i = 0; i < buf->box_height; ++i) + { + tb_put_cell( + box_x - 1, + box_y + i, + &c1); + + tb_put_cell( + box_x2, + box_y + i, + &c2); + } + } + + if (config.blank_box) + { + struct tb_cell blank = {' ', config.fg, config.bg}; + + for (uint8_t i = 0; i < buf->box_height; ++i) + { + for (uint8_t k = 0; k < buf->box_width; ++k) + { + tb_put_cell( + box_x + k, + box_y + i, + &blank); + } + } + } +} + +struct tb_cell* strn_cell(char* s, uint16_t len) // throws +{ + struct tb_cell* cells = malloc((sizeof (struct tb_cell)) * len); + char* s2 = s; + uint32_t c; + + if (cells != NULL) + { + for (uint16_t i = 0; i < len; ++i) + { + if ((s2 - s) >= len) + { + break; + } + + s2 += utf8_char_to_unicode(&c, s2); + + cells[i].ch = c; + cells[i].bg = config.bg; + cells[i].fg = config.fg; + } + } + else + { + dgn_throw(DGN_ALLOC); + } + + return cells; +} + +struct tb_cell* str_cell(char* s) // throws +{ + return strn_cell(s, strlen(s)); +} + +void draw_labels(struct term_buf* buf) // throws +{ + // login text + struct tb_cell* login = str_cell(lang.login); + + if (dgn_catch()) + { + dgn_reset(); + } + else + { + tb_blit( + buf->box_x + config.margin_box_h, + buf->box_y + config.margin_box_v + 4, + strlen(lang.login), + 1, + login); + free(login); + } + + // password text + struct tb_cell* password = str_cell(lang.password); + + if (dgn_catch()) + { + dgn_reset(); + } + else + { + tb_blit( + buf->box_x + config.margin_box_h, + buf->box_y + config.margin_box_v + 6, + strlen(lang.password), + 1, + password); + free(password); + } + + if (buf->info_line != NULL) + { + uint16_t len = strlen(buf->info_line); + struct tb_cell* info_cell = str_cell(buf->info_line); + + if (dgn_catch()) + { + dgn_reset(); + } + else + { + tb_blit( + buf->box_x + ((buf->box_width - len) / 2), + buf->box_y + config.margin_box_v, + len, + 1, + info_cell); + free(info_cell); + } + } +} + +void draw_f_commands() +{ + struct tb_cell* f1 = str_cell(lang.f1); + + if (dgn_catch()) + { + dgn_reset(); + } + else + { + tb_blit(0, 0, strlen(lang.f1), 1, f1); + free(f1); + } + + struct tb_cell* f2 = str_cell(lang.f2); + + if (dgn_catch()) + { + dgn_reset(); + } + else + { + tb_blit(strlen(lang.f1) + 1, 0, strlen(lang.f2), 1, f2); + free(f2); + } +} + +void draw_lock_state(struct term_buf* buf) +{ + // get values + int fd = open(config.console_dev, O_RDONLY); + + if (fd < 0) + { + buf->info_line = lang.err_console_dev; + return; + } + + bool numlock_on; + bool capslock_on; + +#if defined(__DragonFly__) || defined(__FreeBSD__) + int led; + ioctl(fd, KDGETLED, &led); + numlock_on = led & LED_NUM; + capslock_on = led & LED_CAP; +#else // linux + char led; + ioctl(fd, KDGKBLED, &led); + numlock_on = led & K_NUMLOCK; + capslock_on = led & K_CAPSLOCK; +#endif + + close(fd); + + // print text + uint16_t pos_x = buf->width - strlen(lang.numlock); + + if (numlock_on) + { + struct tb_cell* numlock = str_cell(lang.numlock); + + if (dgn_catch()) + { + dgn_reset(); + } + else + { + tb_blit(pos_x, 0, strlen(lang.numlock), 1, numlock); + free(numlock); + } + } + + pos_x -= strlen(lang.capslock) + 1; + + if (capslock_on) + { + struct tb_cell* capslock = str_cell(lang.capslock); + + if (dgn_catch()) + { + dgn_reset(); + } + else + { + tb_blit(pos_x, 0, strlen(lang.capslock), 1, capslock); + free(capslock); + } + } +} + +void draw_desktop(struct desktop* target) +{ + uint16_t len = strlen(target->list[target->cur]); + + if (len > (target->visible_len - 3)) + { + len = target->visible_len - 3; + } + + tb_change_cell( + target->x, + target->y, + '<', + config.fg, + config.bg); + + tb_change_cell( + target->x + target->visible_len - 1, + target->y, + '>', + config.fg, + config.bg); + + for (uint16_t i = 0; i < len; ++ i) + { + tb_change_cell( + target->x + i + 2, + target->y, + target->list[target->cur][i], + config.fg, + config.bg); + } +} + +void draw_input(struct text* input) +{ + uint16_t len = strlen(input->text); + uint16_t visible_len = input->visible_len; + + if (len > visible_len) + { + len = visible_len; + } + + struct tb_cell* cells = strn_cell(input->visible_start, len); + + if (dgn_catch()) + { + dgn_reset(); + } + else + { + tb_blit(input->x, input->y, len, 1, cells); + free(cells); + + struct tb_cell c1 = {' ', config.fg, config.bg}; + + for (uint16_t i = input->end - input->visible_start; i < visible_len; ++i) + { + tb_put_cell( + input->x + i, + input->y, + &c1); + } + } +} + +void draw_input_mask(struct text* input) +{ + uint16_t len = strlen(input->text); + uint16_t visible_len = input->visible_len; + + if (len > visible_len) + { + len = visible_len; + } + + struct tb_cell c1 = {config.asterisk, config.fg, config.bg}; + struct tb_cell c2 = {' ', config.fg, config.bg}; + + for (uint16_t i = 0; i < visible_len; ++i) + { + if (input->visible_start + i < input->end) + { + tb_put_cell( + input->x + i, + input->y, + &c1); + } + else + { + tb_put_cell( + input->x + i, + input->y, + &c2); + } + } +} + +void position_input( + struct term_buf* buf, + struct desktop* desktop, + struct text* login, + struct text* password) +{ + uint16_t x = buf->box_x + config.margin_box_h + buf->labels_max_len + 1; + int32_t len = buf->box_x + buf->box_width - config.margin_box_h - x; + + if (len < 0) + { + return; + } + + desktop->x = x; + desktop->y = buf->box_y + config.margin_box_v + 2; + desktop->visible_len = len; + + login->x = x; + login->y = buf->box_y + config.margin_box_v + 4; + login->visible_len = len; + + password->x = x; + password->y = buf->box_y + config.margin_box_v + 6; + password->visible_len = len; +} + +static void doom_init(struct term_buf* buf) +{ + buf->init_width = buf->width; + buf->init_height = buf->height; + buf->astate.doom = malloc(sizeof(struct doom_state)); + + if (buf->astate.doom == NULL) + { + dgn_throw(DGN_ALLOC); + } + + uint16_t tmp_len = buf->width * buf->height; + buf->astate.doom->buf = malloc(tmp_len); + tmp_len -= buf->width; + + if (buf->astate.doom->buf == NULL) + { + dgn_throw(DGN_ALLOC); + } + + memset(buf->astate.doom->buf, 0, tmp_len); + memset(buf->astate.doom->buf + tmp_len, DOOM_STEPS - 1, buf->width); +} + +static void doom_free(struct term_buf* buf) +{ + free(buf->astate.doom->buf); + free(buf->astate.doom); +} + +// Adapted from cmatrix +static void matrix_init(struct term_buf* buf) +{ + buf->init_width = buf->width; + buf->init_height = buf->height; + buf->astate.matrix = malloc(sizeof(struct matrix_state)); + struct matrix_state* s = buf->astate.matrix; + + if (s == NULL) + { + dgn_throw(DGN_ALLOC); + } + + uint16_t len = buf->height + 1; + s->grid = malloc(sizeof(struct matrix_dot*) * len); + + if (s->grid == NULL) + { + dgn_throw(DGN_ALLOC); + } + + len = (buf->height + 1) * buf->width; + (s->grid)[0] = malloc(sizeof(struct matrix_dot) * len); + + if ((s->grid)[0] == NULL) + { + dgn_throw(DGN_ALLOC); + } + + for (int i = 1; i <= buf->height; ++i) + { + s->grid[i] = s->grid[i - 1] + buf->width; + + if (s->grid[i] == NULL) + { + dgn_throw(DGN_ALLOC); + } + } + + s->length = malloc(buf->width * sizeof(int)); + + if (s->length == NULL) + { + dgn_throw(DGN_ALLOC); + } + + s->spaces = malloc(buf->width * sizeof(int)); + + if (s->spaces == NULL) + { + dgn_throw(DGN_ALLOC); + } + + s->updates = malloc(buf->width * sizeof(int)); + + if (s->updates == NULL) + { + dgn_throw(DGN_ALLOC); + } + + // Initialize grid + for (int i = 0; i <= buf->height; ++i) + { + for (int j = 0; j <= buf->width - 1; j += 2) + { + s->grid[i][j].val = -1; + } + } + + for (int j = 0; j < buf->width; j += 2) + { + s->spaces[j] = (int) rand() % buf->height + 1; + s->length[j] = (int) rand() % (buf->height - 3) + 3; + s->grid[1][j].val = ' '; + s->updates[j] = (int) rand() % 3 + 1; + } +} + +static void matrix_free(struct term_buf* buf) +{ + free(buf->astate.matrix->grid[0]); + free(buf->astate.matrix->grid); + free(buf->astate.matrix->length); + free(buf->astate.matrix->spaces); + free(buf->astate.matrix->updates); + free(buf->astate.matrix); +} + +void animate_init(struct term_buf* buf) +{ + if (config.animate) + { + switch(config.animation) + { + case 0: + { + doom_init(buf); + break; + } + case 1: + { + matrix_init(buf); + break; + } + } + } +} + +static void doom(struct term_buf* term_buf) +{ + static struct tb_cell fire[DOOM_STEPS] = + { + {' ', 9, 0}, // default + {0x2591, 2, 0}, // red + {0x2592, 2, 0}, // red + {0x2593, 2, 0}, // red + {0x2588, 2, 0}, // red + {0x2591, 4, 2}, // yellow + {0x2592, 4, 2}, // yellow + {0x2593, 4, 2}, // yellow + {0x2588, 4, 2}, // yellow + {0x2591, 8, 4}, // white + {0x2592, 8, 4}, // white + {0x2593, 8, 4}, // white + {0x2588, 8, 4}, // white + }; + + uint16_t src; + uint16_t random; + uint16_t dst; + + uint16_t w = term_buf->init_width; + uint8_t* tmp = term_buf->astate.doom->buf; + + if ((term_buf->width != term_buf->init_width) || (term_buf->height != term_buf->init_height)) + { + return; + } + + struct tb_cell* buf = tb_cell_buffer(); + + for (uint16_t x = 0; x < w; ++x) + { + for (uint16_t y = 1; y < term_buf->init_height; ++y) + { + src = y * w + x; + random = ((rand() % 7) & 3); + dst = src - random + 1; + + if (w > dst) + { + dst = 0; + } + else + { + dst -= w; + } + + tmp[dst] = tmp[src] - (random & 1); + + if (tmp[dst] > 12) + { + tmp[dst] = 0; + } + + buf[dst] = fire[tmp[dst]]; + buf[src] = fire[tmp[src]]; + } + } +} + +// Adapted from cmatrix +static void matrix(struct term_buf* buf) +{ + static int frame = 3; + const int frame_delay = 8; + static int count = 0; + bool first_col; + struct matrix_state* s = buf->astate.matrix; + + // Allowed codepoints + const int randmin = 33; + const int randnum = 123 - randmin; + // Chars change mid-scroll + const bool changes = true; + + if ((buf->width != buf->init_width) || (buf->height != buf->init_height)) + { + return; + } + + count += 1; + if (count > frame_delay) { + frame += 1; + if (frame > 4) frame = 1; + count = 0; + + for (int j = 0; j < buf->width; j += 2) + { + int tail; + if (frame > s->updates[j]) + { + if (s->grid[0][j].val == -1 && s->grid[1][j].val == ' ') + { + if (s->spaces[j] > 0) + { + s->spaces[j]--; + } else { + s->length[j] = (int) rand() % (buf->height - 3) + 3; + s->grid[0][j].val = (int) rand() % randnum + randmin; + s->spaces[j] = (int) rand() % buf->height + 1; + } + } + + int i = 0, seg_len = 0; + first_col = 1; + while (i <= buf->height) + { + // Skip over spaces + while (i <= buf->height + && (s->grid[i][j].val == ' ' || s->grid[i][j].val == -1)) + { + i++; + } + + if (i > buf->height) break; + + // Find the head of this col + tail = i; + seg_len = 0; + while (i <= buf->height + && (s->grid[i][j].val != ' ' && s->grid[i][j].val != -1)) + { + s->grid[i][j].is_head = false; + if (changes) + { + if (rand() % 8 == 0) + s->grid[i][j].val = (int) rand() % randnum + randmin; + } + i++; + seg_len++; + } + + // Head's down offscreen + if (i > buf->height) + { + s->grid[tail][j].val = ' '; + continue; + } + + s->grid[i][j].val = (int) rand() % randnum + randmin; + s->grid[i][j].is_head = true; + + if (seg_len > s->length[j] || !first_col) { + s->grid[tail][j].val = ' '; + s->grid[0][j].val = -1; + } + first_col = 0; + i++; + } + } + } + } + + uint32_t blank; + utf8_char_to_unicode(&blank, " "); + + for (int j = 0; j < buf->width; j += 2) { + for (int i = 1; i <= buf->height; ++i) + { + uint32_t c; + int fg = TB_GREEN; + int bg = TB_DEFAULT; + + if (s->grid[i][j].val == -1 || s->grid[i][j].val == ' ') + { + tb_change_cell(j, i - 1, blank, fg, bg); + continue; + } + + char tmp[2]; + tmp[0] = s->grid[i][j].val; + tmp[1] = '\0'; + if(utf8_char_to_unicode(&c, tmp)) + { + if (s->grid[i][j].is_head) + { + fg = TB_WHITE | TB_BOLD; + } + tb_change_cell(j, i - 1, c, fg, bg); + } + } + } +} + +void animate(struct term_buf* buf) +{ + buf->width = tb_width(); + buf->height = tb_height(); + + if (config.animate) + { + switch(config.animation) + { + case 0: + { + doom(buf); + break; + } + case 1: + { + matrix(buf); + break; + } + } + } +} + +bool cascade(struct term_buf* term_buf, uint8_t* fails) +{ + uint16_t width = term_buf->width; + uint16_t height = term_buf->height; + + struct tb_cell* buf = tb_cell_buffer(); + bool changes = false; + char c_under; + char c; + + for (int i = height - 2; i >= 0; --i) + { + for (int k = 0; k < width; ++k) + { + c = buf[i * width + k].ch; + + if (isspace(c)) + { + continue; + } + + c_under = buf[(i + 1) * width + k].ch; + + if (!isspace(c_under)) + { + continue; + } + + if (!changes) + { + changes = true; + } + + if ((rand() % 10) > 7) + { + continue; + } + + buf[(i + 1) * width + k] = buf[i * width + k]; + buf[i * width + k].ch = ' '; + } + } + + // stop force-updating + if (!changes) + { + sleep(7); + *fails = 0; + + return false; + } + + // force-update + return true; +} diff --git a/src/draw.h b/src/draw.h new file mode 100644 index 0000000..1ca7809 --- /dev/null +++ b/src/draw.h @@ -0,0 +1,89 @@ +#ifndef H_4y_DRAW +#define H_4y_DRAW + +#include "termbox.h" +#include "inputs.h" + +#include +#include + +struct box +{ + uint32_t left_up; + uint32_t left_down; + uint32_t right_up; + uint32_t right_down; + uint32_t top; + uint32_t bot; + uint32_t left; + uint32_t right; +}; + +struct matrix_dot +{ + int val; + bool is_head; +}; + +struct matrix_state +{ + struct matrix_dot** grid; + int* length; + int* spaces; + int* updates; +}; + +struct doom_state +{ + uint8_t* buf; +}; + +union anim_state +{ + struct doom_state* doom; + struct matrix_state* matrix; +}; + +struct term_buf +{ + uint16_t width; + uint16_t height; + uint16_t init_width; + uint16_t init_height; + + struct box box_chars; + char* info_line; + uint16_t labels_max_len; + uint16_t box_x; + uint16_t box_y; + uint16_t box_width; + uint16_t box_height; + + union anim_state astate; +}; + +void draw_init(struct term_buf* buf); +void draw_free(struct term_buf* buf); +void draw_box(struct term_buf* buf); + +struct tb_cell* strn_cell(char* s, uint16_t len); +struct tb_cell* str_cell(char* s); + +void draw_labels(struct term_buf* buf); +void draw_f_commands(); +void draw_lock_state(struct term_buf* buf); +void draw_desktop(struct desktop* target); +void draw_input(struct text* input); +void draw_input_mask(struct text* input); + +void position_input( + struct term_buf* buf, + struct desktop* desktop, + struct text* login, + struct text* password); + +void animate_init(struct term_buf* buf); +void animate(struct term_buf* buf); +bool cascade(struct term_buf* buf, uint8_t* fails); + +#endif diff --git a/src/inputs.c b/src/inputs.c new file mode 100644 index 0000000..d425c06 --- /dev/null +++ b/src/inputs.c @@ -0,0 +1,267 @@ +#include "dragonfail.h" +#include "termbox.h" +#include "inputs.h" +#include "config.h" + +#include +#include +#include +#include + +void handle_desktop(void* input_struct, struct tb_event* event) +{ + struct desktop* target = (struct desktop*) input_struct; + + if ((event != NULL) && (event->type == TB_EVENT_KEY)) + { + if (event->key == TB_KEY_ARROW_LEFT) + { + input_desktop_right(target); + } + else if (event->key == TB_KEY_ARROW_RIGHT) + { + input_desktop_left(target); + } + } + + tb_set_cursor(target->x + 2, target->y); +} + +void handle_text(void* input_struct, struct tb_event* event) +{ + struct text* target = (struct text*) input_struct; + + if ((event != NULL) && (event->type == TB_EVENT_KEY)) + { + if (event->key == TB_KEY_ARROW_LEFT) + { + input_text_left(target); + } + else if (event->key == TB_KEY_ARROW_RIGHT) + { + input_text_right(target); + } + else if (event->key == TB_KEY_DELETE) + { + input_text_delete(target); + } + else if ((event->key == TB_KEY_BACKSPACE) + || (event->key == TB_KEY_BACKSPACE2)) + { + input_text_backspace(target); + } + else if (((event->ch > 31) && (event->ch < 127)) + || (event->key == TB_KEY_SPACE)) + { + char buf[7] = {0}; + + if (event->key == TB_KEY_SPACE) + { + buf[0] = ' '; + } + else + { + utf8_unicode_to_char(buf, event->ch); + } + + input_text_write(target, buf[0]); + } + } + + tb_set_cursor( + target->x + (target->cur - target->visible_start), + target->y); +} + +void input_desktop(struct desktop* target) +{ + target->list = NULL; + target->cmd = NULL; + target->display_server = NULL; + target->cur = 0; + target->len = 0; + + input_desktop_add(target, strdup(lang.shell), strdup(""), DS_SHELL); + input_desktop_add(target, strdup(lang.xinitrc), strdup("~/.xinitrc"), DS_XINITRC); +#if 0 + input_desktop_add(target, strdup(lang.wayland), strdup(""), DS_WAYLAND); +#endif +} + +void input_text(struct text* target, uint64_t len) +{ + target->text = malloc(len + 1); + + if (target->text == NULL) + { + dgn_throw(DGN_ALLOC); + return; + } + else + { + int ok = mlock(target->text, len + 1); + + if (ok < 0) + { + dgn_throw(DGN_MLOCK); + return; + } + + memset(target->text, 0, len + 1); + } + + target->cur = target->text; + target->end = target->text; + target->visible_start = target->text; + target->len = len; + target->x = 0; + target->y = 0; +} + +void input_desktop_free(struct desktop* target) +{ + if (target != NULL) + { + for (uint16_t i = 0; i < target->len; ++i) + { + if (target->list[i] != NULL) + { + free(target->list[i]); + } + + if (target->cmd[i] != NULL) + { + free(target->cmd[i]); + } + } + + free(target->list); + free(target->cmd); + free(target->display_server); + } +} + +void input_text_free(struct text* target) +{ + memset(target->text, 0, target->len); + munlock(target->text, target->len + 1); + free(target->text); +} + +void input_desktop_right(struct desktop* target) +{ + ++(target->cur); + + if (target->cur >= target->len) + { + target->cur = 0; + } +} + +void input_desktop_left(struct desktop* target) +{ + --(target->cur); + + if (target->cur >= target->len) + { + target->cur = target->len - 1; + } +} + +void input_desktop_add( + struct desktop* target, + char* name, + char* cmd, + enum display_server display_server) +{ + ++(target->len); + target->list = realloc(target->list, target->len * (sizeof (char*))); + target->cmd = realloc(target->cmd, target->len * (sizeof (char*))); + target->display_server = realloc( + target->display_server, + target->len * (sizeof (enum display_server))); + target->cur = target->len - 1; + + if ((target->list == NULL) + || (target->cmd == NULL) + || (target->display_server == NULL)) + { + dgn_throw(DGN_ALLOC); + return; + } + + target->list[target->cur] = name; + target->cmd[target->cur] = cmd; + target->display_server[target->cur] = display_server; +} + +void input_text_right(struct text* target) +{ + if (target->cur < target->end) + { + ++(target->cur); + + if ((target->cur - target->visible_start) > target->visible_len) + { + ++(target->visible_start); + } + } +} + +void input_text_left(struct text* target) +{ + if (target->cur > target->text) + { + --(target->cur); + + if ((target->cur - target->visible_start) < 0) + { + --(target->visible_start); + } + } +} + +void input_text_write(struct text* target, char ascii) +{ + if (ascii <= 0) + { + return; // unices do not support usernames and passwords other than ascii + } + + if ((target->end - target->text + 1) < target->len) + { + // moves the text to the right to add space for the new ascii char + memcpy(target->cur + 1, target->cur, target->end - target->cur); + ++(target->end); + // adds the new char and moves the cursor to the right + *(target->cur) = ascii; + input_text_right(target); + } +} + +void input_text_delete(struct text* target) +{ + if (target->cur < target->end) + { + // moves the text on the right to overwrite the currently pointed char + memcpy(target->cur, target->cur + 1, target->end - target->cur + 1); + --(target->end); + } +} + +void input_text_backspace(struct text* target) +{ + if (target->cur > target->text) + { + input_text_left(target); + input_text_delete(target); + } +} + +void input_text_clear(struct text* target) +{ + memset(target->text, 0, target->len + 1); + target->cur = target->text; + target->end = target->text; + target->visible_start = target->text; +} diff --git a/src/inputs.h b/src/inputs.h new file mode 100644 index 0000000..ccc4e00 --- /dev/null +++ b/src/inputs.h @@ -0,0 +1,56 @@ +#ifndef H_4y_INPUTS +#define H_4y_INPUTS + +#include "termbox.h" + +#include + +enum display_server {DS_WAYLAND, DS_SHELL, DS_XINITRC, DS_XORG}; + +struct text +{ + char* text; + char* end; + int64_t len; + char* cur; + char* visible_start; + uint16_t visible_len; + + uint16_t x; + uint16_t y; +}; + +struct desktop +{ + char** list; + char** cmd; + enum display_server* display_server; + + uint16_t cur; + uint16_t len; + uint16_t visible_len; + uint16_t x; + uint16_t y; +}; + +void handle_desktop(void* input_struct, struct tb_event* event); +void handle_text(void* input_struct, struct tb_event* event); +void input_desktop(struct desktop* target); +void input_text(struct text* target, uint64_t len); +void input_desktop_free(struct desktop* target); +void input_text_free(struct text* target); +void input_desktop_right(struct desktop* target); +void input_desktop_left(struct desktop* target); +void input_desktop_add( + struct desktop* target, + char* name, + char* cmd, + enum display_server display_server); +void input_text_right(struct text* target); +void input_text_left(struct text* target); +void input_text_write(struct text* target, char ascii); +void input_text_delete(struct text* target); +void input_text_backspace(struct text* target); +void input_text_clear(struct text* target); + +#endif diff --git a/src/login.c b/src/login.c new file mode 100644 index 0000000..1a8e229 --- /dev/null +++ b/src/login.c @@ -0,0 +1,680 @@ +#include "dragonfail.h" +#include "termbox.h" + +#include "inputs.h" +#include "draw.h" +#include "utils.h" +#include "config.h" +#include "login.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int get_free_display() +{ + char xlock[1024]; + uint8_t i; + + for (i = 0; i < 200; ++i) + { + snprintf(xlock, 1024, "/tmp/.X%d-lock", i); + + if (access(xlock, F_OK) == -1) + { + break; + } + } + + return i; +} + +void reset_terminal(struct passwd* pwd) +{ + pid_t pid = fork(); + + if (pid == 0) + { + execl(pwd->pw_shell, pwd->pw_shell, "-c", config.term_reset_cmd, NULL); + exit(EXIT_SUCCESS); + } + + int status; + waitpid(pid, &status, 0); +} + +int login_conv( + int num_msg, + const struct pam_message** msg, + struct pam_response** resp, + void* appdata_ptr) +{ + *resp = calloc(num_msg, sizeof (struct pam_response)); + + if (*resp == NULL) + { + return PAM_BUF_ERR; + } + + char* username; + char* password; + int ok = PAM_SUCCESS; + int i; + + for (i = 0; i < num_msg; ++i) + { + switch (msg[i]->msg_style) + { + case PAM_PROMPT_ECHO_ON: + { + username = ((char**) appdata_ptr)[0]; + (*resp)[i].resp = strdup(username); + break; + } + case PAM_PROMPT_ECHO_OFF: + { + password = ((char**) appdata_ptr)[1]; + (*resp)[i].resp = strdup(password); + break; + } + case PAM_ERROR_MSG: + { + ok = PAM_CONV_ERR; + break; + } + } + + if (ok != PAM_SUCCESS) + { + break; + } + } + + if (ok != PAM_SUCCESS) + { + for (i = 0; i < num_msg; ++i) + { + if ((*resp)[i].resp == NULL) + { + continue; + } + + free((*resp)[i].resp); + (*resp)[i].resp = NULL; + } + + free(*resp); + *resp = NULL; + } + + return ok; +} + +void pam_diagnose(int error, struct term_buf* buf) +{ + switch (error) + { + case PAM_ACCT_EXPIRED: + { + buf->info_line = lang.err_pam_acct_expired; + break; + } + case PAM_AUTH_ERR: + { + buf->info_line = lang.err_pam_auth; + break; + } + case PAM_AUTHINFO_UNAVAIL: + { + buf->info_line = lang.err_pam_authinfo_unavail; + break; + } + case PAM_BUF_ERR: + { + buf->info_line = lang.err_pam_buf; + break; + } + case PAM_CRED_ERR: + { + buf->info_line = lang.err_pam_cred_err; + break; + } + case PAM_CRED_EXPIRED: + { + buf->info_line = lang.err_pam_cred_expired; + break; + } + case PAM_CRED_INSUFFICIENT: + { + buf->info_line = lang.err_pam_cred_insufficient; + break; + } + case PAM_CRED_UNAVAIL: + { + buf->info_line = lang.err_pam_cred_unavail; + break; + } + case PAM_MAXTRIES: + { + buf->info_line = lang.err_pam_maxtries; + break; + } + case PAM_NEW_AUTHTOK_REQD: + { + buf->info_line = lang.err_pam_authok_reqd; + break; + } + case PAM_PERM_DENIED: + { + buf->info_line = lang.err_pam_perm_denied; + break; + } + case PAM_SESSION_ERR: + { + buf->info_line = lang.err_pam_session; + break; + } + case PAM_SYSTEM_ERR: + { + buf->info_line = lang.err_pam_sys; + break; + } + case PAM_USER_UNKNOWN: + { + buf->info_line = lang.err_pam_user_unknown; + break; + } + case PAM_ABORT: + default: + { + buf->info_line = lang.err_pam_abort; + break; + } + } + + dgn_throw(DGN_PAM); +} + +void env_init(struct passwd* pwd) +{ + extern char** environ; + char* term = getenv("TERM"); + char* lang = getenv("LANG"); + // clean env + environ[0] = NULL; + + setenv("TERM", term ? term : "linux", 1); + setenv("HOME", pwd->pw_dir, 1); + setenv("PWD", pwd->pw_dir, 1); + setenv("SHELL", pwd->pw_shell, 1); + setenv("USER", pwd->pw_name, 1); + setenv("LOGNAME", pwd->pw_name, 1); + setenv("LANG", lang ? lang : "C", 1); + + // Set PATH if specified in the configuration + if (strlen(config.path)) + { + int ok = setenv("PATH", config.path, 1); + + if (ok != 0) + { + dgn_throw(DGN_PATH); + } + } +} + +void env_xdg_session(const enum display_server display_server) +{ + switch (display_server) + { + case DS_WAYLAND: + { + setenv("XDG_SESSION_TYPE", "wayland", 1); + break; + } + case DS_SHELL: + { + setenv("XDG_SESSION_TYPE", "tty", 0); + break; + } + case DS_XINITRC: + case DS_XORG: + { + setenv("XDG_SESSION_TYPE", "x11", 0); + break; + } + } +} + +void env_xdg(const char* tty_id) +{ + char user[15]; + snprintf(user, 15, "/run/user/%d", getuid()); + setenv("XDG_RUNTIME_DIR", user, 0); + setenv("XDG_SESSION_CLASS", "user", 0); + setenv("XDG_SESSION_ID", "1", 0); + setenv("XDG_SEAT", "seat0", 0); + setenv("XDG_VTNR", tty_id, 0); +} + +void add_utmp_entry( + struct utmp *entry, + char *username, + pid_t display_pid +) { + entry->ut_type = USER_PROCESS; + entry->ut_pid = display_pid; + strcpy(entry->ut_line, ttyname(STDIN_FILENO) + strlen("/dev/")); + + /* only correct for ptys named /dev/tty[pqr][0-9a-z] */ + strcpy(entry->ut_id, ttyname(STDIN_FILENO) + strlen("/dev/tty")); + + time((long int *) &entry->ut_time); + + strncpy(entry->ut_user, username, UT_NAMESIZE); + memset(entry->ut_host, 0, UT_HOSTSIZE); + entry->ut_addr = 0; + setutent(); + + pututline(entry); +} + +void remove_utmp_entry(struct utmp *entry) { + entry->ut_type = DEAD_PROCESS; + memset(entry->ut_line, 0, UT_LINESIZE); + entry->ut_time = 0; + memset(entry->ut_user, 0, UT_NAMESIZE); + setutent(); + pututline(entry); + endutent(); +} + +void xauth(const char* display_name, const char* shell, const char* dir) +{ + char xauthority[256]; + snprintf(xauthority, 256, "%s/%s", dir, ".4yxauth"); + setenv("XAUTHORITY", xauthority, 1); + setenv("DISPLAY", display_name, 1); + + FILE* fp = fopen(xauthority, "ab+"); + + if (fp != NULL) + { + fclose(fp); + } + + pid_t pid = fork(); + + if (pid == 0) + { + char cmd[1024]; + snprintf( + cmd, + 1024, + "%s add %s . `%s`", + config.xauth_cmd, + display_name, + config.mcookie_cmd); + execl(shell, shell, "-c", cmd, NULL); + exit(EXIT_SUCCESS); + } + + int status; + waitpid(pid, &status, 0); +} + +void xorg( + struct passwd* pwd, + const char* vt, + const char* desktop_cmd) +{ + // generate xauthority file + const char* xauth_dir = getenv("XDG_CONFIG_HOME"); + + if ((xauth_dir == NULL) || (*xauth_dir == '\0')) + { + xauth_dir = pwd->pw_dir; + } + + char display_name[4]; + + snprintf(display_name, 3, ":%d", get_free_display()); + xauth(display_name, pwd->pw_shell, xauth_dir); + + // start xorg + pid_t pid = fork(); + + if (pid == 0) + { + char x_cmd[1024]; + snprintf( + x_cmd, + 1024, + "%s %s %s", + config.x_cmd, + display_name, + vt); + execl(pwd->pw_shell, pwd->pw_shell, "-c", x_cmd, NULL); + exit(EXIT_SUCCESS); + } + + int ok; + xcb_connection_t* xcb; + + do + { + xcb = xcb_connect(NULL, NULL); + ok = xcb_connection_has_error(xcb); + kill(pid, 0); + } + while((ok != 0) && (errno != ESRCH)); + + if (ok != 0) + { + return; + } + + pid_t xorg_pid = fork(); + + if (xorg_pid == 0) + { + char de_cmd[1024]; + snprintf( + de_cmd, + 1024, + "%s %s", + config.x_cmd_setup, + desktop_cmd); + execl(pwd->pw_shell, pwd->pw_shell, "-c", de_cmd, NULL); + exit(EXIT_SUCCESS); + } + + int status; + waitpid(xorg_pid, &status, 0); + xcb_disconnect(xcb); + kill(pid, 0); + + if (errno != ESRCH) + { + kill(pid, SIGTERM); + waitpid(pid, &status, 0); + } +} + +void wayland( + struct passwd* pwd, + const char* desktop_cmd) +{ + + char cmd[1024]; + snprintf(cmd, 1024, "%s %s", config.wayland_cmd, desktop_cmd); + execl(pwd->pw_shell, pwd->pw_shell, "-c", cmd, NULL); +} + +void shell(struct passwd* pwd) +{ + const char* pos = strrchr(pwd->pw_shell, '/'); + char args[1024]; + args[0] = '-'; + + if (pos != NULL) + { + pos = pos + 1; + } + else + { + pos = pwd->pw_shell; + } + + strncpy(args + 1, pos, 1023); + execl(pwd->pw_shell, args, NULL); +} + +// pam_do performs the pam action specified in pam_action +// on pam_action fail, call diagnose and end pam session +int pam_do( + int (pam_action)(struct pam_handle *, int), + struct pam_handle *handle, + int flags, + struct term_buf *buf) +{ + int status = pam_action(handle, flags); + + if (status != PAM_SUCCESS) { + pam_diagnose(status, buf); + pam_end(handle, status); + } + + return status; +} + +void auth( + struct desktop* desktop, + struct text* login, + struct text* password, + struct term_buf* buf) +{ + int ok; + + // open pam session + const char* creds[2] = {login->text, password->text}; + struct pam_conv conv = {login_conv, creds}; + struct pam_handle* handle; + + ok = pam_start(config.service_name, NULL, &conv, &handle); + + // Set XDG_SESSION_TYPE earlier to fix some bugs + env_xdg_session(desktop->display_server[desktop->cur]); + + if (ok != PAM_SUCCESS) + { + pam_diagnose(ok, buf); + pam_end(handle, ok); + return; + } + + ok = pam_do(pam_authenticate, handle, 0, buf); + + if (ok != PAM_SUCCESS) + { + return; + } + + ok = pam_do(pam_acct_mgmt, handle, 0, buf); + + if (ok != PAM_SUCCESS) + { + return; + } + + ok = pam_do(pam_setcred, handle, PAM_ESTABLISH_CRED, buf); + + if (ok != PAM_SUCCESS) + { + return; + } + + ok = pam_do(pam_open_session, handle, 0, buf); + + if (ok != PAM_SUCCESS) + { + return; + } + + // clear the credentials + input_text_clear(password); + + // get passwd structure + struct passwd* pwd = getpwnam(login->text); + endpwent(); + + if (pwd == NULL) + { + dgn_throw(DGN_PWNAM); + pam_end(handle, ok); + return; + } + + // set user shell + if (pwd->pw_shell[0] == '\0') + { + setusershell(); + + char* shell = getusershell(); + + if (shell != NULL) + { + strcpy(pwd->pw_shell, shell); + } + + endusershell(); + } + + // restore regular terminal mode + tb_clear(); + tb_present(); + tb_shutdown(); + + // start desktop environment + pid_t pid = fork(); + + if (pid == 0) + { + // set user info + ok = initgroups(pwd->pw_name, pwd->pw_gid); + + if (ok != 0) + { + dgn_throw(DGN_USER_INIT); + exit(EXIT_FAILURE); + } + + ok = setgid(pwd->pw_gid); + + if (ok != 0) + { + dgn_throw(DGN_USER_GID); + exit(EXIT_FAILURE); + } + + ok = setuid(pwd->pw_uid); + + if (ok != 0) + { + dgn_throw(DGN_USER_UID); + exit(EXIT_FAILURE); + } + + // get a display + char tty_id [3]; + char vt[5]; + + snprintf(tty_id, 3, "%d", config.tty); + snprintf(vt, 5, "vt%d", config.tty); + + // set env + env_init(pwd); + + if (dgn_catch()) + { + exit(EXIT_FAILURE); + } + + // add pam variables + char** env = pam_getenvlist(handle); + + for (uint16_t i = 0; env && env[i]; ++i) + { + putenv(env[i]); + } + + // add xdg variables + env_xdg(tty_id); + + // execute + int ok = chdir(pwd->pw_dir); + + if (ok != 0) + { + dgn_throw(DGN_CHDIR); + exit(EXIT_FAILURE); + } + + reset_terminal(pwd); + switch (desktop->display_server[desktop->cur]) + { + case DS_WAYLAND: + { + wayland(pwd, desktop->cmd[desktop->cur]); + break; + } + case DS_SHELL: + { + shell(pwd); + break; + } + case DS_XINITRC: + case DS_XORG: + { + xorg(pwd, vt, desktop->cmd[desktop->cur]); + break; + } + } + + exit(EXIT_SUCCESS); + } + + // add utmp audit + struct utmp entry; + add_utmp_entry(&entry, pwd->pw_name, pid); + + // wait for the session to stop + int status; + waitpid(pid, &status, 0); + remove_utmp_entry(&entry); + + reset_terminal(pwd); + + // reinit termbox + tb_init(); + tb_select_output_mode(TB_OUTPUT_NORMAL); + + // reload the desktop environment list on logout + input_desktop_free(desktop); + input_desktop(desktop); + desktop_load(desktop); + + // close pam session + ok = pam_do(pam_close_session, handle, 0, buf); + + if (ok != PAM_SUCCESS) + { + return; + } + + ok = pam_do(pam_setcred, handle, PAM_DELETE_CRED, buf); + + if (ok != PAM_SUCCESS) + { + return; + } + + ok = pam_end(handle, 0); + + if (ok != PAM_SUCCESS) + { + pam_diagnose(ok, buf); + } +} + diff --git a/src/login.h b/src/login.h new file mode 100644 index 0000000..b3120ef --- /dev/null +++ b/src/login.h @@ -0,0 +1,13 @@ +#ifndef H_4y_LOGIN +#define H_4y_LOGIN + +#include "draw.h" +#include "inputs.h" + +void auth( + struct desktop* desktop, + struct text* login, + struct text* password, + struct term_buf* buf); + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..357fe7b --- /dev/null +++ b/src/main.c @@ -0,0 +1,325 @@ +#include "argoat.h" +#include "configator.h" +#include "dragonfail.h" +#include "termbox.h" + +#include "draw.h" +#include "inputs.h" +#include "login.h" +#include "utils.h" +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#define ARG_COUNT 7 +// things you can define: +// GIT_VERSION_STRING + +// global +struct lang lang; +struct config config; + +// args handles +void arg_help(void* data, char** pars, const int pars_count) +{ + printf("RTFM\n"); +} + +void arg_version(void* data, char** pars, const int pars_count) +{ +#ifdef GIT_VERSION_STRING + printf("4y version %s\n", GIT_VERSION_STRING); +#else + printf("4y version unknown\n"); +#endif +} + +// low-level error messages +void log_init(char** log) +{ + log[DGN_OK] = lang.err_dgn_oob; + log[DGN_NULL] = lang.err_null; + log[DGN_ALLOC] = lang.err_alloc; + log[DGN_BOUNDS] = lang.err_bounds; + log[DGN_DOMAIN] = lang.err_domain; + log[DGN_MLOCK] = lang.err_mlock; + log[DGN_XSESSIONS_DIR] = lang.err_xsessions_dir; + log[DGN_XSESSIONS_OPEN] = lang.err_xsessions_open; + log[DGN_PATH] = lang.err_path; + log[DGN_CHDIR] = lang.err_chdir; + log[DGN_PWNAM] = lang.err_pwnam; + log[DGN_USER_INIT] = lang.err_user_init; + log[DGN_USER_GID] = lang.err_user_gid; + log[DGN_USER_UID] = lang.err_user_uid; + log[DGN_PAM] = lang.err_pam; + log[DGN_HOSTNAME] = lang.err_hostname; +} + +void arg_config(void* data, char** pars, const int pars_count) +{ + *((char **)data) = *pars; +} + +// 4y! +int main(int argc, char** argv) +{ + // init error lib + log_init(dgn_init()); + + // load config + config_defaults(); + lang_defaults(); + + char *config_path = NULL; + // parse args + const struct argoat_sprig sprigs[ARG_COUNT] = + { + {NULL, 0, NULL, NULL}, + {"config", 0, &config_path, arg_config}, + {"c", 0, &config_path, arg_config}, + {"help", 0, NULL, arg_help}, + {"h", 0, NULL, arg_help}, + {"version", 0, NULL, arg_version}, + {"v", 0, NULL, arg_version}, + }; + + struct argoat args = {sprigs, ARG_COUNT, NULL, 0, 0}; + argoat_graze(&args, argc, argv); + + // init inputs + struct desktop desktop; + struct text login; + struct text password; + input_desktop(&desktop); + input_text(&login, config.max_login_len); + input_text(&password, config.max_password_len); + + if (dgn_catch()) + { + config_free(); + lang_free(); + return 1; + } + + config_load(config_path); + lang_load(); + + void* input_structs[3] = + { + (void*) &desktop, + (void*) &login, + (void*) &password, + }; + + void (*input_handles[3]) (void*, struct tb_event*) = + { + handle_desktop, + handle_text, + handle_text, + }; + + desktop_load(&desktop); + load(&desktop, &login); + + // start termbox + tb_init(); + tb_select_output_mode(TB_OUTPUT_NORMAL); + tb_clear(); + + // init visible elements + struct tb_event event; + struct term_buf buf; + uint8_t active_input = config.default_input; + + + // init drawing stuff + draw_init(&buf); + + // draw_box and position_input are called because they need to be + // called before *input_handles[active_input] for the cursor to be + // positioned correctly + draw_box(&buf); + position_input(&buf, &desktop, &login, &password); + (*input_handles[active_input])(input_structs[active_input], NULL); + + if (config.animate) + { + animate_init(&buf); + + if (dgn_catch()) + { + config.animate = false; + dgn_reset(); + } + } + + // init state info + int error; + bool run = true; + bool update = true; + bool reboot = false; + bool shutdown = false; + uint8_t auth_fails = 0; + + switch_tty(&buf); + + // main loop + while (run) + { + if (update) + { + if (auth_fails < 10) + { + (*input_handles[active_input])(input_structs[active_input], NULL); + tb_clear(); + animate(&buf); + draw_box(&buf); + draw_labels(&buf); + if(!config.hide_f1_commands) + draw_f_commands(); + draw_lock_state(&buf); + position_input(&buf, &desktop, &login, &password); + draw_desktop(&desktop); + draw_input(&login); + draw_input_mask(&password); + update = config.animate; + } + else + { + usleep(10000); + update = cascade(&buf, &auth_fails); + } + + tb_present(); + } + + if (config.animate) { + error = tb_peek_event(&event, config.min_refresh_delta); + } else { + error = tb_poll_event(&event); + } + + if (error < 0) + { + continue; + } + + if (event.type == TB_EVENT_KEY) + { + switch (event.key) + { + case TB_KEY_F1: + shutdown = true; + run = false; + break; + case TB_KEY_F2: + reboot = true; + run = false; + break; + case TB_KEY_CTRL_C: + run = false; + break; + case TB_KEY_CTRL_U: + if (active_input > 0) + { + input_text_clear(input_structs[active_input]); + update = true; + } + break; + case TB_KEY_ARROW_UP: + if (active_input > 0) + { + --active_input; + update = true; + } + break; + case TB_KEY_ARROW_DOWN: + if (active_input < 2) + { + ++active_input; + update = true; + } + break; + case TB_KEY_TAB: + ++active_input; + + if (active_input > 2) + { + active_input = PASSWORD_INPUT; + } + update = true; + break; + case TB_KEY_ENTER: + save(&desktop, &login); + auth(&desktop, &login, &password, &buf); + update = true; + + if (dgn_catch()) + { + ++auth_fails; + // move focus back to password input + active_input = PASSWORD_INPUT; + + if (dgn_output_code() != DGN_PAM) + { + buf.info_line = dgn_output_log(); + } + + if (config.blank_password) + { + input_text_clear(&password); + } + + dgn_reset(); + } + else + { + buf.info_line = lang.logout; + } + + load(&desktop, &login); + system("tput cnorm"); + break; + default: + (*input_handles[active_input])( + input_structs[active_input], + &event); + update = true; + break; + } + } + } + + // stop termbox + tb_shutdown(); + + // free inputs + input_desktop_free(&desktop); + input_text_free(&login); + input_text_free(&password); + free_hostname(); + + // unload config + draw_free(&buf); + lang_free(); + + if (shutdown) + { + execl("/bin/sh", "sh", "-c", config.shutdown_cmd, NULL); + } + + if (reboot) + { + execl("/bin/sh", "sh", "-c", config.restart_cmd, NULL); + } + + config_free(); + + return 0; +} diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..b7bfabc --- /dev/null +++ b/src/utils.c @@ -0,0 +1,276 @@ +#include "configator.h" +#include "dragonfail.h" +#include "inputs.h" +#include "config.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__DragonFly__) || defined(__FreeBSD__) + #include +#else // linux + #include +#endif + +void desktop_crawl( + struct desktop* target, + char* sessions, + enum display_server server) +{ + DIR* dir; + struct dirent* dir_info; + int ok; + + ok = access(sessions, F_OK); + + if (ok == -1) + { + dgn_throw(DGN_XSESSIONS_DIR); + return; + } + + dir = opendir(sessions); + + if (dir == NULL) + { + dgn_throw(DGN_XSESSIONS_OPEN); + return; + } + + char* name = NULL; + char* exec = NULL; + + struct configator_param map_desktop[] = + { + {"Exec", &exec, config_handle_str}, + {"Name", &name, config_handle_str}, + }; + + struct configator_param* map[] = + { + NULL, + map_desktop, + }; + + struct configator_param sections[] = + { + {"Desktop Entry", NULL, NULL}, + }; + + uint16_t map_len[] = {0, 2}; + uint16_t sections_len = 1; + + struct configator desktop_config; + desktop_config.map = map; + desktop_config.map_len = map_len; + desktop_config.sections = sections; + desktop_config.sections_len = sections_len; + +#if defined(NAME_MAX) + char path[NAME_MAX]; +#elif defined(_POSIX_PATH_MAX) + char path[_POSIX_PATH_MAX]; +#else + char path[1024]; +#endif + + dir_info = readdir(dir); + + while (dir_info != NULL) + { + if ((dir_info->d_name)[0] == '.') + { + dir_info = readdir(dir); + continue; + } + + snprintf(path, (sizeof (path)) - 1, "%s/", sessions); + strncat(path, dir_info->d_name, (sizeof (path)) - 1); + configator(&desktop_config, path); + + // if these are wayland sessions, add " (Wayland)" to their names, + // as long as their names don't already contain that string + if (server == DS_WAYLAND && config.wayland_specifier) + { + const char wayland_specifier[] = " (Wayland)"; + if (strstr(name, wayland_specifier) == NULL) + { + name = realloc(name, (strlen(name) + sizeof(wayland_specifier) + 1)); + // using strcat is safe because the string is constant + strcat(name, wayland_specifier); + } + } + + if ((name != NULL) && (exec != NULL)) + { + input_desktop_add(target, name, exec, server); + } + + name = NULL; + exec = NULL; + dir_info = readdir(dir); + } + + closedir(dir); +} + +void desktop_load(struct desktop* target) +{ + // we don't care about desktop environments presence + // because the fallback shell is always available + // so we just dismiss any "throw" for now + int err = 0; + + desktop_crawl(target, config.waylandsessions, DS_WAYLAND); + + if (dgn_catch()) + { + ++err; + dgn_reset(); + } + + desktop_crawl(target, config.xsessions, DS_XORG); + + if (dgn_catch()) + { + ++err; + dgn_reset(); + } +} + +static char* hostname_backup = NULL; + +void hostname(char** out) +{ + if (hostname_backup != NULL) + { + *out = hostname_backup; + return; + } + + int maxlen = sysconf(_SC_HOST_NAME_MAX); + + if (maxlen < 0) + { + maxlen = _POSIX_HOST_NAME_MAX; + } + + hostname_backup = malloc(maxlen + 1); + + if (hostname_backup == NULL) + { + dgn_throw(DGN_ALLOC); + return; + } + + if (gethostname(hostname_backup, maxlen) < 0) + { + dgn_throw(DGN_HOSTNAME); + return; + } + + hostname_backup[maxlen] = '\0'; + *out = hostname_backup; +} + +void free_hostname() +{ + free(hostname_backup); +} + +void switch_tty(struct term_buf* buf) +{ + FILE* console = fopen(config.console_dev, "w"); + + if (console == NULL) + { + buf->info_line = lang.err_console_dev; + return; + } + + int fd = fileno(console); + + ioctl(fd, VT_ACTIVATE, config.tty); + ioctl(fd, VT_WAITACTIVE, config.tty); + + fclose(console); +} + +void save(struct desktop* desktop, struct text* login) +{ + if (config.save) + { + FILE* fp = fopen(config.save_file, "wb+"); + + if (fp != NULL) + { + fprintf(fp, "%s\n%d", login->text, desktop->cur); + fclose(fp); + } + } +} + +void load(struct desktop* desktop, struct text* login) +{ + if (!config.load) + { + return; + } + + FILE* fp = fopen(config.save_file, "rb"); + + if (fp == NULL) + { + return; + } + + char* line = malloc(config.max_login_len + 1); + + if (line == NULL) + { + fclose(fp); + return; + } + + if (fgets(line, config.max_login_len + 1, fp)) + { + int len = strlen(line); + strncpy(login->text, line, login->len); + + if (len == 0) + { + login->end = login->text; + } + else + { + login->end = login->text + len - 1; + login->text[len - 1] = '\0'; + } + } + else + { + fclose(fp); + free(line); + return; + } + + if (fgets(line, config.max_login_len + 1, fp)) + { + int saved_cur = abs(atoi(line)); + + if (saved_cur < desktop->len) + { + desktop->cur = saved_cur; + } + } + + fclose(fp); + free(line); +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..7e5e5c8 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,15 @@ +#ifndef H_4y_UTILS +#define H_4y_UTILS + +#include "draw.h" +#include "inputs.h" +#include "config.h" + +void desktop_load(struct desktop* target); +void hostname(char** out); +void free_hostname(); +void switch_tty(struct term_buf* buf); +void save(struct desktop* desktop, struct text* login); +void load(struct desktop* desktop, struct text* login); + +#endif