getent passwd

This commit is contained in:
Motiejus Jakštys 2022-07-11 11:11:25 +03:00
parent aaf9d2cc67
commit f0777b9e94
6 changed files with 151 additions and 16 deletions

View File

@ -388,15 +388,18 @@ fn pushStr(str: []const u8, buf: []u8, offset: *usize) [*:0]const u8 {
return buf[start .. start + str.len :0].ptr;
}
pub fn writeUser(self: *const DB, user: PackedUser, buf: []u8) error{BufferTooSmall}!CUser {
const shell_reader = ShellReader{
pub fn shellReader(self: *const DB) ShellReader {
return ShellReader{
.index = self.shell_index,
.blob = self.shell_blob,
};
}
pub fn writeUser(self: *const DB, user: PackedUser, buf: []u8) error{BufferTooSmall}!CUser {
const name = user.name();
const gecos = user.gecos();
const home = user.home();
const shell = user.shell(shell_reader);
const shell = user.shell(self.shellReader());
const strlen =
name.len + 1 +
@ -421,7 +424,7 @@ pub fn writeUser(self: *const DB, user: PackedUser, buf: []u8) error{BufferTooSm
};
}
pub fn getUser(self: *const DB, name: []const u8) ?PackedUser {
pub fn getUserByName(self: *const DB, name: []const u8) ?PackedUser {
const idx = bdz.search(self.bdz_username, name);
// bdz may return a hash that's bigger than the number of users
if (idx >= self.header.num_users) return null;
@ -434,18 +437,23 @@ pub fn getUser(self: *const DB, name: []const u8) ?PackedUser {
// get a CUser entry by name.
pub fn getpwnam(self: *const DB, name: []const u8, buf: []u8) error{BufferTooSmall}!?CUser {
const user = self.getUser(name) orelse return null;
const user = self.getUserByName(name) orelse return null;
return try self.writeUser(user, buf);
}
// get a CUser entry by uid.
pub fn getpwuid(self: *const DB, uid: u32, buf: []u8) error{BufferTooSmall}!?CUser {
pub fn getUserByUid(self: *const DB, uid: u32) ?PackedUser {
const idx = bdz.search_u32(self.bdz_uid, uid);
if (idx >= self.header.num_users) return null;
const offset = self.idx_uid2user[idx];
const nbits = PackedUser.alignment_bits;
const user = PackedUser.fromBytes(self.users[offset << nbits ..]).user;
if (uid != user.uid()) return null;
return user;
}
// get a CUser entry by uid.
pub fn getpwuid(self: *const DB, uid: u32, buf: []u8) error{BufferTooSmall}!?CUser {
const user = self.getUserByUid(uid) orelse return null;
return try self.writeUser(user, buf);
}

View File

@ -205,6 +205,18 @@ pub fn shell(self: PackedUser, shell_reader: ShellReader) []const u8 {
return shell_reader.get(self.inner.shell_len_or_idx);
}
// returns a User representation of the PackedUser.
pub fn toUser(self: *const PackedUser, shell_reader: ShellReader) User {
return User{
.uid = self.uid(),
.gid = self.gid(),
.name = self.name(),
.gecos = self.gecos(),
.home = self.home(),
.shell = self.shell(shell_reader),
};
}
pub const max_str_len =
math.maxInt(fieldInfo(Inner, .shell_len_or_idx).field_type) + 1 +
math.maxInt(fieldInfo(Inner, .home_len).field_type) + 1 +

View File

@ -266,13 +266,8 @@ fn setpwent(state: *State) void {
state.getpwent_iterator_mu.lock();
defer state.getpwent_iterator_mu.unlock();
state.getpwent_iterator = PackedUser.iterator(
state.file.db.users,
ShellReader{
.index = state.file.db.shell_index,
.blob = state.file.db.shell_blob,
},
);
const db = state.file.db;
state.getpwent_iterator = PackedUser.iterator(db.users, db.shellReader());
}
export fn _nss_turbo_endpwent() void {
@ -425,7 +420,7 @@ fn initgroups_dyn(
errnop: *c_int,
) c.enum_nss_status {
const db = state.file.db;
const user = db.getUser(mem.sliceTo(user_name, 0)) orelse {
const user = db.getUserByName(mem.sliceTo(user_name, 0)) orelse {
errnop.* = @enumToInt(os.E.NOENT);
return c.NSS_STATUS_NOTFOUND;
};

View File

@ -16,6 +16,7 @@ test "turbonss test suite" {
_ = @import("validate.zig");
// main
_ = @import("turbo-getent.zig");
_ = @import("turbo-unix2db.zig");
_ = @import("turbo-analyze.zig");
}

View File

@ -12,7 +12,6 @@ const Allocator = std.mem.Allocator;
const BoundedArray = std.BoundedArray;
const flags = @import("flags.zig");
const DB = @import("DB.zig");
const File = @import("File.zig");
const PackedUser = @import("PackedUser.zig");

View File

@ -1,9 +1,16 @@
const std = @import("std");
const io = std.io;
const os = std.os;
const mem = std.mem;
const fmt = std.fmt;
const ArrayList = std.ArrayList;
const flags = @import("flags.zig");
const DB = @import("DB.zig");
const File = @import("File.zig");
const PackedUser = @import("PackedUser.zig");
const Mode = enum { group, passwd };
const usage =
\\usage: turbo-getent [OPTION]... group|passwd [key...]
@ -16,6 +23,13 @@ const usage =
\\entries that match the supplied keys will be displayed. If no key is
\\provided, all entries will be displayed.
\\
\\Exit codes:
\\
\\ 0 command completed successfully
\\ 1 invalid arguments or db could not be opened
\\ 2 one or more supplied key could not be found in the database
\\ 3 output error
\\
;
pub fn main() !void {
@ -36,6 +50,7 @@ fn execute(
) u8 {
const myflags = flags.parse(argv, &[_]flags.Flag{
.{ .name = "-h", .kind = .boolean },
.{ .name = "--db", .kind = .arg },
}) catch {
stderr.writeAll(usage) catch {};
return 1;
@ -46,5 +61,110 @@ fn execute(
return 0;
}
if (myflags.args.len == 0) {
stderr.writeAll(usage) catch {};
return 1;
}
const arg0 = mem.span(myflags.args[0]);
const mode: Mode = blk: {
if (mem.eql(u8, arg0, "passwd")) {
break :blk .passwd;
} else if (mem.eql(u8, arg0, "group")) {
break :blk .group;
} else {
stderr.print("bad argument {s}: expected passwd or group\n", .{
myflags.args[0],
}) catch return 3;
return 1;
}
};
const db_file = myflags.argFlag("--db") orelse "/etc/turbonss/db.turbo";
var file = File.open(db_file) catch |err| {
stderr.print(
"ERROR {s}: file '{s}' is corrupted or cannot be read\n",
.{ @errorName(err), db_file },
) catch return 3;
return 1;
};
defer file.close();
return switch (mode) {
.passwd => passwd(stdout, &file.db, myflags.args[1..]),
.group => group(stdout, &file.db, myflags.args[1..]),
};
}
fn passwd(stdout: anytype, db: *const DB, keys: []const [*:0]const u8) u8 {
var some_notfound = false;
if (keys.len == 0)
return passwd_all(stdout, db);
const shell_reader = db.shellReader();
for (keys) |key| {
const keyZ = mem.span(key);
const maybe_packed_user = if (fmt.parseUnsigned(u32, keyZ, 10)) |uid|
db.getUserByUid(uid)
else |_|
db.getUserByName(keyZ);
const packed_user = maybe_packed_user orelse {
some_notfound = true;
continue;
};
const line = packed_user.toUser(shell_reader).toLine();
stdout.writeAll(line.constSlice()) catch return 3;
}
return if (some_notfound) 2 else 0;
}
fn passwd_all(stdout: anytype, db: *const DB) u8 {
var it = PackedUser.iterator(db.users, db.shellReader());
while (it.next()) |packed_user| {
const line = packed_user.toUser(db.shellReader()).toLine();
stdout.writeAll(line.constSlice()) catch return 3;
}
return 0;
}
const testing = std.testing;
test "passwd and passwd_all" {
var tf = try File.TestDB.init(testing.allocator);
defer tf.deinit();
var stdout = ArrayList(u8).init(testing.allocator);
defer stdout.deinit();
var stderr = ArrayList(u8).init(testing.allocator);
defer stderr.deinit();
const args = &[_][*:0]const u8{
"--db",
tf.path,
"passwd",
"root",
"doesnotexist",
"vidmantas",
"0",
"1",
};
const got = execute(stdout.writer(), stderr.writer(), args);
try testing.expectEqual(got, 2);
const want_root = "root:x:0:0::/root:/bin/bash\n";
try testing.expectEqualStrings(stdout.items[0..want_root.len], want_root);
const want_vidmantas = "vidmantas:x:128:128:Vidmantas Kaminskas:/home/vidmantas:/bin/bash\n";
var offset: usize = want_root.len + want_vidmantas.len;
try testing.expectEqualStrings(stdout.items[want_root.len..offset], want_vidmantas);
try testing.expectEqualStrings(stdout.items[offset..], want_root);
}
fn group(stdout: anytype, db: *const DB, keys: []const [*:0]const u8) u8 {
_ = db;
_ = stdout;
_ = keys;
return 0;
}