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