From 6bf9b928f26af807910797e8f4b429d87c993b7c Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Sun, 26 May 2024 11:06:51 +0200 Subject: [PATCH 1/5] 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); From cc15ab35d06af617433a231981e1bdf7429b08fc Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Sun, 26 May 2024 11:39:18 +0200 Subject: [PATCH 2/5] Add fprintd to the pam file --- res/pam.d/ly | 2 ++ src/main.zig | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/res/pam.d/ly b/res/pam.d/ly index 1a5fe94..d99690f 100644 --- a/res/pam.d/ly +++ b/res/pam.d/ly @@ -1,5 +1,7 @@ #%PAM-1.0 +auth sufficient pam_unix-so try_first_pass likeauth nullok # empty-password will not pass this, but will not fail causing the next line to get executed +-auth sufficient pam_fprintd.so # We do not want to get errors when pam_fprintd.so is not present auth include login -auth optional pam_gnome_keyring.so -auth optional pam_kwallet5.so diff --git a/src/main.zig b/src/main.zig index f8c9262..1cb8b5f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -602,8 +602,8 @@ pub fn main() !void { asyncPamHandle = null; try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, tb_termios); - termbox.tb_clear(); - termbox.tb_present(); + _ = termbox.tb_clear(); + _ = termbox.tb_present(); std.time.sleep(1000000000); } From 2db48c50c958688913f9690ef34faa97ee3f10e7 Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Wed, 17 Jul 2024 20:26:27 +0200 Subject: [PATCH 3/5] Fix fast blinking cursor when no timeout is set --- src/main.zig | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/main.zig b/src/main.zig index 1cb8b5f..b15a956 100644 --- a/src/main.zig +++ b/src/main.zig @@ -556,7 +556,7 @@ pub fn main() !void { _ = termbox.tb_present(); } - var timeout: i32 = 100; + var timeout: i32 = -1; // 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) { @@ -586,28 +586,32 @@ pub fn main() !void { timeout = @intCast(1000 - @divTrunc(tv.tv_usec, 1000) + 1); } - timeout = @min(timeout, 100); + const timeoutEnd: i64 = if (timeout == -1) std.math.maxInt(i64) else std.time.milliTimestamp() + timeout; - const event_error = if (timeout == -1) termbox.tb_poll_event(&event) else termbox.tb_peek_event(&event, timeout); + const maxtimeout = 100; + var skipEvent: bool = false; + while (timeoutEnd - std.time.milliTimestamp() > 0) { + const event_error = termbox.tb_peek_event(&event, @intCast(@min(timeoutEnd - std.time.milliTimestamp(), maxtimeout))); - 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); + 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(); } - - asyncPamHandle = null; - try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, tb_termios); - _ = termbox.tb_clear(); - _ = termbox.tb_present(); - std.time.sleep(1000000000); + skipEvent = event_error < 0; + if ((event_error < 0 and event_error != -6) or event.type == termbox.TB_EVENT_KEY) break; } - - if (event_error < 0 or event.type != termbox.TB_EVENT_KEY) continue; + update = timeout != -1; + if (skipEvent or event.type != termbox.TB_EVENT_KEY) continue; switch (event.key) { termbox.TB_KEY_ESC => { From c42cf125d3c50573061c1bccd5151953d4f92d3a Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Thu, 18 Jul 2024 15:59:23 +0200 Subject: [PATCH 4/5] Fix processing wrt fingerprint login This also involves a major split of the authentication code, which allows the fingerprint and password logins to use the same code. --- src/auth.zig | 130 +++++++++------------------------------------------ src/main.zig | 66 ++++++++++++++++++++------ 2 files changed, 72 insertions(+), 124 deletions(-) diff --git a/src/auth.zig b/src/auth.zig index ab3cf01..ed25c33 100644 --- a/src/auth.zig +++ b/src/auth.zig @@ -23,17 +23,10 @@ pub fn sessionSignalHandler(i: c_int) callconv(.C) void { if (child_pid > 0) _ = std.c.kill(child_pid, i); } -pub fn authenticate(config: Config, current_environment: Session.Environment, login: [:0]const u8, password: [:0]const u8) !void { - var tty_buffer: [3]u8 = undefined; - const tty_str = try std.fmt.bufPrintZ(&tty_buffer, "{d}", .{config.tty}); - +pub fn authenticate(config: Config, login: [:0]const u8, password: [:0]const u8) !*interop.pam.pam_handle { var pam_tty_buffer: [6]u8 = undefined; const pam_tty_str = try std.fmt.bufPrintZ(&pam_tty_buffer, "tty{d}", .{config.tty}); - // Set the XDG environment variables - setXdgSessionEnv(current_environment.display_server); - try setXdgEnv(tty_str, current_environment.xdg_session_desktop orelse "", current_environment.xdg_desktop_names orelse ""); - // Open the PAM session var credentials = [_:null]?[*:0]const u8{ login, password }; @@ -43,15 +36,15 @@ pub fn authenticate(config: Config, current_environment: Session.Environment, lo }; var handle: ?*interop.pam.pam_handle = undefined; + // Do the PAM routine var status = interop.pam.pam_start(config.service_name, null, &conv, &handle); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); - defer _ = interop.pam.pam_end(handle, status); + errdefer interop.pam.pam_end(handle, status); // Set PAM_TTY as the current TTY. This is required in case it isn't being set by another PAM module status = interop.pam.pam_set_item(handle, interop.pam.PAM_TTY, pam_tty_str.ptr); 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); @@ -59,12 +52,27 @@ pub fn authenticate(config: Config, current_environment: Session.Environment, lo if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED); + defer { + if (status != interop.pam.PAM_SUCCESS) _ = interop.pam.pam_end(handle, status); + } + if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); + return handle.?; +} + +pub fn finaliseAuth(config: Config, current_environment: Session.Environment, 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}); + + // Close the PAM session + var status = interop.pam.pam_open_session(handle, 0); + defer status = interop.pam.pam_close_session(handle, status); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); defer status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED); + defer status = interop.pam.pam_end(handle, status); - status = interop.pam.pam_open_session(handle, 0); - if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); - defer status = interop.pam.pam_close_session(handle, 0); + // Set the XDG environment variables + setXdgSessionEnv(current_environment.display_server); + try setXdgEnv(tty_str, current_environment.xdg_session_desktop orelse "", current_environment.xdg_desktop_names orelse ""); var pwd: *interop.pwd.passwd = undefined; { @@ -120,102 +128,6 @@ 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 b15a956..d361854 100644 --- a/src/main.zig +++ b/src/main.zig @@ -45,7 +45,7 @@ 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); + const pamHandle = try auth.authenticate(config, login, ""); if (currentLogin != null and !std.mem.eql(u8, std.mem.span(@as([*:0]const u8, currentLogin.?)), login)) { return; } @@ -594,18 +594,49 @@ pub fn main() !void { const event_error = termbox.tb_peek_event(&event, @intCast(@min(timeoutEnd - std.time.milliTimestamp(), maxtimeout))); 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); + var shared_err = try SharedError.init(); + defer shared_err.deinit(); + + { + const login_text = try allocator.dupeZ(u8, login.text.items); + defer allocator.free(login_text); + + InfoLine.clearRendered(allocator, buffer) catch {}; + info_line.draw(buffer); + _ = termbox.tb_present(); + + session_pid = try std.posix.fork(); + if (session_pid == 0) { + const current_environment = session.environments.items[session.current]; + auth.finaliseAuth(config, current_environment, handle, login_text) catch |err| { + shared_err.writeError(err); + std.process.exit(1); + }; + + std.process.exit(0); + } + _ = std.posix.waitpid(session_pid, 0); + + session_pid = -1; } + const auth_err = shared_err.readError(); + if (auth_err) |err| { + try info_line.setText(getAuthErrorMsg(err, lang)); + } else { + try info_line.setText(lang.logout); + } asyncPamHandle = null; + try startFingerPrintLogin(allocator, config, login); + try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, tb_termios); _ = termbox.tb_clear(); _ = termbox.tb_present(); + + update = true; + + var restore_cursor = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", config.term_restore_cursor_cmd }, allocator); + _ = restore_cursor.spawnAndWait() catch .{}; } skipEvent = event_error < 0; if ((event_error < 0 and event_error != -6) or event.type == termbox.TB_EVENT_KEY) break; @@ -721,16 +752,21 @@ pub fn main() !void { _ = termbox.tb_shutdown(); session_pid = try std.posix.fork(); - if (session_pid == 0) { - const current_environment = session.label.list.items[session.label.current]; - auth.authenticate(config, current_environment, login_text, password_text) catch |err| { - shared_err.writeError(err); - std.process.exit(1); - }; - std.process.exit(0); + const current_environment = session.environments.items[session.current]; + if (auth.authenticate(config, login_text, password_text)) |handle| { + if (session_pid == 0) { + auth.finaliseAuth(config, current_environment, handle, login_text) catch |err| { + shared_err.writeError(err); + std.process.exit(1); + }; + + std.process.exit(0); + } + _ = std.posix.waitpid(session_pid, 0); + } else |err| { + shared_err.writeError(err); } - _ = std.posix.waitpid(session_pid, 0); session_pid = -1; } From 4bf4de816eaf371902e64951c123602fb17f0bb7 Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Sat, 24 Aug 2024 12:23:43 +0200 Subject: [PATCH 5/5] Reimplement most of the asynchronous handling --- res/pam.d/ly | 2 +- src/auth.zig | 109 +++++++--- src/main.zig | 549 ++++++++++++++++++++++++++------------------------- 3 files changed, 363 insertions(+), 297 deletions(-) diff --git a/res/pam.d/ly b/res/pam.d/ly index d99690f..b6f28d2 100644 --- a/res/pam.d/ly +++ b/res/pam.d/ly @@ -1,6 +1,6 @@ #%PAM-1.0 -auth sufficient pam_unix-so try_first_pass likeauth nullok # empty-password will not pass this, but will not fail causing the next line to get executed +auth sufficient pam_unix.so try_first_pass likeauth nullok # empty-password will not pass this, but will not fail causing the next line to get executed -auth sufficient pam_fprintd.so # We do not want to get errors when pam_fprintd.so is not present auth include login -auth optional pam_gnome_keyring.so diff --git a/src/auth.zig b/src/auth.zig index ed25c33..da95799 100644 --- a/src/auth.zig +++ b/src/auth.zig @@ -13,6 +13,66 @@ const utmp = interop.utmp; const Utmp = utmp.utmpx; const SharedError = @import("SharedError.zig"); +// When setting the currentLogin you must deallocate the previous currentLogin first +pub var currentLogin: ?[:0]const u8 = null; +pub var asyncPamHandle: ?*interop.pam.pam_handle = null; +pub var current_environment: ?Session.Environment = null; + +fn environment_equals(e0: Session.Environment, e1: Session.Environment) bool { + if (!std.mem.eql(u8, e0.cmd, e1.cmd)) { + return false; + } + if (!std.mem.eql(u8, e0.name, e1.name)) { + return false; + } + if (!std.mem.eql(u8, e0.specifier, e1.specifier)) { + return false; + } + if (!(e0.xdg_desktop_names == null and e1.xdg_desktop_names == null) or + (e0.xdg_desktop_names != null and e1.xdg_desktop_names != null and !std.mem.eql(u8, e0.xdg_desktop_names.?, e1.xdg_desktop_names.?))) + { + return false; + } + if (!(e0.xdg_session_desktop == null and e1.xdg_session_desktop == null) or + (e0.xdg_session_desktop != null and e1.xdg_session_desktop != null and !std.mem.eql(u8, e0.xdg_session_desktop.?, e1.xdg_session_desktop.?))) + { + return false; + } + if (e0.display_server != e1.display_server) { + return false; + } + return true; +} + +pub fn automaticLogin(config: Config, login: [:0]const u8, environment: Session.Environment, wakesem: *std.Thread.Semaphore) !void { + while (asyncPamHandle == null and currentLogin != null and std.mem.eql(u8, currentLogin.?, login)) { + if (authenticate(config, login, "", environment)) |handle| { + if (currentLogin != null and !std.mem.eql(u8, currentLogin.?, login) and environment_equals(current_environment.?, environment)) { + return; + } + asyncPamHandle = handle; + wakesem.post(); + return; + } else |_| {} + } +} + +pub fn startAutomaticLogin(allocator: std.mem.Allocator, config: Config, login: Text, environment: Session.Environment, wakesem: *std.Thread.Semaphore) !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(.{}, automaticLogin, .{ + config, + login_text, + environment, + wakesem, + }); + handle.detach(); +} + var xorg_pid: std.posix.pid_t = 0; pub fn xorgSignalHandler(i: c_int) callconv(.C) void { if (xorg_pid > 0) _ = std.c.kill(xorg_pid, i); @@ -23,9 +83,20 @@ pub fn sessionSignalHandler(i: c_int) callconv(.C) void { if (child_pid > 0) _ = std.c.kill(child_pid, i); } -pub fn authenticate(config: Config, login: [:0]const u8, password: [:0]const u8) !*interop.pam.pam_handle { +pub fn authenticate( + config: Config, + login: [:0]const u8, + password: [:0]const u8, + environment: Session.Environment, +) !*interop.pam.pam_handle { var pam_tty_buffer: [6]u8 = undefined; const pam_tty_str = try std.fmt.bufPrintZ(&pam_tty_buffer, "tty{d}", .{config.tty}); + var tty_buffer: [2]u8 = undefined; + const tty_str = try std.fmt.bufPrintZ(&tty_buffer, "{d}", .{config.tty}); + + // Set the XDG environment variables + setXdgSessionEnv(environment.display_server); + try setXdgEnv(tty_str, environment.xdg_session_desktop orelse "", environment.xdg_desktop_names orelse ""); // Open the PAM session var credentials = [_:null]?[*:0]const u8{ login, password }; @@ -39,7 +110,7 @@ pub fn authenticate(config: Config, login: [:0]const u8, password: [:0]const u8) // Do the PAM routine var status = interop.pam.pam_start(config.service_name, null, &conv, &handle); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); - errdefer interop.pam.pam_end(handle, status); + errdefer _ = interop.pam.pam_end(handle, status); // Set PAM_TTY as the current TTY. This is required in case it isn't being set by another PAM module status = interop.pam.pam_set_item(handle, interop.pam.PAM_TTY, pam_tty_str.ptr); @@ -52,27 +123,21 @@ pub fn authenticate(config: Config, login: [:0]const u8, password: [:0]const u8) if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED); - defer { - if (status != interop.pam.PAM_SUCCESS) _ = interop.pam.pam_end(handle, status); - } + errdefer _ = interop.pam.pam_end(handle, status); + if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); return handle.?; } -pub fn finaliseAuth(config: Config, current_environment: Session.Environment, 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}); - - // Close the PAM session - var status = interop.pam.pam_open_session(handle, 0); - defer status = interop.pam.pam_close_session(handle, status); - if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); - defer status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED); +pub fn finaliseAuth(config: Config, environment: Session.Environment, handle: ?*interop.pam.pam_handle, login: [:0]const u8) !void { + var status: c_int = undefined; defer status = interop.pam.pam_end(handle, status); + defer status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED); - // Set the XDG environment variables - setXdgSessionEnv(current_environment.display_server); - try setXdgEnv(tty_str, current_environment.xdg_session_desktop orelse "", current_environment.xdg_desktop_names orelse ""); + // Open the PAM session + status = interop.pam.pam_open_session(handle, 0); + if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); + defer status = interop.pam.pam_close_session(handle, status); var pwd: *interop.pwd.passwd = undefined; { @@ -94,7 +159,7 @@ pub fn finaliseAuth(config: Config, current_environment: Session.Environment, ha child_pid = try std.posix.fork(); if (child_pid == 0) { - startSession(config, pwd, handle, current_environment) catch |e| { + startSession(config, pwd, handle, environment) catch |e| { shared_err.writeError(e); std.process.exit(1); }; @@ -132,7 +197,7 @@ fn startSession( config: Config, pwd: *interop.pwd.passwd, handle: ?*interop.pam.pam_handle, - current_environment: Session.Environment, + environment: Session.Environment, ) !void { if (builtin.os.tag == .freebsd) { // FreeBSD has initgroups() in unistd @@ -164,13 +229,13 @@ fn startSession( std.posix.chdirZ(pwd.pw_dir.?) catch return error.ChangeDirectoryFailed; // Execute what the user requested - switch (current_environment.display_server) { - .wayland => try executeWaylandCmd(pwd.pw_shell.?, config, current_environment.cmd), + switch (environment.display_server) { + .wayland => try executeWaylandCmd(pwd.pw_shell.?, config, environment.cmd), .shell => try executeShellCmd(pwd.pw_shell.?, config), .xinitrc, .x11 => if (build_options.enable_x11_support) { var vt_buf: [5]u8 = undefined; const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{config.tty}); - try executeX11Cmd(pwd.pw_shell.?, pwd.pw_dir.?, config, current_environment.cmd, vt); + try executeX11Cmd(pwd.pw_shell.?, pwd.pw_dir.?, config, environment.cmd, vt); }, } } diff --git a/src/main.zig b/src/main.zig index d361854..370585f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -39,31 +39,14 @@ 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.authenticate(config, login, ""); - if (currentLogin != null and !std.mem.eql(u8, std.mem.span(@as([*:0]const u8, currentLogin.?)), login)) { - return; +fn eventThreadMain(event: *?termbox.tb_event, event_error: *c_int, timeout: *c_int, wake: *std.Thread.Semaphore, cont: *std.Thread.Semaphore) void { + while (true) { + cont.wait(); + var e: termbox.tb_event = undefined; + event_error.* = termbox.tb_peek_event(&e, timeout.*); + event.* = e; + wake.post(); } - 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 { @@ -305,9 +288,6 @@ 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); @@ -359,7 +339,11 @@ pub fn main() !void { const brightness_up_key = try std.fmt.parseInt(u8, config.brightness_up_key[1..], 10); const brightness_up_len = try utils.strWidth(lang.brightness_up); - var event: termbox.tb_event = undefined; + var event_timeout: c_int = std.math.maxInt(c_int); + var event_error: c_int = undefined; + var event: ?termbox.tb_event = null; + var wake: std.Thread.Semaphore = .{}; + var doneEvent: std.Thread.Semaphore = .{}; var run = true; var update = true; var resolution_changed = false; @@ -370,6 +354,16 @@ pub fn main() !void { try info_line.addMessage(lang.err_console_dev, config.error_bg, config.error_fg); }; + doneEvent.post(); + const thandle = try std.Thread.spawn(.{}, eventThreadMain, .{ &event, &event_error, &event_timeout, &wake, &doneEvent }); + + thandle.detach(); + + { + const current_environment = session.label.list.items[session.label.current]; + try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake); + } + defer allocator.free(auth.currentLogin.?); while (run) { // If there's no input or there's an animation, a resolution change needs to be checked if (!update or config.animation != .none) { @@ -556,7 +550,7 @@ pub fn main() !void { _ = termbox.tb_present(); } - var timeout: i32 = -1; + var timeout: u64 = std.math.maxInt(u64); // 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) { @@ -586,273 +580,280 @@ pub fn main() !void { timeout = @intCast(1000 - @divTrunc(tv.tv_usec, 1000) + 1); } - const timeoutEnd: i64 = if (timeout == -1) std.math.maxInt(i64) else std.time.milliTimestamp() + timeout; - - const maxtimeout = 100; var skipEvent: bool = false; - while (timeoutEnd - std.time.milliTimestamp() > 0) { - const event_error = termbox.tb_peek_event(&event, @intCast(@min(timeoutEnd - std.time.milliTimestamp(), maxtimeout))); + _ = wake.timedWait(timeout) catch .{}; + if (auth.asyncPamHandle) |handle| { + var shared_err = try SharedError.init(); + defer shared_err.deinit(); - if (asyncPamHandle) |handle| { - var shared_err = try SharedError.init(); - defer shared_err.deinit(); + { + const login_text = try allocator.dupeZ(u8, login.text.items); + defer allocator.free(login_text); - { - const login_text = try allocator.dupeZ(u8, login.text.items); - defer allocator.free(login_text); - - InfoLine.clearRendered(allocator, buffer) catch {}; - info_line.draw(buffer); - _ = termbox.tb_present(); - - session_pid = try std.posix.fork(); - if (session_pid == 0) { - const current_environment = session.environments.items[session.current]; - auth.finaliseAuth(config, current_environment, handle, login_text) catch |err| { - shared_err.writeError(err); - std.process.exit(1); - }; - - std.process.exit(0); - } - _ = std.posix.waitpid(session_pid, 0); - - session_pid = -1; - } - - const auth_err = shared_err.readError(); - if (auth_err) |err| { - try info_line.setText(getAuthErrorMsg(err, lang)); - } else { - try info_line.setText(lang.logout); - } - asyncPamHandle = null; - try startFingerPrintLogin(allocator, config, login); - - try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, tb_termios); - _ = termbox.tb_clear(); - _ = termbox.tb_present(); - - update = true; - - var restore_cursor = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", config.term_restore_cursor_cmd }, allocator); - _ = restore_cursor.spawnAndWait() catch .{}; - } - skipEvent = event_error < 0; - if ((event_error < 0 and event_error != -6) or event.type == termbox.TB_EVENT_KEY) break; - } - update = timeout != -1; - if (skipEvent or event.type != termbox.TB_EVENT_KEY) continue; - - switch (event.key) { - termbox.TB_KEY_ESC => { - if (config.vi_mode and insert_mode) { - insert_mode = false; - update = true; - } - }, - termbox.TB_KEY_F12...termbox.TB_KEY_F1 => { - const pressed_key = 0xFFFF - event.key + 1; - if (pressed_key == shutdown_key) { - shutdown = true; - run = false; - } else if (pressed_key == restart_key) { - restart = true; - run = false; - } else if (pressed_key == sleep_key) { - if (config.sleep_cmd) |sleep_cmd| { - var sleep = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", sleep_cmd }, allocator); - _ = sleep.spawnAndWait() catch .{}; - } - } else if (pressed_key == brightness_down_key) { - var brightness = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", config.brightness_down_cmd }, allocator); - _ = brightness.spawnAndWait() catch .{}; - } else if (pressed_key == brightness_up_key) { - var brightness = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", config.brightness_up_cmd }, allocator); - _ = brightness.spawnAndWait() catch .{}; - } - }, - termbox.TB_KEY_CTRL_C => run = false, - termbox.TB_KEY_CTRL_U => { - if (active_input == .login) { - login.clear(); - update = true; - } else if (active_input == .password) { - password.clear(); - update = true; - } - }, - termbox.TB_KEY_CTRL_K, termbox.TB_KEY_ARROW_UP => { - active_input = switch (active_input) { - .session, .info_line => .info_line, - .login => .session, - .password => .login, - }; - update = true; - }, - termbox.TB_KEY_CTRL_J, termbox.TB_KEY_ARROW_DOWN => { - active_input = switch (active_input) { - .info_line => .session, - .session => .login, - .login, .password => .password, - }; - update = true; - }, - termbox.TB_KEY_TAB => { - active_input = switch (active_input) { - .info_line => .session, - .session => .login, - .login => .password, - .password => .info_line, - }; - update = true; - }, - termbox.TB_KEY_BACK_TAB => { - active_input = switch (active_input) { - .info_line => .password, - .session => .info_line, - .login => .session, - .password => .login, - }; - - update = true; - }, - termbox.TB_KEY_ENTER => { - try info_line.addMessage(lang.authenticating, config.bg, config.fg); + try info_line.addMessage(lang.authenticating, config.error_bg, config.error_fg); InfoLine.clearRendered(allocator, buffer) catch { try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); }; info_line.label.draw(); _ = termbox.tb_present(); - if (config.save) save_last_settings: { - var file = std.fs.cwd().createFile(save_path, .{}) catch break :save_last_settings; - defer file.close(); - - const save_data = Save{ - .user = login.text.items, - .session_index = session.label.current, - }; - ini.writeFromStruct(save_data, file.writer(), null, true, .{}) catch break :save_last_settings; - - // Delete previous save file if it exists - if (migrator.maybe_save_file) |path| std.fs.cwd().deleteFile(path) catch {}; - } - - var shared_err = try SharedError.init(); - defer shared_err.deinit(); - - { - const login_text = try allocator.dupeZ(u8, login.text.items); - defer allocator.free(login_text); - const password_text = try allocator.dupeZ(u8, password.text.items); - defer allocator.free(password_text); - - // Give up control on the TTY - _ = termbox.tb_shutdown(); - - session_pid = try std.posix.fork(); - const current_environment = session.environments.items[session.current]; - if (auth.authenticate(config, login_text, password_text)) |handle| { - if (session_pid == 0) { - auth.finaliseAuth(config, current_environment, handle, login_text) catch |err| { - shared_err.writeError(err); - std.process.exit(1); - }; - - std.process.exit(0); - } - _ = std.posix.waitpid(session_pid, 0); - } else |err| { + session_pid = try std.posix.fork(); + if (session_pid == 0) { + const current_environment = session.label.list.items[session.label.current]; + auth.finaliseAuth(config, current_environment, handle, login_text) catch |err| { shared_err.writeError(err); - } + std.process.exit(1); + }; - session_pid = -1; + std.process.exit(0); } + _ = std.posix.waitpid(session_pid, 0); - // Take back control of the TTY - _ = termbox.tb_init(); - _ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_NORMAL); + session_pid = -1; + } - const auth_err = shared_err.readError(); - if (auth_err) |err| { - auth_fails += 1; - active_input = .password; - try info_line.addMessage(getAuthErrorMsg(err, lang), config.error_bg, config.error_fg); - if (config.clear_password or err != error.PamAuthError) password.clear(); - } else { - if (config.logout_cmd) |logout_cmd| { - var logout_process = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", logout_cmd }, allocator); - _ = logout_process.spawnAndWait() catch .{}; + auth.asyncPamHandle = null; + + const auth_err = shared_err.readError(); + if (auth_err) |err| { + try info_line.addMessage(getAuthErrorMsg(err, lang), config.error_bg, config.error_fg); + // We don't want to start login back in instantly. The user must first edit + // the login/desktop in order to login. Only in case of a failed login (so not a logout) + // should we automatically restart it. + const current_environment = session.label.list.items[session.label.current]; + try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake); + } else { + try info_line.addMessage(lang.logout, config.error_bg, config.error_fg); + } + + try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, tb_termios); + _ = termbox.tb_clear(); + _ = termbox.tb_present(); + + update = true; + + _ = termbox.tb_set_cursor(0, 0); + _ = termbox.tb_present(); + } else if (event) |e| { + defer doneEvent.post(); + update = timeout != -1; + skipEvent = event_error < 0; + if (skipEvent or e.type != termbox.TB_EVENT_KEY) continue; + + switch (e.key) { + termbox.TB_KEY_ESC => { + if (config.vi_mode and insert_mode) { + insert_mode = false; + update = true; } - - password.clear(); - try info_line.addMessage(lang.logout, config.bg, config.fg); - } - - try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, tb_termios); - if (auth_fails < config.auth_fails) _ = termbox.tb_clear(); - - update = true; - - // Restore the cursor - _ = termbox.tb_set_cursor(0, 0); - _ = termbox.tb_present(); - }, - else => { - if (!insert_mode) { - switch (event.ch) { - 'k' => { - active_input = switch (active_input) { - .session, .info_line => .info_line, - .login => .session, - .password => .login, - }; - update = true; - continue; - }, - 'j' => { - active_input = switch (active_input) { - .info_line => .session, - .session => .login, - .login, .password => .password, - }; - update = true; - continue; - }, - 'i' => { - insert_mode = true; - update = true; - continue; - }, - else => {}, + }, + termbox.TB_KEY_F12...termbox.TB_KEY_F1 => { + const pressed_key = 0xFFFF - e.key + 1; + if (pressed_key == shutdown_key) { + shutdown = true; + run = false; + } else if (pressed_key == restart_key) { + restart = true; + run = false; + } else if (pressed_key == sleep_key) { + if (config.sleep_cmd) |sleep_cmd| { + var sleep = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", sleep_cmd }, allocator); + _ = sleep.spawnAndWait() catch .{}; + } + } else if (pressed_key == brightness_down_key) { + var brightness = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", config.brightness_down_cmd }, allocator); + _ = brightness.spawnAndWait() catch .{}; + } else if (pressed_key == brightness_up_key) { + var brightness = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", config.brightness_up_cmd }, allocator); + _ = brightness.spawnAndWait() catch .{}; } - } + }, + termbox.TB_KEY_CTRL_C => run = false, + termbox.TB_KEY_CTRL_U => { + if (active_input == .login) { + login.clear(); + update = true; + } else if (active_input == .password) { + password.clear(); + update = true; + } + }, + termbox.TB_KEY_CTRL_K, termbox.TB_KEY_ARROW_UP => { + active_input = switch (active_input) { + .session, .info_line => .info_line, + .login => .session, + .password => .login, + }; + update = true; + }, + termbox.TB_KEY_CTRL_J, termbox.TB_KEY_ARROW_DOWN => { + if (active_input == .login) { + const current_environment = session.label.list.items[session.label.current]; + try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake); + } + active_input = switch (active_input) { + .info_line => .session, + .session => .login, + .login, .password => .password, + }; + update = true; + }, + termbox.TB_KEY_TAB => { + if (active_input == .login) { + const current_environment = session.label.list.items[session.label.current]; + try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake); + } + active_input = switch (active_input) { + .info_line => .session, + .session => .login, + .login => .password, + .password => .info_line, + }; + update = true; + }, + termbox.TB_KEY_BACK_TAB => { + if (active_input == .info_line) { + const current_environment = session.label.list.items[session.label.current]; + try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake); + } + active_input = switch (active_input) { + .info_line => .password, + .session => .info_line, + .login => .session, + .password => .login, + }; - switch (active_input) { - .info_line => info_line.label.handle(&event, insert_mode), - .session => session.label.handle(&event, insert_mode), - .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, + update = true; + }, + termbox.TB_KEY_ENTER => { + try info_line.addMessage(lang.authenticating, config.bg, config.fg); + InfoLine.clearRendered(allocator, buffer) catch { + try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); + }; + info_line.label.draw(); + _ = termbox.tb_present(); + + if (config.save) save_last_settings: { + var file = std.fs.cwd().createFile(save_path, .{}) catch break :save_last_settings; + defer file.close(); + + const save_data = Save{ + .user = login.text.items, + .session_index = session.label.current, }; + ini.writeFromStruct(save_data, file.writer(), null, true, .{}) catch break :save_last_settings; - if (shouldFingerprint) { - try startFingerPrintLogin(allocator, config, login); + // Delete previous save file if it exists + if (migrator.maybe_save_file) |path| std.fs.cwd().deleteFile(path) catch {}; + } + + var shared_err = try SharedError.init(); + defer shared_err.deinit(); + + { + const login_text = try allocator.dupeZ(u8, login.text.items); + defer allocator.free(login_text); + const password_text = try allocator.dupeZ(u8, password.text.items); + defer allocator.free(password_text); + + // Give up control on the TTY + _ = termbox.tb_shutdown(); + + session_pid = try std.posix.fork(); + const current_environment = session.label.list.items[session.label.current]; + if (auth.authenticate(config, login_text, password_text, current_environment)) |handle| { + if (session_pid == 0) { + auth.finaliseAuth(config, current_environment, handle, login_text) catch |err| { + shared_err.writeError(err); + std.process.exit(1); + }; + + std.process.exit(0); + } + _ = std.posix.waitpid(session_pid, 0); + } else |err| { + shared_err.writeError(err); } - login.handle(&event, insert_mode) catch { + session_pid = -1; + } + + // Take back control of the TTY + _ = termbox.tb_init(); + _ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_NORMAL); + + const auth_err = shared_err.readError(); + if (auth_err) |err| { + auth_fails += 1; + active_input = .password; + try info_line.addMessage(getAuthErrorMsg(err, lang), config.error_bg, config.error_fg); + if (config.clear_password or err != error.PamAuthError) password.clear(); + } else { + if (config.logout_cmd) |logout_cmd| { + var logout_process = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", logout_cmd }, allocator); + _ = logout_process.spawnAndWait() catch .{}; + } + + password.clear(); + try info_line.addMessage(lang.logout, config.bg, config.fg); + } + + try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, tb_termios); + if (auth_fails < config.auth_fails) _ = termbox.tb_clear(); + + update = true; + + // Restore the cursor + _ = termbox.tb_set_cursor(0, 0); + _ = termbox.tb_present(); + }, + else => { + if (!insert_mode) { + switch (e.ch) { + 'k' => { + active_input = switch (active_input) { + .session, .info_line => .info_line, + .login => .session, + .password => .login, + }; + update = true; + continue; + }, + 'j' => { + if (active_input == .login) { + const current_environment = session.label.list.items[session.label.current]; + try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake); + } + active_input = switch (active_input) { + .info_line => .session, + .session => .login, + .login, .password => .password, + }; + update = true; + continue; + }, + 'i' => { + insert_mode = true; + update = true; + continue; + }, + else => {}, + } + } + + 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); - }; - }, - .password => password.handle(&event, insert_mode) catch { - try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); - }, - } - update = true; - }, + }, + .password => password.handle(&event.?, insert_mode) catch { + try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); + }, + } + update = true; + }, + } } } }