hare

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

passwd.ha (4281B)


      1 // SPDX-License-Identifier: MPL-2.0
      2 // (c) Hare authors <https://harelang.org>
      3 
      4 use bufio;
      5 use io;
      6 use memio;
      7 use os;
      8 use strconv;
      9 use strings;
     10 use unix;
     11 
     12 // A Unix-like password database entry.
     13 export type pwent = struct {
     14 	// Login name
     15 	username: str,
     16 	// Optional encrypted password
     17 	password: str,
     18 	// Numerical user ID
     19 	uid: unix::uid,
     20 	// Numerical group ID
     21 	gid: unix::gid,
     22 	// User name or comment field
     23 	comment: str,
     24 	// User home directory
     25 	homedir: str,
     26 	// Optional user command interpreter
     27 	shell: str,
     28 };
     29 
     30 // Reads a Unix-like password entry from an [[io::handle]]. The caller must free
     31 // the return value using [[pwent_finish]].
     32 export fn nextpw(in: io::handle) (pwent | io::EOF | io::error | invalid) = {
     33 	let line = match (bufio::read_line(in)?) {
     34 	case io::EOF =>
     35 		return io::EOF;
     36 	case let ln: []u8 =>
     37 		yield ln;
     38 	};
     39 	let line = match (strings::fromutf8(line)) {
     40 	case let s: str =>
     41 		yield s;
     42 	case =>
     43 		return invalid;
     44 	};
     45 
     46 	let fields = strings::split(line, ":");
     47 	defer free(fields);
     48 
     49 	if (len(fields) != 7) {
     50 		return invalid;
     51 	};
     52 
     53 	let uid = match (strconv::stou64(fields[2])) {
     54 	case let u: u64 =>
     55 		yield u: unix::uid;
     56 	case =>
     57 		return invalid;
     58 	};
     59 
     60 	let gid = match (strconv::stou64(fields[3])) {
     61 	case let u: u64 =>
     62 		yield u: unix::gid;
     63 	case =>
     64 		return invalid;
     65 	};
     66 
     67 	return pwent {
     68 		// Borrows the return value of bufio::read_line
     69 		username = fields[0],
     70 		password = fields[1],
     71 		uid      = uid,
     72 		gid      = gid,
     73 		comment  = fields[4],
     74 		homedir  = fields[5],
     75 		shell    = fields[6],
     76 	};
     77 };
     78 
     79 // Frees resources associated with a [[pwent]].
     80 export fn pwent_finish(ent: *pwent) void = {
     81 	// pwent fields are sliced from one allocated string returned by
     82 	// bufio::read_line. Freeing the first field frees the entire string in
     83 	// one go.
     84 	free(ent.username);
     85 };
     86 
     87 // Looks up a user by name in a Unix-like password file. It expects a password
     88 // database file at /etc/passwd. Aborts if that file doesn't exist or is not
     89 // properly formatted. The return value must be freed with [[pwent_finish]].
     90 //
     91 // See [[nextpw]] for low-level parsing API.
     92 export fn getuser(username: str) (pwent | void) = {
     93 	let file = match (os::open("/etc/passwd")) {
     94 	case let f: io::file =>
     95 		yield f;
     96 	case =>
     97 		abort("Can't open /etc/passwd");
     98 	};
     99 	defer io::close(file)!;
    100 
    101 	let rbuf: [os::BUFSZ]u8 = [0...];
    102 	let strm = bufio::init(file, rbuf, []);
    103 	for (true) {
    104 		let ent = match (nextpw(&strm)) {
    105 		case let e: pwent =>
    106 			yield e;
    107 		case io::EOF =>
    108 			break;
    109 		case =>
    110 			abort("Invalid entry in /etc/passwd");
    111 		};
    112 
    113 		if (ent.username == username) {
    114 			return ent;
    115 		} else {
    116 			pwent_finish(&ent);
    117 		};
    118 	};
    119 };
    120 
    121 // Looks up a user by ID in a Unix-like password file. It expects a password
    122 // database file at /etc/passwd. Aborts if that file doesn't exist or is not
    123 // properly formatted. The return value must be freed with [[pwent_finish]].
    124 //
    125 // See [[nextpw]] for low-level parsing API.
    126 export fn getuid(uid: unix::uid) (pwent | void) = {
    127 	let file = match (os::open("/etc/passwd")) {
    128 	case let f: io::file =>
    129 		yield f;
    130 	case =>
    131 		abort("Can't open /etc/passwd");
    132 	};
    133 	defer io::close(file)!;
    134 
    135 	let rbuf: [os::BUFSZ]u8 = [0...];
    136 	let strm = bufio::init(file, rbuf, []);
    137 	for (true) {
    138 		let ent = match (nextpw(&strm)) {
    139 		case let e: pwent =>
    140 			yield e;
    141 		case io::EOF =>
    142 			break;
    143 		case =>
    144 			abort("Invalid entry in /etc/passwd");
    145 		};
    146 
    147 		if (ent.uid == uid) {
    148 			return ent;
    149 		} else {
    150 			pwent_finish(&ent);
    151 		};
    152 	};
    153 };
    154 
    155 @test fn nextpw() void = {
    156 	let buf = memio::fixed(strings::toutf8(
    157 		"sircmpwn:x:1000:1000:sircmpwn's comment:/home/sircmpwn:/bin/mrsh\n"
    158 		"alex:x:1001:1001::/home/alex:/bin/zsh"));
    159 
    160 	let ent = nextpw(&buf) as pwent;
    161 	defer pwent_finish(&ent);
    162 
    163 	assert(ent.username == "sircmpwn");
    164 	assert(ent.password == "x");
    165 	assert(ent.uid == 1000);
    166 	assert(ent.gid == 1000);
    167 	assert(ent.comment == "sircmpwn's comment");
    168 	assert(ent.homedir == "/home/sircmpwn");
    169 	assert(ent.shell == "/bin/mrsh");
    170 
    171 	let ent = nextpw(&buf) as pwent;
    172 	defer pwent_finish(&ent);
    173 
    174 	assert(ent.username == "alex");
    175 	assert(ent.password == "x");
    176 	assert(ent.uid == 1001);
    177 	assert(ent.gid == 1001);
    178 	assert(ent.comment == "");
    179 	assert(ent.homedir == "/home/alex");
    180 	assert(ent.shell == "/bin/zsh");
    181 
    182 	// No more entries
    183 	assert(nextpw(&buf) is io::EOF);
    184 };