5 Commits

Author SHA1 Message Date
Jaap Aarts
4bf4de816e Reimplement most of the asynchronous handling 2024-08-24 12:47:09 +02:00
Jaap Aarts
c42cf125d3 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.
2024-08-20 17:49:49 +02:00
Jaap Aarts
2db48c50c9 Fix fast blinking cursor when no timeout is set 2024-08-20 17:35:45 +02:00
Jaap Aarts
cc15ab35d0 Add fprintd to the pam file 2024-08-20 17:33:14 +02:00
Jaap Aarts
6bf9b928f2 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 <kerigancreighton@gmail.com>
2024-08-20 17:33:12 +02:00
3 changed files with 363 additions and 188 deletions

View File

@@ -1,5 +1,7 @@
#%PAM-1.0 #%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 include login
-auth optional pam_gnome_keyring.so -auth optional pam_gnome_keyring.so
-auth optional pam_kwallet5.so -auth optional pam_kwallet5.so

View File

@@ -13,6 +13,66 @@ const utmp = interop.utmp;
const Utmp = utmp.utmpx; const Utmp = utmp.utmpx;
const SharedError = @import("SharedError.zig"); 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; var xorg_pid: std.posix.pid_t = 0;
pub fn xorgSignalHandler(i: c_int) callconv(.C) void { pub fn xorgSignalHandler(i: c_int) callconv(.C) void {
if (xorg_pid > 0) _ = std.c.kill(xorg_pid, i); if (xorg_pid > 0) _ = std.c.kill(xorg_pid, i);
@@ -23,16 +83,20 @@ pub fn sessionSignalHandler(i: c_int) callconv(.C) void {
if (child_pid > 0) _ = std.c.kill(child_pid, i); 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 { pub fn authenticate(
var tty_buffer: [3]u8 = undefined; config: Config,
const tty_str = try std.fmt.bufPrintZ(&tty_buffer, "{d}", .{config.tty}); login: [:0]const u8,
password: [:0]const u8,
environment: Session.Environment,
) !*interop.pam.pam_handle {
var pam_tty_buffer: [6]u8 = undefined; var pam_tty_buffer: [6]u8 = undefined;
const pam_tty_str = try std.fmt.bufPrintZ(&pam_tty_buffer, "tty{d}", .{config.tty}); 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 // Set the XDG environment variables
setXdgSessionEnv(current_environment.display_server); setXdgSessionEnv(environment.display_server);
try setXdgEnv(tty_str, current_environment.xdg_session_desktop orelse "", current_environment.xdg_desktop_names orelse ""); try setXdgEnv(tty_str, environment.xdg_session_desktop orelse "", environment.xdg_desktop_names orelse "");
// Open the PAM session // Open the PAM session
var credentials = [_:null]?[*:0]const u8{ login, password }; var credentials = [_:null]?[*:0]const u8{ login, password };
@@ -43,15 +107,15 @@ pub fn authenticate(config: Config, current_environment: Session.Environment, lo
}; };
var handle: ?*interop.pam.pam_handle = undefined; var handle: ?*interop.pam.pam_handle = undefined;
// Do the PAM routine
var status = interop.pam.pam_start(config.service_name, null, &conv, &handle); var status = interop.pam.pam_start(config.service_name, null, &conv, &handle);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); 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 // 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); status = interop.pam.pam_set_item(handle, interop.pam.PAM_TTY, pam_tty_str.ptr);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
// Do the PAM routine
status = interop.pam.pam_authenticate(handle, 0); status = interop.pam.pam_authenticate(handle, 0);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
@@ -59,12 +123,21 @@ pub fn authenticate(config: Config, current_environment: Session.Environment, lo
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED); status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED);
errdefer _ = interop.pam.pam_end(handle, status);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
return handle.?;
}
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); defer status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED);
// Open the PAM session
status = interop.pam.pam_open_session(handle, 0); status = interop.pam.pam_open_session(handle, 0);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
defer status = interop.pam.pam_close_session(handle, 0); defer status = interop.pam.pam_close_session(handle, status);
var pwd: *interop.pwd.passwd = undefined; var pwd: *interop.pwd.passwd = undefined;
{ {
@@ -86,7 +159,7 @@ pub fn authenticate(config: Config, current_environment: Session.Environment, lo
child_pid = try std.posix.fork(); child_pid = try std.posix.fork();
if (child_pid == 0) { if (child_pid == 0) {
startSession(config, pwd, handle, current_environment) catch |e| { startSession(config, pwd, handle, environment) catch |e| {
shared_err.writeError(e); shared_err.writeError(e);
std.process.exit(1); std.process.exit(1);
}; };
@@ -124,7 +197,7 @@ fn startSession(
config: Config, config: Config,
pwd: *interop.pwd.passwd, pwd: *interop.pwd.passwd,
handle: ?*interop.pam.pam_handle, handle: ?*interop.pam.pam_handle,
current_environment: Session.Environment, environment: Session.Environment,
) !void { ) !void {
if (builtin.os.tag == .freebsd) { if (builtin.os.tag == .freebsd) {
// FreeBSD has initgroups() in unistd // FreeBSD has initgroups() in unistd
@@ -156,13 +229,13 @@ fn startSession(
std.posix.chdirZ(pwd.pw_dir.?) catch return error.ChangeDirectoryFailed; std.posix.chdirZ(pwd.pw_dir.?) catch return error.ChangeDirectoryFailed;
// Execute what the user requested // Execute what the user requested
switch (current_environment.display_server) { switch (environment.display_server) {
.wayland => try executeWaylandCmd(pwd.pw_shell.?, config, current_environment.cmd), .wayland => try executeWaylandCmd(pwd.pw_shell.?, config, environment.cmd),
.shell => try executeShellCmd(pwd.pw_shell.?, config), .shell => try executeShellCmd(pwd.pw_shell.?, config),
.xinitrc, .x11 => if (build_options.enable_x11_support) { .xinitrc, .x11 => if (build_options.enable_x11_support) {
var vt_buf: [5]u8 = undefined; var vt_buf: [5]u8 = undefined;
const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{config.tty}); 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);
}, },
} }
} }

View File

@@ -39,6 +39,16 @@ pub fn signalHandler(i: c_int) callconv(.C) void {
std.c.exit(i); std.c.exit(i);
} }
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();
}
}
pub fn main() !void { pub fn main() !void {
var shutdown = false; var shutdown = false;
var restart = false; var restart = false;
@@ -329,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_key = try std.fmt.parseInt(u8, config.brightness_up_key[1..], 10);
const brightness_up_len = try utils.strWidth(lang.brightness_up); 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 run = true;
var update = true; var update = true;
var resolution_changed = false; var resolution_changed = false;
@@ -340,6 +354,16 @@ pub fn main() !void {
try info_line.addMessage(lang.err_console_dev, config.error_bg, config.error_fg); 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) { while (run) {
// If there's no input or there's an animation, a resolution change needs to be checked // If there's no input or there's an animation, a resolution change needs to be checked
if (!update or config.animation != .none) { if (!update or config.animation != .none) {
@@ -526,9 +550,9 @@ pub fn main() !void {
_ = termbox.tb_present(); _ = 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 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) { if (animate and !animation_timed_out) {
timeout = config.min_refresh_delta; timeout = config.min_refresh_delta;
@@ -556,13 +580,67 @@ pub fn main() !void {
timeout = @intCast(1000 - @divTrunc(tv.tv_usec, 1000) + 1); timeout = @intCast(1000 - @divTrunc(tv.tv_usec, 1000) + 1);
} }
const event_error = if (timeout == -1) termbox.tb_poll_event(&event) else termbox.tb_peek_event(&event, timeout); var skipEvent: bool = false;
_ = wake.timedWait(timeout) catch .{};
if (auth.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);
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();
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);
};
std.process.exit(0);
}
_ = std.posix.waitpid(session_pid, 0);
session_pid = -1;
}
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; update = timeout != -1;
skipEvent = event_error < 0;
if (skipEvent or e.type != termbox.TB_EVENT_KEY) continue;
if (event_error < 0 or event.type != termbox.TB_EVENT_KEY) continue; switch (e.key) {
switch (event.key) {
termbox.TB_KEY_ESC => { termbox.TB_KEY_ESC => {
if (config.vi_mode and insert_mode) { if (config.vi_mode and insert_mode) {
insert_mode = false; insert_mode = false;
@@ -570,7 +648,7 @@ pub fn main() !void {
} }
}, },
termbox.TB_KEY_F12...termbox.TB_KEY_F1 => { termbox.TB_KEY_F12...termbox.TB_KEY_F1 => {
const pressed_key = 0xFFFF - event.key + 1; const pressed_key = 0xFFFF - e.key + 1;
if (pressed_key == shutdown_key) { if (pressed_key == shutdown_key) {
shutdown = true; shutdown = true;
run = false; run = false;
@@ -609,6 +687,10 @@ pub fn main() !void {
update = true; update = true;
}, },
termbox.TB_KEY_CTRL_J, termbox.TB_KEY_ARROW_DOWN => { 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) { active_input = switch (active_input) {
.info_line => .session, .info_line => .session,
.session => .login, .session => .login,
@@ -617,6 +699,10 @@ pub fn main() !void {
update = true; update = true;
}, },
termbox.TB_KEY_TAB => { 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) { active_input = switch (active_input) {
.info_line => .session, .info_line => .session,
.session => .login, .session => .login,
@@ -626,6 +712,10 @@ pub fn main() !void {
update = true; update = true;
}, },
termbox.TB_KEY_BACK_TAB => { 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) { active_input = switch (active_input) {
.info_line => .password, .info_line => .password,
.session => .info_line, .session => .info_line,
@@ -670,16 +760,21 @@ pub fn main() !void {
_ = termbox.tb_shutdown(); _ = termbox.tb_shutdown();
session_pid = try std.posix.fork(); session_pid = try std.posix.fork();
if (session_pid == 0) {
const current_environment = session.label.list.items[session.label.current]; const current_environment = session.label.list.items[session.label.current];
auth.authenticate(config, current_environment, login_text, password_text) catch |err| { 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); shared_err.writeError(err);
std.process.exit(1); std.process.exit(1);
}; };
std.process.exit(0); std.process.exit(0);
} }
_ = std.posix.waitpid(session_pid, 0); _ = std.posix.waitpid(session_pid, 0);
} else |err| {
shared_err.writeError(err);
}
session_pid = -1; session_pid = -1;
} }
@@ -714,7 +809,7 @@ pub fn main() !void {
}, },
else => { else => {
if (!insert_mode) { if (!insert_mode) {
switch (event.ch) { switch (e.ch) {
'k' => { 'k' => {
active_input = switch (active_input) { active_input = switch (active_input) {
.session, .info_line => .info_line, .session, .info_line => .info_line,
@@ -725,6 +820,10 @@ pub fn main() !void {
continue; continue;
}, },
'j' => { '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) { active_input = switch (active_input) {
.info_line => .session, .info_line => .session,
.session => .login, .session => .login,
@@ -743,12 +842,12 @@ pub fn main() !void {
} }
switch (active_input) { switch (active_input) {
.info_line => info_line.label.handle(&event, insert_mode), .info_line => info_line.label.handle(&event.?, insert_mode),
.session => session.label.handle(&event, insert_mode), .session => session.label.handle(&event.?, insert_mode),
.login => login.handle(&event, insert_mode) catch { .login => login.handle(&event.?, insert_mode) catch {
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
}, },
.password => password.handle(&event, insert_mode) catch { .password => password.handle(&event.?, insert_mode) catch {
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
}, },
} }
@@ -756,6 +855,7 @@ pub fn main() !void {
}, },
} }
} }
}
} }
fn getAuthErrorMsg(err: anyerror, lang: Lang) []const u8 { fn getAuthErrorMsg(err: anyerror, lang: Lang) []const u8 {