From 6bf9b928f26af807910797e8f4b429d87c993b7c Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Sun, 26 May 2024 11:06:51 +0200 Subject: [PATCH] Implement finger-print authentication Many thanks to Kerigan for providing the original C code. This commit mainly posts his code to zig. Additional features provided by me are: - automatically checking every 100ms - request new login info for new usernames Co-authored-by: Kerigan --- src/auth.zig | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 67 +++++++++++++++++++++++++++++++++--- 2 files changed, 159 insertions(+), 4 deletions(-) diff --git a/src/auth.zig b/src/auth.zig index 077fd35..ab3cf01 100644 --- a/src/auth.zig +++ b/src/auth.zig @@ -120,6 +120,102 @@ pub fn authenticate(config: Config, current_environment: Session.Environment, lo if (shared_err.readError()) |err| return err; } +pub fn fingerprintAuth(config: Config, login: [:0]const u8) !*interop.pam.pam_handle { + // Open the PAM session + var credentials = [_:null]?[*:0]const u8{ login, "" }; + + const conv = interop.pam.pam_conv{ + .conv = loginConv, + .appdata_ptr = @ptrCast(&credentials), + }; + var handle: ?*interop.pam.pam_handle = undefined; + + var status = interop.pam.pam_start(config.service_name.ptr, null, &conv, &handle); + if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); + + // Do the PAM routine + status = interop.pam.pam_authenticate(handle, 0); + if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); + + status = interop.pam.pam_acct_mgmt(handle, 0); + if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); + + status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED); + if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); + + status = interop.pam.pam_open_session(handle, 0); + if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); + + return handle.?; +} + +pub fn postFingerprintAuth(config: Config, desktop: Desktop, handle: *interop.pam.pam_handle, login: [:0]const u8) !void { + var tty_buffer: [2]u8 = undefined; + const tty_str = try std.fmt.bufPrintZ(&tty_buffer, "{d}", .{config.tty}); + const current_environment = desktop.environments.items[desktop.current]; + + // Set the XDG environment variables + setXdgSessionEnv(current_environment.display_server); + try setXdgEnv(tty_str, current_environment.xdg_session_desktop, current_environment.xdg_desktop_names orelse ""); + + var pwd: *interop.passwd = undefined; + { + defer interop.endpwent(); + + // Get password structure from username + pwd = interop.getpwnam(login.ptr) orelse return error.GetPasswordNameFailed; + } + + // Set user shell if it hasn't already been set + if (pwd.pw_shell[0] == 0) { + interop.setusershell(); + pwd.pw_shell = interop.getusershell(); + interop.endusershell(); + } + + var shared_err = try SharedError.init(); + defer shared_err.deinit(); + + child_pid = try std.posix.fork(); + if (child_pid == 0) { + startSession(config, pwd, handle, current_environment) catch |e| { + shared_err.writeError(e); + std.process.exit(1); + }; + std.process.exit(0); + } + + var entry: Utmp = std.mem.zeroes(Utmp); + addUtmpEntry(&entry, pwd.pw_name, child_pid) catch {}; + + // If we receive SIGTERM, forward it to child_pid + const act = std.posix.Sigaction{ + .handler = .{ .handler = &sessionSignalHandler }, + .mask = std.posix.empty_sigset, + .flags = 0, + }; + try std.posix.sigaction(std.posix.SIG.TERM, &act, null); + + // Wait for the session to stop + _ = std.posix.waitpid(child_pid, 0); + + removeUtmpEntry(&entry); + + try resetTerminal(pwd.pw_shell, config.term_reset_cmd); + + // Close the PAM session + var status = interop.pam.pam_close_session(handle, 0); + if (status != 0) return pamDiagnose(status); + + status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED); + if (status != 0) return pamDiagnose(status); + + status = interop.pam.pam_end(handle, status); + if (status != 0) return pamDiagnose(status); + + if (shared_err.readError()) |err| return err; +} + fn startSession( config: Config, pwd: *interop.pwd.passwd, diff --git a/src/main.zig b/src/main.zig index 00dde46..f8c9262 100644 --- a/src/main.zig +++ b/src/main.zig @@ -39,6 +39,33 @@ pub fn signalHandler(i: c_int) callconv(.C) void { std.c.exit(i); } +// When setting the currentLogin you must deallocate the previous currentLogin first +var currentLogin: ?[:0]const u8 = null; +var asyncPamHandle: ?*interop.pam.pam_handle = null; + +fn fingerprintLogin(config: Config, login: [:0]const u8) !void { + // TODO: loop if there was an error + const pamHandle = try auth.fingerprintAuth(config, login); + if (currentLogin != null and !std.mem.eql(u8, std.mem.span(@as([*:0]const u8, currentLogin.?)), login)) { + return; + } + asyncPamHandle = pamHandle; +} + +fn startFingerPrintLogin(allocator: std.mem.Allocator, config: Config, login: Text) !void { + if (currentLogin) |clogin| { + allocator.free(clogin); + currentLogin = null; + } + const login_text = try allocator.dupeZ(u8, login.text.items); + currentLogin = login_text; + var handle = try std.Thread.spawn(.{}, fingerprintLogin, .{ + config, + login_text, + }); + handle.detach(); +} + pub fn main() !void { var shutdown = false; var restart = false; @@ -278,6 +305,9 @@ pub fn main() !void { } } + try startFingerPrintLogin(allocator, config, login); + defer allocator.free(currentLogin.?); + // Place components on the screen { buffer.drawBoxCenter(!config.hide_borders, config.blank_box); @@ -526,9 +556,9 @@ pub fn main() !void { _ = termbox.tb_present(); } - var timeout: i32 = -1; + var timeout: i32 = 100; - // Calculate the maximum timeout based on current animations, or the (big) clock. If there's none, we wait for the event indefinitely instead + // Calculate the maximum timeout based on current animations, or the (big) clock. If there's none, we wait for 100ms instead if (animate and !animation_timed_out) { timeout = config.min_refresh_delta; @@ -556,9 +586,26 @@ pub fn main() !void { timeout = @intCast(1000 - @divTrunc(tv.tv_usec, 1000) + 1); } + timeout = @min(timeout, 100); + const event_error = if (timeout == -1) termbox.tb_poll_event(&event) else termbox.tb_peek_event(&event, timeout); update = timeout != -1; + if (asyncPamHandle) |handle| { + if (auth.postFingerprintAuth(config, session, handle, currentLogin.?)) |_| { + try info_line.setText(lang.logout); + } else |err| { + active_input = .password; + try info_line.setText(getAuthErrorMsg(err, lang)); + try startFingerPrintLogin(allocator, config, login); + } + + asyncPamHandle = null; + try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, tb_termios); + termbox.tb_clear(); + termbox.tb_present(); + std.time.sleep(1000000000); + } if (event_error < 0 or event.type != termbox.TB_EVENT_KEY) continue; @@ -745,8 +792,20 @@ pub fn main() !void { switch (active_input) { .info_line => info_line.label.handle(&event, insert_mode), .session => session.label.handle(&event, insert_mode), - .login => login.handle(&event, insert_mode) catch { - try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); + .login => { + const shouldFingerprint = switch (event.key) { + termbox.TB_KEY_DELETE => true, + termbox.TB_KEY_BACKSPACE, termbox.TB_KEY_BACKSPACE2 => true, + else => event.ch > 31 and event.ch < 127, + }; + + if (shouldFingerprint) { + try startFingerPrintLogin(allocator, config, login); + } + + login.handle(&event, insert_mode) catch { + try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); + }; }, .password => password.handle(&event, insert_mode) catch { try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);