diff --git a/src/config/Config.zig b/src/config/Config.zig index e9169bd..4d2c096 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -93,3 +93,4 @@ x_cmd: []const u8 = build_options.prefix_directory ++ "/bin/X", xauth_cmd: []const u8 = build_options.prefix_directory ++ "/bin/xauth", xinitrc: ?[]const u8 = "~/.xinitrc", xsessions: []const u8 = build_options.prefix_directory ++ "/share/xsessions", +username_whitelist: ?[]const u8 = null, diff --git a/src/main.zig b/src/main.zig index 8565c9a..13b206f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -223,7 +223,7 @@ pub fn main() !void { } var maybe_uid_range_error: ?anyerror = null; - var usernames = try getAllUsernames(allocator, config.login_defs_path, &maybe_uid_range_error); + var usernames = try getAllUsernames(allocator, config.login_defs_path, config.username_whitelist, &maybe_uid_range_error); defer { for (usernames.items) |username| allocator.free(username); usernames.deinit(allocator); @@ -486,7 +486,7 @@ pub fn main() !void { const auto_user = config.auto_login_user orelse break :check_autologin; const auto_session = config.auto_login_session orelse break :check_autologin; - if (!isValidUsername(auto_user, usernames)) { + if (!stringListContains(auto_user, usernames)) { try info_line.addMessage(lang.err_pam_user_unknown, config.error_bg, config.error_fg); try log_writer.print("autologin failed: username '{s}' not found\n", .{auto_user}); break :check_autologin; @@ -1294,9 +1294,9 @@ fn crawl(session: *Session, lang: Lang, path: []const u8, display_server: Displa } } -fn isValidUsername(username: []const u8, usernames: StringList) bool { - for (usernames.items) |valid_username| { - if (std.mem.eql(u8, username, valid_username)) return true; +fn stringListContains(needle: []const u8, list: StringList) bool { + for (list.items) |valid_username| { + if (std.mem.eql(u8, needle, valid_username)) return true; } return false; } @@ -1314,7 +1314,8 @@ fn findSessionByName(session: *Session, name: []const u8) ?usize { return null; } -fn getAllUsernames(allocator: std.mem.Allocator, login_defs_path: []const u8, uid_range_error: *?anyerror) !StringList { +fn getAllUsernames(allocator: std.mem.Allocator, login_defs_path: []const u8, username_whitelist_string: ?[]const u8, uid_range_error: *?anyerror) !StringList { + // Find all valid usernames const uid_range = interop.getUserIdRange(allocator, login_defs_path) catch |err| no_uid_range: { uid_range_error.* = err; break :no_uid_range UidRange{ @@ -1323,13 +1324,30 @@ fn getAllUsernames(allocator: std.mem.Allocator, login_defs_path: []const u8, ui }; }; + var whitelisted_usernames: StringList = .empty; + + if (username_whitelist_string) |s| { + whitelisted_usernames = try parseCSVString(allocator, s); + } + var usernames: StringList = .empty; var maybe_entry = interop.getNextUsernameEntry(); while (maybe_entry) |entry| { - // We check if the UID is equal to 0 because we always want to add root - // as a username (even if you can't log into it) - if (entry.uid >= uid_range.uid_min and entry.uid <= uid_range.uid_max or entry.uid == 0 and entry.username != null) { + var valid: bool = true; + // Check if not in valid UID range and not o + // because we always want to add root as a username (even if you can't log into it) + if ((entry.uid < uid_range.uid_min or entry.uid > uid_range.uid_max) and entry.uid != 0) { + valid = false; + } + + // Check if not whitelist if enabled + if (username_whitelist_string != null and !stringListContains(entry.username.?, whitelisted_usernames)) { + valid = false; + } + + // Valid and not null, add + if (valid and entry.username != null) { const username = try allocator.dupe(u8, entry.username.?); try usernames.append(allocator, username); } @@ -1337,10 +1355,40 @@ fn getAllUsernames(allocator: std.mem.Allocator, login_defs_path: []const u8, ui maybe_entry = interop.getNextUsernameEntry(); } + defer { + // Cleanup whitelisted_usernames + if (username_whitelist_string != null) { + for (whitelisted_usernames.items) |username| allocator.free(username); + whitelisted_usernames.deinit(allocator); + } + } + interop.closePasswordDatabase(); return usernames; } + + +pub fn parseCSVString(allocator: std.mem.Allocator, input: []const u8) !StringList { + var list = StringList{}; + + var start: usize = 0; + while (start < input.len) { + var end = start; + while (end < input.len and input[end] != ',') : (end += 1) {} + + const token = std.mem.trim(u8, input[start..end], " \t\n\r"); + if (token.len > 0) { // Only add non-empty tokens + const duped = try allocator.dupe(u8, token); + try list.append(allocator, duped); + } + + start = end + 1; + } + + return list; +} + fn adjustBrightness(allocator: std.mem.Allocator, cmd: []const u8) !void { var brightness = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", cmd }, allocator); brightness.stdout_behavior = .Ignore;