Fully remove nix as a direct dependency of the shadow-rs workspace.
New module shadow_core::process provides process-wide POSIX wrappers
via libc for setuid/setgid/initgroups/execv/sigprocmask. These MUST
use libc (not rustix) because rustix intentionally provides only
per-thread versions, which are incorrect for setuid-root tools.
Migrations:
- hardening.rs: setrlimit → rustix::process, sigprocmask → process
module, User::from_uid → /etc/passwd parser lookup
- pam.rs: nix::sys::termios → rustix::termios
- passwd.rs: full migration (seteuid, setuid, getuid, User lookup)
- newgrp.rs: full migration (termios, User lookup, setgid, initgroups,
execv)
- chpasswd/chage/chfn/chsh: setuid → shadow_core::process
- lock.rs tests: fcntl_getfd → rustix::io
libc stays for: PAM FFI, crypt(3) FFI, process-wide POSIX wrappers.
nix remains only as a transitive dep of uucore (upstream).
Security fixes:
- newgrp: add initgroups() before execv to reset supplementary groups (H-3)
- pam: use Zeroizing<String> for password buffers on all exit paths (M-5)
- atomic: retry-once on stale tmp file from crashed run (M-3)
- atomic: UmaskGuard opts out of Send/Sync via PhantomData (L-2)
- lock: flush + fsync in write_pid_file before hard_link (L-7)
Deduplication (~300 lines removed):
- Centralize caller_is_root(), current_username(), SignalBlocker in
shadow_core::hardening — remove 10+5+5 private copies across tools
- Add SignalBlocker to all 6 remaining mutating tools (M-4)
- Remove dead CantSort variant from grpck, fix pwck allow (M-8)
- Remove dead read_passwd from test_chfn (M-8)
Binary cleanup:
- shadow-rs multicall: use ExitCode instead of process::exit (M-6)
- completions: use ExitCode + Result pattern, remove unwrap/exit (H-3)
Documentation:
- crypt: thread-safety warning on public API (H-1)
- shadow: clarify unlock() behavior for * password (H-2)
- validate: document $ rejection deviation from GNU (L-4)
- selinux/audit: note shell-out implementation status (L-6)
- chpasswd/chage tests: TODO for --prefix integration (M-8)
- userdel: document inline skip_unless_root reason (L-9)
Testing:
- Add fuzz targets for group and gshadow parsers (M-7)
- skel: use create_dir for tighter error detection (L-8)
- crypt: compile-time modulo bias assertion (L-1)
Replace 13 local copies across all test files with a shared import
from tests/common/mod.rs. Fix getuid/geteuid inconsistency in
test_useradd.rs and test_userdel.rs (effective UID is correct for
setuid-root tools).
Fixes#133
- Error on missing shadow entry instead of silent no-op
- Validate password hash rejects ':', '\n', '\r'
- Centralize days_since_epoch() in shadow-core
- Assert field count and reserved field in test
Ansible's user module calls `usermod -p <hash>` to set passwords.
This was the most significant gap for Ansible interop.
Implementation:
- Add PASSWORD option constant and clap argument (-p/--password)
- Write hash directly to shadow entry, update last_change to today
- Follows the same lock → read → mutate → atomic_write pattern
Tests:
- test_set_password: basic -p flag with hash verification
- test_set_password_long_flag: --password long form
- test_set_password_preserves_other_fields: all 9 shadow fields intact
Also updated e2e Ansible playbook to use native `user: password:`
parameter instead of the chpasswd -e workaround.
Fixes#114
- Fix cargo build --release --workspace in test scripts
- Document setuid-on-symlinks as intentional in Makefile
- Warn (not abort) on subid allocation failure in useradd
- Propagate subid file read errors instead of unwrap_or_default
- Update SELinux docs to describe best-effort behavior
- Add env_clear() to getfattr subprocess in selinux.rs
- Use hardening::sanitized_env() in nscd.rs instead of local reimpl
- Create testuser in pam-e2e.sh, add cleanup
- Document su-as-root limitation in pam-e2e.sh
- Fix set -e exit code capture in gnu-compat.sh
- Add 5 date validation unit tests in chage
- Wire audit::log_user_event into all 7 tools
All 14 shadow-utils tools are now implemented.
groupadd: create groups with auto GID allocation, system groups, force mode.
groupdel: delete groups, checks for primary group usage.
groupmod: modify GID, rename, password changes.
grpck: verify /etc/group and /etc/gshadow integrity.
chfn: change GECOS sub-fields (name, room, phone).
chsh: change login shell with /etc/shells validation.
newgrp: change effective group with crypt(3) password verification.
449 tests, zero clippy warnings, all 14 tools in multicall binary.
Copilot findings (PR #36):
- parsers: use trim_start() for blank/comment detection, parse
untrimmed line to preserve GECOS whitespace
- pam: newline after password prompt is best-effort (let _ =)
New tests (#37):
- test_concurrent_lock_operations: 4 threads racing lock/unlock
- test_gnu_compat_status_output: compare -S output with GNU passwd
- test_gnu_compat_lock_unlock: verify lock/unlock matches GNU behavior
147 tests, zero clippy warnings.