hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

passwd.ha (4819B)


      1 // SPDX-License-Identifier: MPL-2.0
      2 // (c) Hare authors <https://harelang.org>
      3 
      4 use bufio;
      5 use encoding::utf8;
      6 use io;
      7 use memio;
      8 use os;
      9 use strconv;
     10 use strings;
     11 use unix;
     12 
     13 // A Unix-like password database entry.
     14 export type pwent = struct {
     15 	// Login name
     16 	username: str,
     17 	// Optional encrypted password
     18 	password: str,
     19 	// Numerical user ID
     20 	uid: unix::uid,
     21 	// Numerical group ID
     22 	gid: unix::gid,
     23 	// User name or comment field
     24 	comment: str,
     25 	// User home directory
     26 	homedir: str,
     27 	// Optional user command interpreter
     28 	shell: str,
     29 };
     30 
     31 export type userreader = struct {
     32 	scan: bufio::scanner,
     33 };
     34 
     35 // Creates a parser for an /etc/passwd-formatted file. Use [[nextpw]] to
     36 // enumerate the users, and [[users_finish]] to free resources associated with
     37 // the reader.
     38 export fn users_read(in: io::handle) userreader = {
     39 	return groupreader {
     40 		scan = bufio::newscanner(in),
     41 	};
     42 };
     43 
     44 // Frees resources associated with a [[groupreader]].
     45 export fn users_finish(rd: *userreader) void = {
     46 	bufio::finish(&rd.scan);
     47 };
     48 
     49 // Reads a Unix-like password entry from an [[io::handle]]. The return value is
     50 // borrowed from the reader.
     51 export fn nextpw(rd: *userreader) (pwent | io::EOF | io::error | invalid) = {
     52 	const line = match (bufio::scan_line(&rd.scan)) {
     53 	case io::EOF =>
     54 		return io::EOF;
     55 	case let ln: const str =>
     56 		yield ln;
     57 	case utf8::invalid =>
     58 		return invalid;
     59 	case let err: io::error =>
     60 		return err;
     61 	};
     62 	const tok = strings::tokenize(line, ":");
     63 
     64 	let i = 0z;
     65 	let fields: [7]str = [""...];
     66 	for (const f => strings::next_token(&tok)) {
     67 		defer i += 1;
     68 		if (i >= len(fields)) {
     69 			return invalid;
     70 		};
     71 		fields[i] = f;
     72 	};
     73 
     74 	const uid = match (strconv::stou64(fields[2])) {
     75 	case let u: u64 =>
     76 		yield u: unix::uid;
     77 	case =>
     78 		return invalid;
     79 	};
     80 
     81 	const gid = match (strconv::stou64(fields[3])) {
     82 	case let u: u64 =>
     83 		yield u: unix::gid;
     84 	case =>
     85 		return invalid;
     86 	};
     87 
     88 	return pwent {
     89 		username = fields[0],
     90 		password = fields[1],
     91 		uid      = uid,
     92 		gid      = gid,
     93 		comment  = fields[4],
     94 		homedir  = fields[5],
     95 		shell    = fields[6],
     96 	};
     97 };
     98 
     99 // Frees resources associated with a [[pwent]].
    100 export fn pwent_finish(ent: *pwent) void = {
    101 	free(ent.username);
    102 	free(ent.password);
    103 	free(ent.comment);
    104 	free(ent.homedir);
    105 	free(ent.shell);
    106 };
    107 
    108 fn pwent_dup(ent: *pwent) void = {
    109 	ent.username = strings::dup(ent.username);
    110 	ent.password = strings::dup(ent.password);
    111 	ent.comment = strings::dup(ent.comment);
    112 	ent.homedir = strings::dup(ent.homedir);
    113 	ent.shell = strings::dup(ent.shell);
    114 };
    115 
    116 // Looks up a user by name in a Unix-like password file. It expects a password
    117 // database file at /etc/passwd. Aborts if that file doesn't exist or is not
    118 // properly formatted. The return value must be freed with [[pwent_finish]].
    119 //
    120 // See [[nextpw]] for low-level parsing API.
    121 export fn getuser(username: str) (pwent | void) = {
    122 	const file = match (os::open("/etc/passwd")) {
    123 	case let f: io::file =>
    124 		yield f;
    125 	case =>
    126 		abort("Can't open /etc/passwd");
    127 	};
    128 	defer io::close(file)!;
    129 
    130 	const rd = users_read(file);
    131 	defer users_finish(&rd);
    132 
    133 	for (const ent => nextpw(&rd)!) {
    134 		if (ent.username == username) {
    135 			pwent_dup(&ent);
    136 			return ent;
    137 		};
    138 	};
    139 };
    140 
    141 // Looks up a user by ID in a Unix-like password file. It expects a password
    142 // database file at /etc/passwd. Aborts if that file doesn't exist or is not
    143 // properly formatted. The return value must be freed with [[pwent_finish]].
    144 //
    145 // See [[nextpw]] for low-level parsing API.
    146 export fn getuid(uid: unix::uid) (pwent | void) = {
    147 	const file = match (os::open("/etc/passwd")) {
    148 	case let f: io::file =>
    149 		yield f;
    150 	case =>
    151 		abort("Can't open /etc/passwd");
    152 	};
    153 	defer io::close(file)!;
    154 
    155 	const rd = users_read(file);
    156 	defer users_finish(&rd);
    157 
    158 	for (const ent => nextpw(&rd)!) {
    159 		if (ent.uid == uid) {
    160 			pwent_dup(&ent);
    161 			return ent;
    162 		};
    163 	};
    164 };
    165 
    166 @test fn nextpw() void = {
    167 	const buf = memio::fixed(strings::toutf8(
    168 		"sircmpwn:x:1000:1000:sircmpwn's comment:/home/sircmpwn:/bin/rc\n"
    169 		"alex:x:1001:1001::/home/alex:/bin/zsh\n"));
    170 
    171 	const rd = users_read(&buf);
    172 	defer users_finish(&rd);
    173 
    174 	const expect = [
    175 		pwent {
    176 			username = "sircmpwn",
    177 			password = "x",
    178 			uid = 1000,
    179 			gid = 1000,
    180 			comment = "sircmpwn's comment",
    181 			homedir = "/home/sircmpwn",
    182 			shell = "/bin/rc",
    183 		},
    184 		pwent {
    185 			username = "alex",
    186 			password = "x",
    187 			uid = 1001,
    188 			gid = 1001,
    189 			comment = "",
    190 			homedir = "/home/alex",
    191 			shell = "/bin/zsh",
    192 		},
    193 	];
    194 
    195 	let i = 0z;
    196 	for (const ent => nextpw(&rd)!) {
    197 		defer i += 1;
    198 		assert(ent.username == expect[i].username);
    199 		assert(ent.password == expect[i].password);
    200 		assert(ent.uid == expect[i].uid);
    201 		assert(ent.gid == expect[i].gid);
    202 		assert(ent.comment == expect[i].comment);
    203 		assert(ent.homedir == expect[i].homedir);
    204 		assert(ent.shell == expect[i].shell);
    205 	};
    206 
    207 	assert(i == len(expect));
    208 };