auth: Check faillock for locked account (closes #898)

Signed-off-by: AnErrupTion <anerruption@disroot.org>
This commit is contained in:
AnErrupTion 2026-08-26 15:24:14 +02:00
commit a6e5f87e24
No known key found for this signature in database
30 changed files with 101 additions and 0 deletions

View file

@ -262,6 +262,13 @@ ly = {
-- Default is red and bold
error_fg = 0x01FF0000,
-- Tally directory for the pam_faillock module (if present)
-- Used for getting if an account is locked or not after too many failed
-- login attempts
-- If directory doesn't exist, lock status won't be checked when
-- authenticating
faillock_tally_dir = "/var/run/faillock",
-- Foreground color id
fg = 0x00FFFFFF,

View file

@ -6,6 +6,7 @@ custom = مخصص
custom_info_err_output_long = الإخراج طويل جداً
custom_info_err_no_output = لا يوجد إخراج
custom_info_err_no_output_error = ، خطأ محتمل
err_alloc = فشل في تخصيص الذاكرة
err_args = تعذر تحليل وسيطات سطر الأوامر
err_autologin_session = لم يتم العثور على جلسة تسجيل الدخول التلقائي

View file

@ -6,6 +6,7 @@ custom = персонализирано
custom_info_err_output_long = резултатът е твърде дълъг
custom_info_err_no_output = няма резултат
custom_info_err_no_output_error = , възможна грешка
err_alloc = неуспешно заделяне на памет
err_args = неуспешен анализ на аргументите от командния ред
err_autologin_session = сесията за автоматично влизане не е намерена

View file

@ -6,6 +6,7 @@ custom = personalitzat
custom_info_err_output_long = sortida massa llarga
custom_info_err_no_output = sense sortida
custom_info_err_no_output_error = , possible error
err_alloc = assignació de memòria fallida
err_args = no s'han pogut analitzar els arguments de la línia d'ordres
err_autologin_session = no s'ha trobat la sessió d'inici de sessió automàtic

View file

@ -6,6 +6,7 @@ custom = vlastní
custom_info_err_output_long = výstup je příliš dlouhý
custom_info_err_no_output = žádný výstup
custom_info_err_no_output_error = , možná chyba
err_alloc = alokace paměti selhala
err_args = nelze analyzovat argumenty příkazového řádku
err_autologin_session = relace automatického přihlášení nebyla nalezena

View file

@ -6,6 +6,7 @@ custom = benutzerdefiniert
custom_info_err_output_long = Ausgabe zu lang
custom_info_err_no_output = keine Ausgabe
custom_info_err_no_output_error = , möglicher Fehler
err_alloc = Speicherzuweisung fehlgeschlagen
err_args = Kommandozeilenargumente konnten nicht verarbeitet werden
err_autologin_session = Autologin-Sitzung nicht gefunden

View file

@ -6,6 +6,7 @@ custom = custom
custom_info_err_output_long = output too long
custom_info_err_no_output = no output
custom_info_err_no_output_error = , possible error
err_acc_locked = account locked, too many attempts
err_alloc = failed memory allocation
err_args = unable to parse command line arguments
err_autologin_session = autologin session not found

View file

@ -6,6 +6,7 @@ custom = propra
custom_info_err_output_long = eligo tro longa
custom_info_err_no_output = neniu eligo
custom_info_err_no_output_error = , ebla eraro
err_alloc = malsukcesis memorasignon
err_args = ne povas analizi argumentojn de komanda linio
err_autologin_session = aŭtomatan ensalutan seancon ne trovis

View file

@ -6,6 +6,7 @@ custom = personalizado
custom_info_err_output_long = salida demasiado larga
custom_info_err_no_output = sin salida
custom_info_err_no_output_error = , posible error
err_alloc = asignación de memoria fallida
err_args = no se pudieron analizar los argumentos de la línea de comandos
err_autologin_session = no se encontró la sesión de inicio de sesión automático

View file

@ -6,6 +6,7 @@ custom = customisé
custom_info_err_output_long = sortie trop longue
custom_info_err_no_output = pas de sortie
custom_info_err_no_output_error = , erreur possible
err_acc_locked = compte bloqué, trop de tentatives
err_alloc = échec d'allocation mémoire
err_args = échec de l'analyse des arguments en lignes de commande
err_autologin_session = session de connexion automatique introuvable

View file

@ -6,6 +6,7 @@ custom = personalizzato
custom_info_err_output_long = output troppo lungo
custom_info_err_no_output = nessun output
custom_info_err_no_output_error = , possibile errore
err_alloc = impossibile allocare memoria
err_args = impossibile analizzare gli argomenti della riga di comando
err_autologin_session = sessione di accesso automatico non trovata

View file

@ -6,6 +6,7 @@ custom = カスタム
custom_info_err_output_long = 出力が長すぎます
custom_info_err_no_output = 出力なし
custom_info_err_no_output_error = 、エラーの可能性あり
err_alloc = メモリ割り当て失敗
err_args = コマンドライン引数を解析できません
err_autologin_session = 自動ログインセッションが見つかりません

View file

@ -6,6 +6,7 @@ custom = kesane
custom_info_err_output_long = encam pir dirêj e
custom_info_err_no_output = encam tune
custom_info_err_no_output_error = , xeletiya mimkun
err_alloc = veqetandina bîrê têk çû
err_args = argumanên rêzika fermanê nehatin analîzkirin
err_autologin_session = danişîna têketina xweber nehate dîtin

View file

@ -6,6 +6,7 @@ custom = pielāgots
custom_info_err_output_long = izvade pārāk gara
custom_info_err_no_output = nav izvades
custom_info_err_no_output_error = , iespējama kļūda
err_alloc = neizdevās atmiņas piešķiršana
err_args = nevar parsēt komandrindas argumentus
err_autologin_session = automātiskās pieteikšanās sesija nav atrasta

View file

@ -6,6 +6,7 @@ custom = własny
custom_info_err_output_long = wyjście zbyt długie
custom_info_err_no_output = brak wyjścia
custom_info_err_no_output_error = , możliwy błąd
err_alloc = nieudana alokacja pamięci
err_args = nie można przetworzyć argumentów wiersza poleceń
err_autologin_session = nie znaleziono sesji autologowania

View file

@ -6,6 +6,7 @@ custom = personalizado
custom_info_err_output_long = saída demasiado longa
custom_info_err_no_output = sem saída
custom_info_err_no_output_error = , possível erro
err_alloc = erro na atribuição de memória
err_args = não foi possível analisar os argumentos da linha de comandos
err_autologin_session = sessão de início de sessão automático não encontrada

View file

@ -6,6 +6,7 @@ custom = personalizado
custom_info_err_output_long = saída muito longa
custom_info_err_no_output = sem saída
custom_info_err_no_output_error = , possível erro
err_alloc = alocação de memória malsucedida
err_args = não foi possível analisar os argumentos da linha de comando
err_autologin_session = sessão de login automático não encontrada

View file

@ -6,6 +6,7 @@ custom = personalizat
custom_info_err_output_long = ieșire prea lungă
custom_info_err_no_output = fără ieșire
custom_info_err_no_output_error = , posibil eroare
err_alloc = alocare de memorie eșuată
err_args = imposibil de analizat argumentele liniei de comandă
err_autologin_session = sesiunea de autentificare automată nu a fost găsită

View file

@ -6,6 +6,7 @@ custom = пользовательский
custom_info_err_output_long = вывод слишком длинный
custom_info_err_no_output = нет вывода
custom_info_err_no_output_error = , возможная ошибка
err_alloc = не удалось выделить память
err_args = не удалось разобрать аргументы командной строки
err_autologin_session = не найдена сессия с автологином

View file

@ -6,6 +6,7 @@ custom = prilagođeno
custom_info_err_output_long = izlaz predugačak
custom_info_err_no_output = nema izlaza
custom_info_err_no_output_error = , moguća greška
err_alloc = neuspješna alokacija memorije
err_args = nije moguće raščlaniti argumente komandne linije
err_autologin_session = sesija automatske prijave nije pronađena

View file

@ -6,6 +6,7 @@ custom = прилагођено
custom_info_err_output_long = излаз предугачак
custom_info_err_no_output = нема излаза
custom_info_err_no_output_error = , могућа грешка
err_alloc = неуспешна алокација меморије
err_args = није могуће рашчланити аргументе командне линије
err_autologin_session = сесија аутоматске пријаве није пронађена

View file

@ -6,6 +6,7 @@ custom = anpassad
custom_info_err_output_long = utdata för lång
custom_info_err_no_output = ingen utdata
custom_info_err_no_output_error = , möjligt fel
err_alloc = minnesallokering misslyckades
err_args = tolkning av kommandoargument misslyckades
err_autologin_session = autologin-session hittades inte

View file

@ -6,6 +6,7 @@ custom = özel
custom_info_err_output_long = çıktı çok uzun
custom_info_err_no_output = çıktı yok
custom_info_err_no_output_error = , olası hata
err_alloc = basarisiz bellek ayirma
err_args = komut satırı argümanları ayrıştırılamıyor
err_autologin_session = otomatik oturum açma oturumu bulunamadı

View file

@ -6,6 +6,7 @@ custom = власний
custom_info_err_output_long = вивід занадто довгий
custom_info_err_no_output = немає виводу
custom_info_err_no_output_error = , можлива помилка
err_alloc = невдале виділення пам'яті
err_args = не вдалося розібрати аргументи командного рядка
err_autologin_session = сеанс автоматичного входу не знайдено

View file

@ -6,6 +6,7 @@ custom = 自定义
custom_info_err_output_long = 输出过长
custom_info_err_no_output = 无输出
custom_info_err_no_output_error = ,可能有错误
err_alloc = 内存分配失败
err_args = 无法解析命令行参数
err_autologin_session = 未找到自动登录会话

View file

@ -6,6 +6,7 @@ custom = 自訂
custom_info_err_output_long = 輸出過長
custom_info_err_no_output = 無輸出
custom_info_err_no_output_error = ,可能有錯誤
err_alloc = 記憶體配置失敗
err_args = 無法解析命令列參數
err_autologin_session = 找不到自動登入工作階段

View file

@ -20,6 +20,7 @@ pub const AuthOptions = struct {
xauth_cmd: []const u8,
setup_cmd: []const u8,
login_cmd: ?[]const u8,
faillock_tally_dir: []const u8,
x_cmd: []const u8,
x_vt: ?u8,
session_pid: std.posix.pid_t,
@ -34,6 +35,18 @@ const PamAppdata = struct {
new_password: []const u8,
};
//https://github.com/linux-pam/linux-pam/blob/master/modules/pam_faillock/faillock.h#L55
const PamFaillockEntry = extern struct {
pub const STATUS_VALID: usize = 0x1;
pub const STATUS_RHOST: usize = 0x2;
pub const STATUS_TTY: usize = 0x4;
source: [52]u8,
reserved: u16,
status: u16,
time: u64,
};
var xorg_pid: std.posix.pid_t = 0;
pub fn xorgSignalHandler(sig: std.posix.SIG) callconv(.c) void {
if (xorg_pid > 0) _ = std.c.kill(xorg_pid, sig);
@ -54,6 +67,11 @@ pub fn authenticate(
password: []const u8,
maybe_new_password: ?[]const u8,
) !void {
var faillock_entries: usize = 0;
if (try dirExists(io, options.faillock_tally_dir)) {
faillock_entries = try getFaillockEntries(allocator, io, login, options.faillock_tally_dir);
}
var tty_buffer: [3]u8 = undefined;
const tty_str = try std.fmt.bufPrint(&tty_buffer, "{d}", .{options.tty});
@ -94,6 +112,13 @@ pub fn authenticate(
// Do the PAM routine
try log_file.info(io, "auth/pam", "authenticating", .{});
status = interop.pam.pam_authenticate(handle, 0);
if (status == interop.pam.PAM_AUTH_ERR and try dirExists(io, options.faillock_tally_dir)) {
const new_faillock_entries = try getFaillockEntries(allocator, io, login, options.faillock_tally_dir);
if (faillock_entries == new_faillock_entries) {
return error.AccountLocked;
}
}
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
try log_file.info(io, "auth/pam", "validating account", .{});
@ -188,6 +213,46 @@ pub fn authenticate(
if (shared_err.readError()) |err| return err;
}
fn dirExists(io: std.Io, path: []const u8) !bool {
var dir = std.Io.Dir.openDirAbsolute(io, path, .{}) catch |err| {
if (err == error.FileNotFound) return false;
return err;
};
defer dir.close(io);
return true;
}
fn getFaillockEntries(
allocator: std.mem.Allocator,
io: std.Io,
username: []const u8,
tally_dir: []const u8,
) !usize {
const path = try std.fs.path.join(allocator, &.{ tally_dir, username });
defer allocator.free(path);
var file = try std.Io.Dir.openFileAbsolute(io, path, .{});
defer file.close(io);
var buffer: [1024]u8 = undefined;
var reader = file.reader(io, &buffer);
var count: usize = 0;
while (!reader.atEnd()) {
const entry = reader.interface.takeStruct(PamFaillockEntry, .little) catch |err| {
if (err == error.EndOfStream) break;
return err;
};
if (entry.status & PamFaillockEntry.STATUS_VALID != 0) {
count += 1;
}
}
return count;
}
fn startSession(
log_file: *LogFile,
allocator: std.mem.Allocator,

View file

@ -59,6 +59,7 @@ dur_y_offset: i32 = 0,
edge_margin: u8 = 0,
error_bg: u32 = 0x00000000,
error_fg: u32 = 0x01FF0000,
faillock_tally_dir: []const u8 = "/var/run/faillock",
fg: u32 = 0x00FFFFFF,
full_color: bool = true,
gameoflife_fg: u32 = 0x0000FF00,

View file

@ -11,6 +11,7 @@ custom: []const u8 = "custom",
custom_info_err_output_long: []const u8 = "output too long",
custom_info_err_no_output: []const u8 = "no output",
custom_info_err_no_output_error: []const u8 = ", possible error",
err_acc_locked: []const u8 = "account locked, too many attempts",
err_alloc: []const u8 = "failed memory allocation",
err_args: []const u8 = "unable to parse command line arguments",
err_autologin_session: []const u8 = "autologin session not found",

View file

@ -1614,6 +1614,7 @@ fn authenticate(ptr: *anyopaque) !bool {
.xauth_cmd = state.config.xauth_cmd,
.setup_cmd = state.config.setup_cmd,
.login_cmd = state.config.login_cmd,
.faillock_tally_dir = state.config.faillock_tally_dir,
.x_cmd = state.config.x_cmd,
.x_vt = state.config.x_vt,
.session_pid = session_pid,
@ -2641,6 +2642,7 @@ fn getAuthErrorMsg(err: anyerror, lang: Lang) []const u8 {
error.PamSystemError => lang.err_pam_sys,
error.PamUserUnknown => lang.err_pam_user_unknown,
error.PamAbort => lang.err_pam_abort,
error.AccountLocked => lang.err_acc_locked,
else => @errorName(err),
};
}