diff --git a/res/pam.d/ly b/res/pam.d/ly index 1a5fe94..b6f28d2 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/auth.zig b/src/auth.zig index 077fd35..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,16 +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, 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, + 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(current_environment.display_server); - try setXdgEnv(tty_str, current_environment.xdg_session_desktop orelse "", current_environment.xdg_desktop_names orelse ""); + 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 }; @@ -43,15 +107,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 +123,21 @@ 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); + errdefer _ = interop.pam.pam_end(handle, 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); + // 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, 0); + defer status = interop.pam.pam_close_session(handle, status); 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(); 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); }; @@ -124,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 @@ -156,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 00dde46..370585f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -39,6 +39,16 @@ pub fn signalHandler(i: c_int) callconv(.C) void { 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 { var shutdown = 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_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; @@ -340,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) { @@ -526,9 +550,9 @@ 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 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,204 +580,280 @@ pub fn main() !void { 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(); - update = timeout != -1; + { + const login_text = try allocator.dupeZ(u8, login.text.items); + defer allocator.free(login_text); - if (event_error < 0 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, + 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); }; - 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 {}; + std.process.exit(0); } + _ = std.posix.waitpid(session_pid, 0); - var shared_err = try SharedError.init(); - defer shared_err.deinit(); + session_pid = -1; + } - { - 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); + auth.asyncPamHandle = null; - // Give up control on the TTY - _ = termbox.tb_shutdown(); + 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); + } - session_pid = try std.posix.fork(); - if (session_pid == 0) { + 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; + } + }, + 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]; - auth.authenticate(config, current_environment, login_text, password_text) catch |err| { - shared_err.writeError(err); - std.process.exit(1); + 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, + }; + + 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, }; - std.process.exit(0); + 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 {}; } - _ = std.posix.waitpid(session_pid, 0); - session_pid = -1; - } + var shared_err = try SharedError.init(); + defer shared_err.deinit(); - // Take back control of the TTY - _ = termbox.tb_init(); - _ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_NORMAL); + { + 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); - 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 .{}; + // 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); + } + + session_pid = -1; } - password.clear(); - try info_line.addMessage(lang.logout, config.bg, config.fg); - } + // Take back control of the TTY + _ = termbox.tb_init(); + _ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_NORMAL); - try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, tb_termios); - if (auth_fails < config.auth_fails) _ = termbox.tb_clear(); + 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 .{}; + } - 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 => {}, + password.clear(); + try info_line.addMessage(lang.logout, config.bg, config.fg); } - } - 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; - }, + 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; + }, + } } } }