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 <jvssxxi@gmail.com>
This commit is contained in:
creations
2025-12-13 15:37:58 -05:00
committed by GitHub
parent feeba5c41b
commit a7a1f42f0a
9 changed files with 135 additions and 22 deletions

View File

@@ -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

View File

@@ -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

30
docs/yubikey.md Normal file
View File

@@ -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.

View File

@@ -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) \

View File

@@ -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",

View File

@@ -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();

View File

@@ -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) {

View File

@@ -5,6 +5,7 @@
#include <errno.h>
#include <limits.h>
#include <pwd.h>
#include <security/pam_appl.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
@@ -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",

View File

@@ -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"