From c03e2e6b87dfb4e1a28d2e22370538e6e6189496 Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Sun, 31 Dec 2023 15:09:49 +0100 Subject: [PATCH] Implement more (untested) authentication code Signed-off-by: AnErrupTion --- src/auth.zig | 257 +++++++++++++++++++++++++++++++++++++++++++++--- src/interop.zig | 21 ++++ src/main.zig | 12 ++- 3 files changed, 277 insertions(+), 13 deletions(-) diff --git a/src/auth.zig b/src/auth.zig index 0c60e3b..4401d29 100644 --- a/src/auth.zig +++ b/src/auth.zig @@ -6,9 +6,20 @@ const Desktop = @import("tui/components/Desktop.zig"); const Text = @import("tui/components/Text.zig"); const Allocator = std.mem.Allocator; -// TODO -pub fn authenticate(allocator: Allocator, tty: u8, buffer: TerminalBuffer, desktop: Desktop, login: Text, password: Text) !void { - _ = buffer; +var login_conv_allocator: Allocator = undefined; + +pub fn authenticate( + allocator: Allocator, + tty: u8, + desktop: Desktop, + login: Text, + password: *Text, + service_name: []const u8, + path: []const u8, + term_reset_cmd: []const u8, + wayland_cmd: []const u8, +) !void { + login_conv_allocator = allocator; const uid = interop.getuid(); @@ -19,7 +30,7 @@ pub fn authenticate(allocator: Allocator, tty: u8, buffer: TerminalBuffer, deskt const uid_str = try std.fmt.bufPrintZ(&uid_buffer, "/run/user/{d}", .{uid}); const current_environment = desktop.environments.items[desktop.current]; - // Add XDG environment variables + // Set the XDG environment variables setXdgSessionEnv(current_environment.display_server); try setXdgEnv(allocator, tty_str, uid_str, current_environment.xdg_name); @@ -29,7 +40,150 @@ pub fn authenticate(allocator: Allocator, tty: u8, buffer: TerminalBuffer, deskt .conv = loginConv, .appdata_ptr = @ptrCast(&credentials), }; - _ = conv; + var handle: ?*interop.pam.pam_handle = undefined; + + var status = interop.pam.pam_start(service_name.ptr, null, &conv, &handle); + defer status = interop.pam.pam_end(handle, status); + + 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, 0); + 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); + + // Clear the password + password.clear(); + + // Get password structure from username + const login_text_z = try allocator.dupeZ(u8, login.text.items); + defer allocator.free(login_text_z); + + const maybe_pwd = interop.getpwnam(login_text_z.ptr); + interop.endpwent(); + + if (maybe_pwd == null) return error.GetPasswordNameFailed; + const pwd = maybe_pwd.?; + + // Set user shell if it hasn't already been set + if (pwd.pw_shell[0] == 0) { + interop.setusershell(); + defer interop.endusershell(); + + const shell = interop.getusershell(); + + if (shell[0] != 0) { + var index: usize = 0; + + while (true) : (index += 1) { + const char = shell[index]; + pwd.pw_shell[index] = char; + + if (char == 0) break; + } + } + } + + // Restore the previous terminal mode + interop.termbox.tb_clear(); + interop.termbox.tb_present(); + interop.termbox.tb_shutdown(); + + const pid = std.c.fork(); + + if (pid == 0) { + // Set the user information + status = interop.initgroups(pwd.pw_name, pwd.pw_gid); + if (status != 0) return error.GroupInitializationFailed; + + status = std.c.setgid(pwd.pw_gid); + if (status != 0) return error.SetUserGidFailed; + + status = std.c.setuid(pwd.pw_uid); + if (status != 0) return error.SetUserUidFailed; + + // Set up the environment (this clears the currently set one) + try initEnv(allocator, pwd, path); + + // Reset the XDG environment variables from before + setXdgSessionEnv(current_environment.display_server); + try setXdgEnv(allocator, tty_str, uid_str, current_environment.xdg_name); + + // Set the PAM variables + const pam_env_vars = interop.pam.pam_getenvlist(handle); + var index: usize = 0; + + while (true) : (index += 1) { + const pam_env_var = pam_env_vars[index]; + if (pam_env_var == null) break; + + _ = interop.putenv(pam_env_var); + } + + // Execute what the user requested + status = interop.chdir(pwd.pw_dir); + if (status != 0) return error.ChangeDirectoryFailed; + + try resetTerminal(allocator, pwd.pw_shell, term_reset_cmd); + + switch (current_environment.display_server) { + .wayland => try executeWaylandCmd(pwd.pw_shell, wayland_cmd, current_environment.cmd), + .shell => executeShellCmd(pwd.pw_shell), + .xinitrc, .x11 => { + // TODO + }, + } + + std.os.exit(0); + } + + // TODO: Add UTMP entry + + // Wait for the session to stop + _ = std.c.waitpid(pid, &status, 0); + // TODO: Remove UTMP entry + + try resetTerminal(allocator, pwd.pw_shell, term_reset_cmd); + + // Re-initialize termbox + _ = interop.termbox.tb_init(); + _ = interop.termbox.tb_select_output_mode(interop.termbox.TB_OUTPUT_NORMAL); + + // TODO: Reload the DE list on log out + + // Close the PAM session + status = interop.pam.pam_close_session(handle, 0); + if (status != 0) return pamDiagnose(status); + + status = interop.pam.pam_setcred(handle, 0); + if (status != 0) return pamDiagnose(status); +} + +fn initEnv(allocator: Allocator, pwd: *interop.passwd, path: []const u8) !void { + const term = interop.getenv("TERM"); + const lang = interop.getenv("LANG"); + + if (term[0] == 0) _ = interop.setenv("TERM", "linux", 1); + if (lang[0] == 0) _ = interop.setenv("LANG", "C", 1); + _ = interop.setenv("HOME", pwd.pw_dir, 1); + _ = interop.setenv("PWD", pwd.pw_dir, 1); + _ = interop.setenv("SHELL", pwd.pw_shell, 1); + _ = interop.setenv("USER", pwd.pw_name, 1); + _ = interop.setenv("LOGNAME", pwd.pw_name, 1); + + const path_z = try allocator.dupeZ(u8, path); + defer allocator.free(path_z); + + const status = interop.setenv("PATH", path_z, 1); + if (status != 0) return error.SetPathFailed; } fn setXdgSessionEnv(display_server: enums.DisplayServer) void { @@ -54,12 +208,91 @@ fn setXdgEnv(allocator: Allocator, tty_str: [:0]u8, uid_str: [:0]u8, desktop_nam fn loginConv( num_msg: c_int, - msg: [*][*]const interop.pam.pam_message, - resp: [*][*]const interop.pam.pam_response, + msg: ?[*]?*const interop.pam.pam_message, + resp: ?*?[*]interop.pam.pam_response, appdata_ptr: ?*anyopaque, -) c_int { - _ = num_msg; - _ = msg; - _ = resp; - _ = appdata_ptr; +) callconv(.C) c_int { + const message_count: u32 = @intCast(num_msg); + const messages = msg.?; + + const response = login_conv_allocator.alloc(interop.pam.pam_response, message_count) catch return interop.pam.PAM_BUF_ERR; + defer login_conv_allocator.free(response); + + var status: c_int = undefined; + + for (0..message_count) |i| set_credentials: { + switch (messages[i].?.msg_style) { + // TODO: Potentially cast appdata pointer before so we only do it once + // TODO: Verify if we need to do string duplication here + interop.pam.PAM_PROMPT_ECHO_ON => { + const appdata: ?*align(8) anyopaque = @alignCast(appdata_ptr); + const data: [*][:0]u8 = @ptrCast(appdata.?); + const username = data[0]; + + response[i].resp = username; + }, + interop.pam.PAM_PROMPT_ECHO_OFF => { + const appdata: ?*align(8) anyopaque = @alignCast(appdata_ptr); + const data: [*][:0]u8 = @ptrCast(appdata.?); + const password = data[1]; + + response[i].resp = password; + }, + interop.pam.PAM_ERROR_MSG => { + status = interop.pam.PAM_CONV_ERR; + break :set_credentials; + }, + else => {}, + } + } + + if (status == interop.pam.PAM_SUCCESS) resp.?.* = response.ptr; + + return status; +} + +fn resetTerminal(allocator: Allocator, shell: [*:0]const u8, term_reset_cmd: []const u8) !void { + const term_reset_cmd_z = try allocator.dupeZ(u8, term_reset_cmd); + defer allocator.free(term_reset_cmd_z); + + const pid = std.c.fork(); + + if (pid == 0) { + _ = interop.execl(shell, shell, "-c\x00".ptr, term_reset_cmd_z.ptr, @as([*c]const u8, 0)); + std.os.exit(0); + } + + var status: c_int = undefined; + _ = std.c.waitpid(pid, &status, 0); +} + +fn executeWaylandCmd(shell: [*:0]const u8, wayland_cmd: []const u8, desktop_cmd: []const u8) !void { + var cmd_buffer = std.mem.zeroes([1024]u8); + + const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s}", .{ wayland_cmd, desktop_cmd }); + _ = interop.execl(shell, shell, "-c", cmd_str.ptr, @as([*c]const u8, 0)); +} + +fn executeShellCmd(shell: [*:0]const u8) void { + _ = interop.execl(shell, shell, @as([*c]const u8, 0)); +} + +fn pamDiagnose(status: c_int) anyerror { + return switch (status) { + interop.pam.PAM_ACCT_EXPIRED => return error.PamAccountExpired, + interop.pam.PAM_AUTH_ERR => return error.PamAuthError, + interop.pam.PAM_AUTHINFO_UNAVAIL => return error.PamAuthInfoUnavailable, + interop.pam.PAM_BUF_ERR => return error.PamBufferError, + interop.pam.PAM_CRED_ERR => return error.PamCredentialsError, + interop.pam.PAM_CRED_EXPIRED => return error.PamCredentialsExpired, + interop.pam.PAM_CRED_INSUFFICIENT => return error.PamCredentialsInsufficient, + interop.pam.PAM_CRED_UNAVAIL => return error.PamCredentialsUnavailable, + interop.pam.PAM_MAXTRIES => return error.PamMaximumTries, + interop.pam.PAM_NEW_AUTHTOK_REQD => return error.PamNewAuthTokenRequired, + interop.pam.PAM_PERM_DENIED => return error.PamPermissionDenied, + interop.pam.PAM_SESSION_ERR => return error.PamSessionError, + interop.pam.PAM_SYSTEM_ERR => return error.PamSystemError, + interop.pam.PAM_USER_UNKNOWN => return error.PamUserUnknown, + else => return error.PamAbort, + }; } diff --git a/src/interop.zig b/src/interop.zig index 3156794..bfc621a 100644 --- a/src/interop.zig +++ b/src/interop.zig @@ -12,6 +12,7 @@ pub const pam = @cImport({ pub const c_size = u64; pub const c_uid = u32; +pub const c_gid = u32; pub const c_time = c_long; pub const tm = extern struct { tm_sec: c_int, @@ -24,6 +25,16 @@ pub const tm = extern struct { tm_yday: c_int, tm_isdst: c_int, }; +pub const passwd = extern struct { + pw_name: [*:0]u8, + pw_passwd: [*:0]u8, + + pw_uid: c_uid, + pw_gid: c_gid, + pw_gecos: [*:0]u8, + pw_dir: [*:0]u8, + pw_shell: [*:0]u8, +}; pub const _POSIX_HOST_NAME_MAX: c_int = 0xFF; pub const _SC_HOST_NAME_MAX: c_int = 0xB4; @@ -50,7 +61,17 @@ pub extern "c" fn time(second: ?*c_time) c_time; pub extern "c" fn localtime(timer: *const c_time) *tm; pub extern "c" fn strftime(str: [*:0]u8, maxsize: c_size, format: [*:0]const u8, timeptr: *const tm) c_size; pub extern "c" fn setenv(name: [*:0]const u8, value: [*:0]const u8, overwrite: c_int) c_int; +pub extern "c" fn getenv(name: [*:0]const u8) [*:0]u8; +pub extern "c" fn putenv(name: [*:0]u8) c_int; pub extern "c" fn getuid() c_uid; +pub extern "c" fn getpwnam(name: [*:0]const u8) ?*passwd; +pub extern "c" fn endpwent() void; +pub extern "c" fn setusershell() void; +pub extern "c" fn getusershell() [*:0]u8; +pub extern "c" fn endusershell() void; +pub extern "c" fn initgroups(user: [*:0]const u8, group: c_gid) c_int; +pub extern "c" fn chdir(path: [*:0]const u8) c_int; +pub extern "c" fn execl(path: [*:0]const u8, arg: [*:0]const u8, ...) c_int; pub fn getHostName(allocator: Allocator) !struct { buffer: []u8, diff --git a/src/main.zig b/src/main.zig index 8fc7d79..4f7fcb1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -470,7 +470,17 @@ pub fn main() !void { var has_error = false; - auth.authenticate(allocator, config.ly.tty, buffer, desktop, login, password) catch { + auth.authenticate( + allocator, + config.ly.tty, + desktop, + login, + &password, + config.ly.service_name, + config.ly.path, + config.ly.term_reset_cmd, + config.ly.wayland_cmd, + ) catch { has_error = true; auth_fails += 1; active_input = .password;