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>
This commit is contained in:
Jaap Aarts 2024-05-26 11:06:51 +02:00
parent 87ceba4de8
commit 6bf9b928f2
2 changed files with 159 additions and 4 deletions

View File

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

View File

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