11 Commits

Author SHA1 Message Date
Pierre Warnier 32c94fbc76 shadow-core, tools: complete nix removal — phase 2 (#140)
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).
2026-04-22 11:26:49 +02:00
Pierre Warnier 92783375aa review: fix all findings from comprehensive code review
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)
2026-04-03 19:27:25 +02:00
Pierre Warnier 96f9c40ca3 tests: deduplicate skip_unless_root() into common module
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
2026-04-03 17:45:10 +02:00
Pierre Warnier 86eb48de44 meta: align Cargo.toml metadata with uutils conventions
- Fix root package edition (2021 → workspace 2024)
- Use workspace inheritance for license, authors, repository, rust-version
- Fix keywords: "coreutils" → "shadow-utils", add "uutils"
- Add homepage, keywords, categories to workspace.package
- Add workspace metadata inheritance to all 15 crate Cargo.toml files
- Normalize tool descriptions to "tool ~ (shadow-rs) verb phrase" format
- Add feat_common_core feature alias (groups all 14 tools)
- Fix pre-existing clippy and fmt issues from edition 2024 alignment
2026-04-03 14:44:59 +02:00
Pierre Warnier c3e0679acc review: address all 4 Copilot review comments
- 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
2026-04-03 13:57:28 +02:00
Pierre Warnier 24347e9913 usermod: add -p/--password flag for pre-hashed passwords (#114)
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
2026-04-03 13:48:11 +02:00
Pierre Warnier 7bd47ff3d0 tests: add integration tests for all 14 tools, add CHANGELOG
Add integration tests for the 8 remaining tools:
- useradd, userdel, usermod (user management)
- groupadd, groupdel, groupmod (group management)
- pwck, grpck (file consistency checkers)

101 new tests covering clap parsing, flag conflicts, error paths,
and root-only file mutation operations with synthetic /etc/ files.

Total test count: 461 → 562.

Also adds CHANGELOG.md for v0.1.0 release.
2026-03-24 15:59:04 +01:00
Pierre Warnier 8ee65a7552 phase3: implement groupadd, groupdel, groupmod, grpck, chfn, chsh, newgrp
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.
2026-03-24 10:47:07 +01:00
Pierre Warnier ed5bb0b71a phase2: implement useradd, userdel, usermod, chpasswd, chage
Fixes #57 — useradd: create user accounts with all core flags
  (-c, -d, -e, -f, -g, -G, -m, -M, -k, -N, -o, -p, -r, -s, -u, -U).
  UID/GID allocation, home dir creation, skel copy, shadow entry.

Fixes #62 — userdel: remove user from passwd/shadow/group/gshadow.
  Flags: -f, -r, -P. Removes from membership lists.

Fixes #63 — usermod: modify user properties.
  Flags: -c, -d, -e, -f, -g, -G, -a, -L, -U, -l, -s, -u, -P.
  Lock/unlock via shadow, group membership management.

Fixes #64 — chpasswd: batch password change from stdin.
  Reads username:password pairs, updates /etc/shadow.
  Supports -e (pre-encrypted), -R (chroot).

Fixes #65 — chage: password aging management.
  All flags: -d, -E, -I, -l, -m, -M, -R, -W.
  List mode (-l) shows aging info. Matches GNU output format.

364 tests, zero clippy warnings.
2026-03-24 10:19:52 +01:00
Pierre Warnier fbb4f496a6 security: address Copilot review on PR #36, add concurrency + compat tests
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.
2026-03-23 16:07:08 +01:00
Pierre Warnier 3ed6188aea tests: add proptest, edge cases, fuzz targets, integration tests, docs
Fixes #11 — tests/by-util/test_passwd.rs: 15 integration tests
  following uutils convention (status format, exit codes, lock/unlock
  cycle, aging, lifecycle, quiet mode, error cases).

Fixes #12 — fuzz targets for all parsers:
  fuzz_passwd_parse, fuzz_shadow_parse, fuzz_login_defs_parse,
  fuzz_validate_username. All must not panic on any input.

Fixes #15 — proptest round-trip tests for PasswdEntry, ShadowEntry:
  generate random valid entries, serialize, parse back, compare.

Fixes #16 — parser edge case tests:
  wrong field counts, empty files, roundtrip via file I/O, negative
  values, boundary values, unicode rejection, null bytes, mixed
  whitespace, duplicate keys, key-only lines.

Also: CONTRIBUTING.md, SECURITY.md created. README.md and CLAUDE.md
updated to reflect uucore integration and current project state.

142 tests passing on Debian/Alpine/Fedora, zero clippy warnings.
2026-03-23 14:11:19 +01:00