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 };