1
Fork 0
turbonss/src/libnss.zig

458 lines
13 KiB
Zig
Raw Normal View History

2022-04-20 07:19:34 +03:00
const std = @import("std");
const os = std.os;
const fmt = std.fmt;
2022-07-06 13:19:15 +03:00
const log = std.log;
2022-04-20 07:19:34 +03:00
const mem = std.mem;
2022-04-21 09:30:39 +03:00
const process = std.process;
2022-07-06 13:19:15 +03:00
const once = std.once;
const Mutex = std.Thread.Mutex;
2022-04-20 07:19:34 +03:00
const DB = @import("DB.zig");
const File = @import("File.zig");
2022-07-04 06:09:03 +03:00
const ErrCtx = @import("ErrCtx.zig");
2022-04-20 07:19:34 +03:00
const CGroup = @import("Group.zig").CGroup;
const PackedGroup = @import("PackedGroup.zig");
const CUser = @import("User.zig").CUser;
const PackedUser = @import("PackedUser.zig");
const ShellReader = @import("shell.zig").ShellReader;
2022-04-20 07:19:34 +03:00
const c = @cImport({
@cInclude("nss.h");
});
2022-07-06 13:19:15 +03:00
const ENV_DB = "TURBONSS_DB";
2022-07-06 14:06:50 +03:00
const ENV_LOGLEVEL = "TURBONSS_LOGLEVEL";
2022-07-06 13:19:15 +03:00
const ENV_OMIT_MEMBERS = "TURBONSS_OMIT_MEMBERS";
2022-04-21 09:30:39 +03:00
2022-07-06 16:46:24 +03:00
export var turbonss_db_path: [:0]const u8 = "/etc/turbonss/db.turbo";
2022-04-20 07:19:34 +03:00
2022-07-06 13:19:15 +03:00
pub var log_level: std.log.Level = .err;
2022-04-20 07:19:34 +03:00
// State is a type of the global variable holding the process state:
// the DB handle and all the iterators.
const State = struct {
file: File,
omit_members: bool,
getpwent_iterator_mu: Mutex = Mutex{},
2022-07-06 13:19:15 +03:00
getpwent_iterator: ?PackedUser.Iterator = null,
getgrent_iterator_mu: Mutex = Mutex{},
2022-07-06 13:19:15 +03:00
getgrent_iterator: ?PackedGroup.Iterator = null,
2022-04-20 07:19:34 +03:00
};
2022-07-06 13:19:15 +03:00
// global_state is initialized on first call to an nss function
var global_state: ?State = null;
2022-07-06 13:29:36 +03:00
var global_init = once(init);
2022-04-20 07:19:34 +03:00
2022-07-06 13:19:15 +03:00
// assigns State from environment variables et al
fn init() void {
2022-07-06 14:06:50 +03:00
if (os.getenvZ(ENV_LOGLEVEL)) |env| {
const got = mem.sliceTo(env, 0);
if (mem.eql(u8, got, "0")) {
log_level = .err;
} else if (mem.eql(u8, got, "1")) {
log_level = .warn;
} else if (mem.eql(u8, got, "2")) {
log_level = .info;
} else if (mem.eql(u8, got, "3")) {
log_level = .debug;
} else {
std.debug.print(
"warning: unrecognized {s}={s}. Expected between 0 and 3\n",
.{ ENV_LOGLEVEL, got },
);
}
}
2022-04-21 09:30:39 +03:00
2022-07-06 14:06:50 +03:00
const omit_members = blk: {
if (os.getenvZ(ENV_OMIT_MEMBERS)) |env| {
const got = mem.sliceTo(env, 0);
if (mem.eql(u8, got, "1")) {
break :blk true;
} else if (mem.eql(u8, got, "0")) {
break :blk false;
} else if (mem.eql(u8, got, "auto")) {
// not set, do autodiscover
} else {
std.debug.print(
"warning: unrecognized {s}={s}. Expected 0, 1 or auto\n",
.{ ENV_OMIT_MEMBERS, got },
);
}
}
if (os.argv.len == 0) break :blk false;
break :blk mem.eql(u8, mem.sliceTo(os.argv[0], 0), "id");
};
2022-07-06 13:19:15 +03:00
log.debug("omitting members from getgr* calls: {any}\n", .{omit_members});
2022-04-21 09:30:39 +03:00
2022-07-06 16:46:24 +03:00
const fname = os.getenvZ(ENV_DB) orelse turbonss_db_path;
2022-07-06 14:06:50 +03:00
log.debug("opening '{s}'", .{fname});
2022-07-06 13:19:15 +03:00
const file = File.open(fname) catch |err| {
log.warn("open '{s}': {s}", .{ fname, @errorName(err) });
2022-04-20 07:19:34 +03:00
return;
};
2022-07-06 13:19:15 +03:00
log.debug("turbonss database opened", .{});
2022-04-20 07:19:34 +03:00
2022-07-06 13:19:15 +03:00
global_state = State{
.file = file,
.omit_members = omit_members,
};
2022-04-20 07:19:34 +03:00
}
export fn _nss_turbo_getpwuid_r(
uid: c_uint,
2022-07-06 16:29:21 +03:00
passwd: *CUser,
buffer: [*]u8,
2022-07-06 13:53:34 +03:00
buflen: usize,
2022-04-20 07:19:34 +03:00
errnop: *c_int,
) c.enum_nss_status {
2022-07-07 21:20:54 +03:00
const db = getDBErrno(errnop) orelse return c.NSS_STATUS_UNAVAIL;
var cuser = db.getpwuid(uid, buffer[0..buflen]) catch |err| switch (err) {
2022-07-06 16:29:21 +03:00
error.BufferTooSmall => {
errnop.* = @enumToInt(os.E.RANGE);
return c.NSS_STATUS_TRYAGAIN;
},
2022-07-07 21:20:54 +03:00
};
2022-07-08 06:29:42 +03:00
const got_cuser = cuser orelse {
2022-07-07 21:20:54 +03:00
errnop.* = @enumToInt(os.E.NOENT);
return c.NSS_STATUS_NOTFOUND;
2022-07-08 06:29:42 +03:00
};
passwd.* = got_cuser;
return c.NSS_STATUS_SUCCESS;
2022-07-06 16:29:21 +03:00
}
2022-07-06 13:53:34 +03:00
2022-07-06 16:29:21 +03:00
export fn _nss_turbo_getpwnam_r(
2022-07-06 16:54:43 +03:00
name: [*:0]const u8,
2022-07-06 16:29:21 +03:00
passwd: *CUser,
buffer: [*]u8,
buflen: usize,
errnop: *c_int,
) c.enum_nss_status {
2022-07-07 21:20:54 +03:00
const db = getDBErrno(errnop) orelse return c.NSS_STATUS_UNAVAIL;
2022-07-06 16:29:21 +03:00
const nameSlice = mem.sliceTo(name, 0);
2022-07-07 21:20:54 +03:00
var buf = buffer[0..buflen];
const cuser = db.getpwnam(nameSlice, buf) catch |err| switch (err) {
2022-07-06 13:53:34 +03:00
error.BufferTooSmall => {
2022-04-21 09:30:39 +03:00
errnop.* = @enumToInt(os.E.RANGE);
return c.NSS_STATUS_TRYAGAIN;
},
2022-07-07 21:20:54 +03:00
};
2022-07-08 06:29:42 +03:00
const got_cuser = cuser orelse {
2022-07-07 21:20:54 +03:00
errnop.* = @enumToInt(os.E.NOENT);
return c.NSS_STATUS_NOTFOUND;
2022-07-08 06:29:42 +03:00
};
passwd.* = got_cuser;
return c.NSS_STATUS_SUCCESS;
2022-07-06 13:53:34 +03:00
}
2022-07-07 06:54:27 +03:00
export fn _nss_turbo_getgrgid_r(
gid: c_uint,
gr: *CGroup,
buffer: [*]u8,
buflen: usize,
errnop: *c_int,
) c.enum_nss_status {
2022-07-07 21:20:54 +03:00
const db = getDBErrno(errnop) orelse return c.NSS_STATUS_UNAVAIL;
var cgroup = db.getgrgid(gid, buffer[0..buflen]) catch |err| switch (err) {
2022-07-07 06:54:27 +03:00
error.BufferTooSmall => {
errnop.* = @enumToInt(os.E.RANGE);
return c.NSS_STATUS_TRYAGAIN;
},
2022-07-07 21:20:54 +03:00
};
2022-07-08 06:29:42 +03:00
const got_cgroup = cgroup orelse {
2022-07-07 21:20:54 +03:00
errnop.* = @enumToInt(os.E.NOENT);
return c.NSS_STATUS_NOTFOUND;
2022-07-08 06:29:42 +03:00
};
gr.* = got_cgroup;
return c.NSS_STATUS_SUCCESS;
2022-07-07 06:54:27 +03:00
}
export fn _nss_turbo_getgrnam_r(
name: [*:0]const u8,
group: *CGroup,
buffer: [*]u8,
buflen: usize,
errnop: *c_int,
) c.enum_nss_status {
2022-07-07 21:20:54 +03:00
const db = getDBErrno(errnop) orelse return c.NSS_STATUS_UNAVAIL;
2022-07-07 06:54:27 +03:00
const nameSlice = mem.sliceTo(name, 0);
2022-07-07 21:20:54 +03:00
var buf = buffer[0..buflen];
var cgroup = db.getgrnam(nameSlice, buf) catch |err| switch (err) {
2022-07-07 06:54:27 +03:00
error.BufferTooSmall => {
errnop.* = @enumToInt(os.E.RANGE);
return c.NSS_STATUS_TRYAGAIN;
},
2022-07-07 21:20:54 +03:00
};
2022-07-08 06:29:42 +03:00
const got_cgroup = cgroup orelse {
2022-07-07 21:20:54 +03:00
errnop.* = @enumToInt(os.E.NOENT);
return c.NSS_STATUS_NOTFOUND;
2022-07-08 06:29:42 +03:00
};
group.* = got_cgroup;
return c.NSS_STATUS_SUCCESS;
2022-07-07 06:54:27 +03:00
}
export fn _nss_turbo_setpwent(_: c_int) void {
global_init.call();
2022-07-07 21:20:54 +03:00
var state = global_state orelse return;
2022-07-07 21:20:54 +03:00
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,
},
);
}
export fn _nss_turbo_endpwent() void {
global_init.call();
2022-07-08 06:29:42 +03:00
var state = global_state orelse return;
state.getpwent_iterator_mu.lock();
state.getpwent_iterator = null;
state.getpwent_iterator_mu.unlock();
}
export fn _nss_turbo_setgrent(_: c_int) void {
2022-07-07 21:20:54 +03:00
var state = getState() orelse return;
state.getgrent_iterator_mu.lock();
defer state.getgrent_iterator_mu.unlock();
state.getgrent_iterator = PackedGroup.iterator(
state.file.db.groups,
);
}
export fn _nss_turbo_endgrent() void {
var state = getState() orelse return;
2022-07-08 06:29:42 +03:00
2022-07-07 21:20:54 +03:00
state.getgrent_iterator_mu.lock();
state.getgrent_iterator = null;
2022-07-08 06:29:42 +03:00
state.getgrent_iterator_mu.unlock();
2022-07-07 21:20:54 +03:00
}
export fn _nss_turbo_getgrent_r(
result: *CGroup,
buffer: [*]u8,
buflen: usize,
errnop: *c_int,
) c.enum_nss_status {
var state = getStateErrno(errnop) orelse return c.NSS_STATUS_UNAVAIL;
state.getgrent_iterator_mu.lock();
defer state.getgrent_iterator_mu.unlock();
var it = state.getgrent_iterator orelse {
// logic from _nss_systemd_getgrent_r
errnop.* = @enumToInt(os.E.HOSTDOWN);
return c.NSS_STATUS_UNAVAIL;
};
2022-07-08 06:29:42 +03:00
const group = it.next() orelse {
2022-07-07 21:20:54 +03:00
errnop.* = @enumToInt(os.E.NOENT);
return c.NSS_STATUS_NOTFOUND;
};
2022-07-08 06:29:42 +03:00
const buf = buffer[0..buflen];
const cgroup = state.file.db.packCGroup(group, buf) catch |err| switch (err) {
2022-07-07 21:20:54 +03:00
error.BufferTooSmall => {
errnop.* = @enumToInt(os.E.RANGE);
return c.NSS_STATUS_TRYAGAIN;
},
2022-07-08 06:29:42 +03:00
};
result.* = cgroup;
return c.NSS_STATUS_SUCCESS;
}
export fn _nss_turbo_getpwent_r(
result: *CUser,
buffer: [*]u8,
buflen: usize,
errnop: *c_int,
) c.enum_nss_status {
var state = getStateErrno(errnop) orelse return c.NSS_STATUS_UNAVAIL;
state.getpwent_iterator_mu.lock();
defer state.getpwent_iterator_mu.unlock();
var it = state.getpwent_iterator orelse {
// logic from _nss_systemd_getgrent_r
errnop.* = @enumToInt(os.E.HOSTDOWN);
return c.NSS_STATUS_UNAVAIL;
};
const user = it.next() orelse {
errnop.* = @enumToInt(os.E.NOENT);
return c.NSS_STATUS_NOTFOUND;
};
const buf = buffer[0..buflen];
const cuser = state.file.db.writeUser(user, buf) catch |err| switch (err) {
error.BufferTooSmall => {
errnop.* = @enumToInt(os.E.RANGE);
return c.NSS_STATUS_TRYAGAIN;
},
};
result.* = cuser;
return c.NSS_STATUS_SUCCESS;
}
export fn _nss_turbo_initgroups_dyn(
user_name: [*:0]const u8,
_: u32,
start: *c_long,
size: *c_long,
groups: [*]u32,
limit: c_long,
errnop: *c_int,
) c.enum_nss_status {
const db = getDBErrno(errnop) orelse return c.NSS_STATUS_UNAVAIL;
const user = db.getUser(mem.sliceTo(user_name, 0)) orelse {
errnop.* = @enumToInt(os.E.NOENT);
return c.NSS_STATUS_NOTFOUND;
};
_ = user;
_ = start;
_ = size;
_ = groups;
_ = limit;
return c.NSS_STATUS_SUCCESS;
}
2022-07-07 21:20:54 +03:00
fn getState() ?State {
global_init.call();
2022-07-07 21:20:54 +03:00
return global_state;
}
2022-07-07 21:20:54 +03:00
fn getStateErrno(errnop: *c_int) ?State {
2022-07-06 13:53:34 +03:00
global_init.call();
if (global_state) |state| {
2022-07-07 21:20:54 +03:00
return state;
2022-07-06 13:53:34 +03:00
} else {
errnop.* = @enumToInt(os.E.AGAIN);
return null;
}
2022-04-21 09:30:39 +03:00
}
2022-07-07 21:20:54 +03:00
fn getDBErrno(errnop: *c_int) ?DB {
if (getStateErrno(errnop)) |state| {
return state.file.db;
} else return null;
}
2022-04-21 09:30:39 +03:00
const testing = std.testing;
2022-07-06 16:54:43 +03:00
test "getpwuid_r and getpwnam_r" {
var tf = try File.TestDB.init(testing.allocator);
2022-06-07 06:30:18 +03:00
defer tf.deinit();
2022-07-06 16:54:43 +03:00
const turbonss_db_path_old = turbonss_db_path;
2022-07-06 16:29:21 +03:00
turbonss_db_path = tf.path;
2022-07-06 16:54:43 +03:00
defer {
turbonss_db_path = turbonss_db_path_old;
}
2022-07-06 16:29:21 +03:00
var buffer: [1024]u8 = undefined;
var errno: c_int = 0;
2022-07-06 16:54:43 +03:00
var passwd: CUser = undefined;
try testing.expectEqual(
c.NSS_STATUS_SUCCESS,
_nss_turbo_getpwuid_r(128, &passwd, &buffer, buffer.len, &errno),
);
2022-07-06 16:29:21 +03:00
try testing.expectEqual(@as(c_int, 0), errno);
2022-07-06 16:54:43 +03:00
try testVidmantas(passwd);
passwd = undefined;
try testing.expectEqual(
c.NSS_STATUS_SUCCESS,
_nss_turbo_getpwnam_r("vidmantas", &passwd, &buffer, buffer.len, &errno),
);
try testing.expectEqual(@as(c_int, 0), errno);
try testVidmantas(passwd);
passwd = undefined;
try testing.expectEqual(
c.NSS_STATUS_NOTFOUND,
_nss_turbo_getpwnam_r("does not exist", &passwd, &buffer, buffer.len, &errno),
);
try testing.expectEqual(@enumToInt(os.E.NOENT), @intCast(u16, errno));
passwd = undefined;
var small_buffer: [1]u8 = undefined;
try testing.expectEqual(
c.NSS_STATUS_TRYAGAIN,
_nss_turbo_getpwuid_r(0, &passwd, &small_buffer, small_buffer.len, &errno),
);
try testing.expectEqual(@enumToInt(os.E.RANGE), @intCast(u16, errno));
}
fn testVidmantas(u: CUser) !void {
try testing.expectEqualStrings("vidmantas", mem.sliceTo(u.pw_name, 0));
try testing.expectEqual(@as(u32, 128), u.pw_uid);
try testing.expectEqual(@as(u32, 128), u.pw_gid);
try testing.expectEqualStrings("Vidmantas Kaminskas", mem.sliceTo(u.pw_gecos, 0));
try testing.expectEqualStrings("/bin/bash", mem.sliceTo(u.pw_shell, 0));
2022-04-20 07:19:34 +03:00
}
2022-07-07 06:54:27 +03:00
test "getgrgid_r and getgrnam_r" {
var tf = try File.TestDB.init(testing.allocator);
defer tf.deinit();
const turbonss_db_path_old = turbonss_db_path;
turbonss_db_path = tf.path;
defer {
turbonss_db_path = turbonss_db_path_old;
}
var buffer: [1024]u8 = undefined;
var errno: c_int = 0;
var group: CGroup = undefined;
try testing.expectEqual(
c.NSS_STATUS_SUCCESS,
_nss_turbo_getgrgid_r(128, &group, &buffer, buffer.len, &errno),
);
try testing.expectEqual(@as(c_int, 0), errno);
try testVidmantasGroup(group);
2022-07-07 07:00:13 +03:00
group = undefined;
try testing.expectEqual(
c.NSS_STATUS_SUCCESS,
_nss_turbo_getgrnam_r("vidmantas", &group, &buffer, buffer.len, &errno),
);
try testing.expectEqual(@as(c_int, 0), errno);
try testVidmantasGroup(group);
group = undefined;
try testing.expectEqual(
c.NSS_STATUS_NOTFOUND,
_nss_turbo_getgrnam_r("does not exist", &group, &buffer, buffer.len, &errno),
);
try testing.expectEqual(@enumToInt(os.E.NOENT), @intCast(u16, errno));
2022-07-07 06:54:27 +03:00
}
fn testVidmantasGroup(g: CGroup) !void {
try testing.expectEqual(@as(u32, 128), g.gid);
2022-07-07 07:00:13 +03:00
try testing.expectEqualStrings("vidmantas", mem.sliceTo(g.name, 0));
2022-07-07 06:54:27 +03:00
const members = g.members;
try testing.expect(members[0] != null);
2022-07-07 07:00:13 +03:00
try testing.expectEqualStrings("vidmantas", mem.sliceTo(members[0].?, 0));
2022-07-07 06:54:27 +03:00
}