From f3e59efb10e4f1f583efa22d6ccaac849fdc6ecc Mon Sep 17 00:00:00 2001 From: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com> Date: Thu, 22 Oct 2026 02:36:07 +0530 Subject: [PATCH 1/2] useradd, usermod: accept -1 for -f and -e useradd(8) and usermod(8) document -1 for --inactive and --expiredate, and the value handling already maps it to an empty field, but clap rejected -1 as an unknown argument. chage already allows hyphen values for these options. --- src/uu/useradd/src/useradd.rs | 22 ++++++++++++++++++++-- src/uu/usermod/src/usermod.rs | 18 ++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/uu/useradd/src/useradd.rs b/src/uu/useradd/src/useradd.rs index 41f57cd..67ec4e6 100644 --- a/src/uu/useradd/src/useradd.rs +++ b/src/uu/useradd/src/useradd.rs @@ -1062,14 +1062,16 @@ pub fn uu_app() -> Command { .short('e') .long("expiredate") .value_name("EXPIRE_DATE") - .help("Account expiration date (YYYY-MM-DD)"), + .help("Account expiration date (YYYY-MM-DD)") + .allow_hyphen_values(true), ) .arg( Arg::new(options::INACTIVE) .short('f') .long("inactive") .value_name("INACTIVE") - .help("Days the password may stay expired before the account is disabled"), + .help("Days the password may stay expired before the account is disabled") + .allow_hyphen_values(true), ) .arg( Arg::new(options::GID) @@ -2376,6 +2378,22 @@ mod tests { assert_eq!(uid, 9100); } + #[test] + fn test_negative_one_inactive_and_expiredate() { + let m = uu_app() + .try_get_matches_from(["useradd", "-f", "-1", "-e", "-1", "testuser"]) + .expect("should parse -f -1 -e -1"); + assert_eq!( + m.get_one::(options::INACTIVE).map(String::as_str), + Some("-1") + ); + assert_eq!( + m.get_one::(options::EXPIRE_DATE) + .map(String::as_str), + Some("-1") + ); + } + #[test] fn test_determine_uid_honors_sys_uid_range_for_system() { let entries: Vec = vec![]; diff --git a/src/uu/usermod/src/usermod.rs b/src/uu/usermod/src/usermod.rs index 113f691..bfc1ed4 100644 --- a/src/uu/usermod/src/usermod.rs +++ b/src/uu/usermod/src/usermod.rs @@ -584,7 +584,8 @@ pub fn uu_app() -> Command { .short('e') .long("expiredate") .value_name("EXPIRE_DATE") - .help("Set the account expiration date"), + .help("Set the account expiration date") + .allow_hyphen_values(true), ) .arg( Arg::new(options::INACTIVE) @@ -592,7 +593,8 @@ pub fn uu_app() -> Command { .long("inactive") .value_name("INACTIVE") .value_parser(clap::value_parser!(i64)) - .help("Days the password may stay expired before disabling the account"), + .help("Days the password may stay expired before disabling the account") + .allow_hyphen_values(true), ) .arg( Arg::new(options::GID) @@ -737,6 +739,18 @@ mod tests { ); } + #[test] + fn test_negative_one_inactive_and_expiredate() { + let m = uu_app() + .try_get_matches_from(["usermod", "-f", "-1", "-e", "-1", "u"]) + .expect("should parse -f -1 -e -1"); + assert_eq!(m.get_one::(options::INACTIVE).copied(), Some(-1)); + assert_eq!( + m.get_one::(options::EXPIREDATE).map(String::as_str), + Some("-1") + ); + } + #[test] fn test_append_groups() { let m = uu_app() From 9de8fb5278103b3737f054cfc11c7d138ce052b1 Mon Sep 17 00:00:00 2001 From: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com> Date: Thu, 22 Oct 2026 18:50:01 +0530 Subject: [PATCH 2/2] usermod: test that -e -1 and -f -1 blank the shadow fields --- tests/by-util/test_usermod.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/by-util/test_usermod.rs b/tests/by-util/test_usermod.rs index b4b3049..8ec5cbf 100644 --- a/tests/by-util/test_usermod.rs +++ b/tests/by-util/test_usermod.rs @@ -527,6 +527,27 @@ fn test_expiredate_accepts_iso_date() { ); } +#[test] +fn test_negative_one_blanks_inactive_and_expiredate() { + if crate::common::skip_unless_root() { + return; + } + + let dir = setup_prefix( + "alice:x:1000:1000:Alice:/home/alice:/bin/bash\n", + "alice:$6$h:19500:0:99999:7:30:19782:\n", + "alice:x:1000:\n", + ); + + let code = run_with_prefix(&dir, &["-e", "-1", "-f", "-1", "alice"]); + assert_eq!(code, 0, "-1 must be accepted for -e and -f"); + assert_eq!( + read_shadow(&dir), + "alice:$6$h:19500:0:99999:7:::\n", + "-1 must blank the inactive and expire fields" + ); +} + #[test] fn test_bad_expiredate_changes_nothing() { if crate::common::skip_unless_root() {