Flash 189 lines
// passwd — interactive password change.
//
// With no argument it changes the calling user's own password (uid ->
// login name via /etc/passwd); with an argument (`passwd <user>`) it
// targets that record — which only root may do for records other than
// its own (sys_passwd enforces this, the tool just passes it through).
// Prompts follow the Unix shape: the current password is skipped when
// the caller is root (root resets without proof), the new password is
// asked twice and must match. All password prompts run with kernel echo
// off.
//
// The KDF and the splice-safe shadow rewrite live in the kernel
// (sys_passwd, slot 46) — this tool only collects strings and reports
// the verdict. Without a writable FAT32 shadow (/mnt/shadow — absent on
// QEMU virt and on a freshly formatted card) the kernel answers -1 and
// the tool says so.
//
// Same coreutil recipe as login / dmesg (flibc _start shim, single
// PT_LOAD, no heap allocator — only fixed stack buffers).
use flibc
use syscall_defs as defs
use pwfile
link "flibc_start"
link "flibc_mem"
const PASSWD_PATH cstr = "/etc/passwd"
fn emit(s []u8) void {
_ = flibc.sys.write_fd(1, s.ptr, s.len)
}
fn emitErr(s []u8) void {
_ = flibc.sys.write_fd(2, s.ptr, s.len)
}
// Read one line from fd 0 (raw, one byte at a time) into `buf`, stopping
// at CR / LF or EOF. Returns the byte count, excluding the terminator.
// Echo of typed bytes is the kernel's job (the console echo flag) — this
// loop never echoes, which is exactly right for password input.
fn readLine(buf []mut u8) usize {
var n usize = 0
while n < buf.len {
var ch [1]u8 = undefined
r := flibc.sys.read(0, &ch, 1)
if r <= 0 {
break
}
if ch[0] == '\n' || ch[0] == '\r' {
break
}
buf[n] = ch[0]
n += 1
}
return n
}
fn strLen(s cstr) usize {
var n usize = 0
while s[n] != 0 {
n += 1
}
return n
}
fn bytesEqual(a []u8, b []u8) bool {
if a.len != b.len {
return false
}
// (paired `for x, y in a, b` is not expressible; an index loop over the
// now-equal lengths compares the same byte pairs.)
var i usize = 0
while i < a.len {
if a[i] != b[i] {
return false
}
i += 1
}
return true
}
export fn main(argc usize, argv argv) noreturn {
var user_buf [64]u8 = undefined
var old_buf [128]u8 = undefined
var new_buf [128]u8 = undefined
var retype_buf [128]u8 = undefined
var pw_buf [512]u8 = undefined
is_root := flibc.sys.geteuid() == 0
// Resolve the target user: argv[1], or the caller's own login name.
var user_len usize = 0
if argc >= 2 {
arg := argv[1].?
alen := strLen(arg)
if alen == 0 || alen > user_buf.len {
emitErr("passwd: bad user name\n")
flibc.exit()
}
var i usize = 0
while i < alen {
user_buf[i] = arg[i]
i += 1
}
user_len = alen
} else {
uid_raw := flibc.sys.getuid()
if uid_raw < 0 {
emitErr("passwd: cannot read uid\n")
flibc.exit()
}
fd := flibc.sys.open(PASSWD_PATH)
if fd < 0 {
emitErr("passwd: cannot open /etc/passwd\n")
flibc.exit()
}
var pn usize = 0
while pn < pw_buf.len {
r := flibc.sys.read(fd, pw_buf[pn..].ptr, pw_buf.len - pn)
if r <= 0 {
break
}
pn += #intCast(r)
}
_ = flibc.sys.close(fd)
// (orelse with a multi-statement block handler is not expressible; an
// explicit null check yields the same divergent exit.)
maybe_entry := pwfile.lookupByUid(pw_buf[0..pn], #intCast(uid_raw))
if maybe_entry == null {
emitErr("passwd: no passwd entry for this uid\n")
flibc.exit()
}
entry := maybe_entry.?
if entry.user.len > user_buf.len {
emitErr("passwd: bad user name\n")
flibc.exit()
}
var i usize = 0
while i < entry.user.len {
user_buf[i] = entry.user[i]
i += 1
}
user_len = entry.user.len
}
emit("Changing password for ")
emit(user_buf[0..user_len])
emit("\n")
// Current password — skipped for root (sys_passwd does not require
// it from euid 0; that is the forgotten-password recovery path).
var old_len usize = 0
if !is_root {
_ = flibc.sys.set_console_mode(0)
emit("Current password: ")
old_len = readLine(&old_buf)
emit("\n")
}
// New password, asked twice, echo off.
_ = flibc.sys.set_console_mode(0)
emit("New password: ")
new_len := readLine(&new_buf)
emit("\n")
emit("Retype new password: ")
retype_len := readLine(&retype_buf)
emit("\n")
if new_len == 0 {
emitErr("passwd: empty password not allowed\n")
flibc.exit()
}
if !bytesEqual(new_buf[0..new_len], retype_buf[0..retype_len]) {
emitErr("passwd: passwords do not match\n")
flibc.exit()
}
ret := flibc.sys.passwd(&user_buf, user_len, &old_buf, old_len, &new_buf, new_len)
if ret == 0 {
emit("passwd: password updated\n")
} else if ret == -defs.EACCES {
emitErr("passwd: authentication failure\n")
} else {
emitErr("passwd: cannot write shadow (read-only or missing)\n")
}
flibc.exit()
}