diff --git a/Makefile b/Makefile index 5e0e561..a9558d2 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ install: lidm uninstall: rm -rf ${DESTDIR}${PREFIX}/bin/lidm ${DESTDIR}/etc/lidm.ini rm -rf ${DESTDIR}/usr/share/man/man{1/lidm.1,5/lidm-config.5}.gz - rm -rf ${DESTDIR}/etc/systemd/system/lidm.service ${DESTDIR}/etc/dinit.d/lidm ${DESTDIR}/etc/runit/sv/lidm + rm -rf ${DESTDIR}/usr/local/lib/systemd/system/lidm.service ${DESTDIR}/etc/dinit.d/lidm ${DESTDIR}/etc/runit/sv/lidm install-service: @if command -v systemctl &> /dev/null; then \ @@ -88,7 +88,7 @@ install-service: fi install-service-systemd: - install -m644 ./assets/services/systemd.service ${DESTDIR}/etc/systemd/system/lidm.service + install -m644 ./assets/services/systemd.service ${DESTDIR}/usr/local/lib/systemd/system/lidm.service @printf '\033[1m%s\033[0m\n\n' " don't forget to run 'systemctl enable lidm'" install-service-dinit: install -m644 ./assets/services/dinit ${DESTDIR}/etc/dinit.d/lidm 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/assets/services/README.md b/assets/services/README.md index bd98bbc..561eb0d 100644 --- a/assets/services/README.md +++ b/assets/services/README.md @@ -13,7 +13,7 @@ The manual steps for installation are: ## Systemd -- Copy `systemd.service` to `/etc/systemd/system/lidm.service` +- Copy `systemd.service` to `/usr/local/lib/systemd/system/lidm.service` (if the directory doesn't exist, create it first) - To enable it you can run `systemctl enable lidm` ## Dinit 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 25dd560..bb7e5f0 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 391c477..6bb1338 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 5ea13d8..e41e84a 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 88506db..2e0dcc3 100644 --- a/src/auth.c +++ b/src/auth.c @@ -1,4 +1,4 @@ -// TODO: handle `fork() == -1`s +// TODO: handle `fork() == -1`// TODO: handle `fork() == -1`s #include #include @@ -240,9 +240,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(); struct pamh_getenv_status env_ret = pamh_get_complete_env(pamh, pw, session.type); diff --git a/src/pam.c b/src/pam.c index bd3edee..21b7568 100644 --- a/src/pam.c +++ b/src/pam.c @@ -9,6 +9,7 @@ #include "macros.h" #include "pam.h" #include "sessions.h" +#include "ui.h" struct envpair { const char* NNULLABLE name; @@ -113,19 +114,45 @@ struct pamh_getenv_status pamh_get_complete_env(pam_handle_t* handle, /////////////// +struct pam_conv_data { + char* password; + void (*display_pam_msg)(const char* msg, int msg_style); +}; + int pam_conversation(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr) { struct pam_response* reply = malloc(sizeof(struct pam_response) * num_msg); if (!reply) { return PAM_BUF_ERR; } + + struct pam_conv_data* conv_data = (struct pam_conv_data*)appdata_ptr; + for (int i = 0; i < num_msg; i++) { reply[i].resp = NULL; reply[i].resp_retcode = 0; - 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 (int j = 0; j < i; j++) + free(reply[j].resp); + free(reply); + return PAM_BUF_ERR; + } + break; + + case PAM_TEXT_INFO: + case PAM_ERROR_MSG: + if (conv_data->display_pam_msg && msg[i]->msg) { + conv_data->display_pam_msg(msg[i]->msg, msg[i]->msg_style); + } + break; + + default: + break; } } *resp = reply; @@ -144,7 +171,9 @@ int pam_conversation(int num_msg, const struct pam_message** msg, } 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"); diff --git a/src/ui.c b/src/ui.c index 0126366..1dda21c 100644 --- a/src/ui.c +++ b/src/ui.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -111,8 +112,8 @@ static char* fmt_time(const char* fmt) { } } -char* trunc_gethostname(const size_t MAXLEN, const char* const ELLIPSIS) { - if (utf8len(ELLIPSIS) > MAXLEN) return NULL; +char* trunc_gethostname(size_t maxlen, const char* const ELLIPSIS) { + if (utf8len(ELLIPSIS) > maxlen) return NULL; size_t alloc_size = HOST_NAME_MAX + strlen(ELLIPSIS) + 1; char* buf = malloc(alloc_size); if (!buf) return NULL; @@ -122,8 +123,8 @@ char* trunc_gethostname(const size_t MAXLEN, const char* const ELLIPSIS) { return NULL; } - if (utf8len(buf) > MAXLEN) { - size_t end = utf8trunc(buf, MAXLEN - utf8len(ELLIPSIS)); + if (utf8len(buf) > maxlen) { + size_t end = utf8trunc(buf, maxlen - utf8len(ELLIPSIS)); strcpy(&buf[end], ELLIPSIS); } return buf; @@ -298,6 +299,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_key == 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_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) { @@ -344,25 +360,40 @@ u_char get_render_pos_offset(struct opts_field* self, u_char maxlen) { return pos - ofield_display_cursor_col(self, maxlen); } -#define HOSTNAME_SIZE (VALUES_COL - VALUES_SEPR - BOX_HMARGIN - 1) void print_head() { - // hostname doesn't just change on runtime - static char* hostname = NULL; - if (!hostname) - hostname = trunc_gethostname(HOSTNAME_SIZE, g_config->strings.ellipsis); - if (!hostname) hostname = "unknown"; + char* fmtd_time = fmt_time(g_config->behavior.timefmt); + size_t len_tm = utf8len(fmtd_time); + + // calculate the space available for the host name + ssize_t hostname_size = BOX_WIDTH - (BOX_HMARGIN * 2) - len_tm - VALUES_SEPR; + if (hostname_size < 0) hostname_size = 0; + + // hostname doesn't just change on runtime, + // but the length of the time string might + static char* NULLABLE hostname = NULL; + static ssize_t hostname_calcd_size; + + // save the truncated hostname and the length it truncated to, + // if said length changes recalculate this (and free previous str) + if (!hostname || hostname_calcd_size != hostname_size) { + if (hostname) free(hostname); + hostname = trunc_gethostname(hostname_size, g_config->strings.ellipsis); + hostname_calcd_size = hostname_size; + } clean_line(box_start, HEAD_ROW); + // put hostname - printf("\x1b[%dG\x1b[%sm%s\x1b[%sm", - box_start.x + VALUES_COL - VALUES_SEPR - (uint)utf8len(hostname), - g_config->colors.e_hostname, hostname, g_config->colors.fg); + if (hostname_size) + printf("\x1b[%dG\x1b[%sm%s\x1b[%sm", box_start.x + 1 + BOX_HMARGIN, + g_config->colors.e_hostname, hostname ? hostname : "unknown", + g_config->colors.fg); // put date - char* fmtd_time = fmt_time(g_config->behavior.timefmt); printf("\x1b[%dG\x1b[%sm%s\x1b[%sm", - box_start.x + BOX_WIDTH - 1 - BOX_HMARGIN - (uint)utf8len(fmtd_time), + box_start.x + BOX_WIDTH - 1 - BOX_HMARGIN - (uint)len_tm, g_config->colors.e_date, fmtd_time, g_config->colors.fg); + free(fmtd_time); } @@ -497,6 +528,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) + @@ -504,19 +537,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) { @@ -524,6 +569,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"