From a7a1f42f0a248a461ba280d9d3441c2630b3afa1 Mon Sep 17 00:00:00 2001 From: creations Date: Sat, 13 Dec 2025 15:37:58 -0500 Subject: [PATCH] feat: add support for fido yubikeys (#89) * add support for fido keybind * add to themes * fix clang format * Update ui.c * docs: add misc stuff about the yubikey --------- Co-authored-by: javalsai --- README.md | 9 +++--- assets/man/lidm-config.5 | 4 +-- docs/yubikey.md | 30 +++++++++++++++++++ include/config.h | 2 ++ include/keys.h | 2 ++ include/ui.h | 2 ++ src/auth.c | 41 +++++++++++++++++++++---- src/ui.c | 65 +++++++++++++++++++++++++++++++++------- themes/default.ini | 2 ++ 9 files changed, 135 insertions(+), 22 deletions(-) create mode 100644 docs/yubikey.md diff --git a/README.md b/README.md index b941ec3..fc700d3 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,11 @@ kmscon -l --vt /dev/tty7 --font-name "Cascadia Code" -- /usr/bin/lidm ## Features -- Simple as C, you only need a C compiler and standard unix libraries to build this. -- Fully customizable, from strings, including action keys, to colors (I hope you know ansi escape codes) -- Automatically detects xorg and wayland sessions, plus allowing to launch the default user shell (if enabled in config) -- Starts with many init systems (systemd, dinit, runit, openrc and s6). +- 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) +- Init agnostinc (systemd, dinit, runit, openrc and s6). +- Supports [fido yubikeys](./docs/yubikey.md) (via pam_u2f). # Table of Contents diff --git a/assets/man/lidm-config.5 b/assets/man/lidm-config.5 index f5d6e16..8e4668e 100644 --- a/assets/man/lidm-config.5 +++ b/assets/man/lidm-config.5 @@ -69,13 +69,13 @@ Characters for the corners of the box (ctl = corner top left, cbr = corner botto .SS functions All these are of type \fBKEY\fP. .TP -\fBpoweroff, reboot, refresh\fP +\fBpoweroff, reboot, fido, refresh\fP Function key to use for such action. .SS strings Display strings to use for some elements. .TP -\fBf_poweroff, f_reboot, f_refresh\fP +\fBf_poweroff, f_reboot, f_fido, f_refresh\fP Text displayed to name such functions at the bottom of the screen. .TP \fBe_user, e_passwd\fP diff --git a/docs/yubikey.md b/docs/yubikey.md new file mode 100644 index 0000000..205b9ca --- /dev/null +++ b/docs/yubikey.md @@ -0,0 +1,30 @@ +# Yubikeys + +Quick reference explaining how yubikeys work for now. + +# Enable + +Yubikeys are disabled by default, to enable them activate a keybinding for it (`[functions] fido`) in the config file. + +Note that pressing this configured keybinding has no difference from trying to log in with an empty password, there's virtually no difference. + +`pam_u2f` must be configured with a registered key (`pamu2fcfg`). + +# Extra + +All my yubikey knowledge comes from the [pr that implemented this](https://github.com/javalsai/lidm/pull/89), please refer to it for extra details. Contributions to this documentation are welcome (explaining more in detail, potential issues... really anything that improves this). + +Allegedly this pam module configuration should work: + +```pam +#%PAM-1.0 + +auth sufficient pam_u2f.so cue +auth requisite pam_nologin.so +auth include system-local-login +account include system-local-login +session include system-local-login +password include system-local-login +``` + +Also, I recommend giving the [arch wiki](https://wiki.archlinux.org/title/YubiKey) a read anyways. diff --git a/include/config.h b/include/config.h index 2b120a9..f06d3e6 100644 --- a/include/config.h +++ b/include/config.h @@ -92,6 +92,7 @@ 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) BUILD(functions, FUNCTIONS, TABLE_FUNCTIONS); @@ -99,6 +100,7 @@ BUILD(functions, FUNCTIONS, TABLE_FUNCTIONS); #define TABLE_STRINGS(F, name) \ F(char* NNULLABLE, f_poweroff, STRING, "poweroff", name) \ F(char* NNULLABLE, f_reboot, STRING, "reboot", name) \ + F(char* NNULLABLE, f_fido, STRING, "fido", name) \ F(char* NNULLABLE, f_refresh, STRING, "refresh", name) \ F(char* NNULLABLE, e_user, STRING, "user", name) \ F(char* NNULLABLE, e_passwd, STRING, "password", name) \ diff --git a/include/keys.h b/include/keys.h index 8b37764..fa084fb 100644 --- a/include/keys.h +++ b/include/keys.h @@ -32,9 +32,11 @@ enum keys { END, PAGE_UP, PAGE_DOWN, + NONE, }; static const char* const KEY_NAMES[] = { + [NONE] = "NONE", [ESC] = "ESC", [F1] = "F1", [F2] = "F2", diff --git a/include/ui.h b/include/ui.h index d37df95..e9f15f2 100644 --- a/include/ui.h +++ b/include/ui.h @@ -46,6 +46,8 @@ void setup(struct config* config); int load(struct Vector* users, struct Vector* sessions); void print_err(const char* /*msg*/); 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_ffield(); diff --git a/src/auth.c b/src/auth.c index 0545d93..e18c309 100644 --- a/src/auth.c +++ b/src/auth.c @@ -17,6 +17,11 @@ #include "unistd.h" #include "util.h" +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 = @@ -24,13 +29,34 @@ int pam_conversation(int num_msg, const struct pam_message** msg, if (!reply) { return PAM_BUF_ERR; } + + 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; - if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF || - msg[i]->msg_style == PAM_PROMPT_ECHO_ON) { - char* input = (char*)appdata_ptr; - reply[i].resp = strdup(input); + + 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; @@ -54,7 +80,9 @@ void clear_screen() { pam_handle_t* get_pamh(char* user, char* passwd) { pam_handle_t* pamh = NULL; - struct pam_conv pamc = {pam_conversation, (void*)passwd}; + 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"); @@ -192,9 +220,10 @@ bool launch(char* user, char* passwd, struct session session, void (*cb)(void), pam_handle_t* pamh = get_pamh(user, passwd); if (pamh == NULL) { - print_err("error on pam authentication"); + print_pam_msg("authentication failed", PAM_ERROR_MSG); return false; } + clear_pam_msg(); bool* reach_session = shmalloc(sizeof(bool)); if (reach_session == NULL) { diff --git a/src/ui.c b/src/ui.c index 8f8c3ee..7293fb5 100644 --- a/src/ui.c +++ b/src/ui.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -295,6 +296,21 @@ int load(struct Vector* users, struct Vector* sessions) { restore_all(); reboot(RB_POWER_OFF); exit(0); + } else if (g_config->functions.fido != NONE && + ansi_code == g_config->functions.fido) { + bool successful_write = write_launch_state((struct LaunchState){ + .username = st_user().username, + .session_opt = + st_session(g_config->behavior.include_defshell).name, + }); + if (!successful_write) log_puts("[E] failed to write launch state"); + + if (!launch(st_user().username, "", + st_session(g_config->behavior.include_defshell), + &restore_all, g_config)) { + print_passwd(utf8len(of_passwd.efield.content), true); + ui_update_cursor_focus(); + } } 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) { @@ -509,6 +525,8 @@ static void print_box() { } static void print_footer() { + bool fido_enabled = g_config->functions.fido != NONE; + size_t bsize = utf8len(g_config->strings.f_poweroff) + utf8len(KEY_NAMES[g_config->functions.poweroff]) + utf8len(g_config->strings.f_reboot) + @@ -516,19 +534,31 @@ static void print_footer() { utf8len(g_config->strings.f_refresh) + utf8len(KEY_NAMES[g_config->functions.refresh]); - bsize += 2 * 2 + // 2 wide separators between 3 items - 3 * 1; // 3 thin separators inside every item + bsize += 2 * 2 + 3 * 1; + + if (fido_enabled) { + bsize += utf8len(g_config->strings.f_fido) + + utf8len(KEY_NAMES[g_config->functions.fido]) + 2 + 1; + } uint row = window.ws_row - 1; uint col = window.ws_col - 2 - bsize; - printf( - "\x1b[%3$d;%4$dH%8$s \x1b[%1$sm%5$s\x1b[%2$sm %9$s " - "\x1b[%1$sm%6$s\x1b[%2$sm %10$s \x1b[%1$sm%7$s\x1b[%2$sm", - g_config->colors.e_key, g_config->colors.fg, row, col, - KEY_NAMES[g_config->functions.poweroff], - KEY_NAMES[g_config->functions.reboot], - KEY_NAMES[g_config->functions.refresh], g_config->strings.f_poweroff, - g_config->strings.f_reboot, g_config->strings.f_refresh); + + printf("\x1b[%d;%dH%s \x1b[%sm%s\x1b[%sm %s \x1b[%sm%s\x1b[%sm ", row, col, + g_config->strings.f_poweroff, g_config->colors.e_key, + KEY_NAMES[g_config->functions.poweroff], g_config->colors.fg, + g_config->strings.f_reboot, g_config->colors.e_key, + KEY_NAMES[g_config->functions.reboot], g_config->colors.fg); + + if (fido_enabled) { + printf("%s \x1b[%sm%s\x1b[%sm ", g_config->strings.f_fido, + g_config->colors.e_key, KEY_NAMES[g_config->functions.fido], + g_config->colors.fg); + } + + printf("%s \x1b[%sm%s\x1b[%sm", g_config->strings.f_refresh, + g_config->colors.e_key, KEY_NAMES[g_config->functions.refresh], + g_config->colors.fg); } void print_err(const char* msg) { @@ -536,6 +566,21 @@ void print_err(const char* msg) { msg, errno, strerror(errno)); } +void print_pam_msg(const char* msg, int msg_style) { + uint row = box_start.y + BOX_HEIGHT + 1; + const char* color = + (msg_style == PAM_ERROR_MSG) ? g_config->colors.err : g_config->colors.fg; + printf("\x1b[%d;%dH\x1b[K\x1b[%sm%.*s\x1b[%sm", row, box_start.x, color, + BOX_WIDTH, msg, g_config->colors.fg); + (void)fflush(stdout); +} + +void clear_pam_msg(void) { + uint row = box_start.y + BOX_HEIGHT + 1; + printf("\x1b[%d;%dH\x1b[K", row, box_start.x); + (void)fflush(stdout); +} + void print_errno(const char* descr) { if (descr == NULL) (void)fprintf(stderr, "\x1b[%d;%dH\x1b[%smunknown error(%d): %s", diff --git a/themes/default.ini b/themes/default.ini index a0859e6..081c989 100644 --- a/themes/default.ini +++ b/themes/default.ini @@ -25,11 +25,13 @@ [functions] # poweroff = F1 # reboot = F2 +# fido = NONE # refresh = F5 [strings] # f_poweroff = "poweroff" # f_reboot = "reboot" +# f_fido = "fido" # f_refresh = "refresh" # e_user = "user" # e_passwd = "password"