diff --git a/.fmf/version b/.fmf/version deleted file mode 100644 index d00491f..0000000 --- a/.fmf/version +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/.gitignore b/.gitignore index 3023995..5db8705 100644 --- a/.gitignore +++ b/.gitignore @@ -32,25 +32,3 @@ shadow-4.1.4.2.tar.bz2 /shadow-4.15.0.tar.xz.asc /shadow-4.15.1.tar.xz /shadow-4.15.1.tar.xz.asc -/shadow-4.16.0.tar.xz -/shadow-4.16.0.tar.xz.asc -/shadow-4.17.0-rc1.tar.xz -/shadow-4.17.0-rc1.tar.xz.asc -/shadow-4.17.0.tar.xz -/shadow-4.17.0.tar.xz.asc -/shadow-4.17.4.tar.xz -/shadow-4.17.4.tar.xz.asc -/shadow-4.18.0.tar.xz -/shadow-4.18.0.tar.xz.asc -/shadow-4.19.0.tar.xz -/shadow-4.19.0.tar.xz.asc -/shadow-4.19.2.tar.xz -/shadow-4.19.2.tar.xz.asc -/shadow-4.19.3.tar.xz -/shadow-4.19.3.tar.xz.asc -/shadow-4.20.0-rc2.tar.xz -/shadow-4.20.0-rc2.tar.xz.asc -/shadow-4.20.0-rc3.tar.xz -/shadow-4.20.0-rc3.tar.xz.asc -/shadow-4.20.0.tar.xz -/shadow-4.20.0.tar.xz.asc diff --git a/plans/tier0-functional.fmf b/plans/tier0-functional.fmf deleted file mode 100644 index a9939b5..0000000 --- a/plans/tier0-functional.fmf +++ /dev/null @@ -1,51 +0,0 @@ -summary: Tier 0 functional tests for shadow-utils -description: | - Run comprehensive system tests for shadow-utils. These tests validate user and group - account management functionality by testing actual system operations including user - creation, password management, group operations, and verification of system files - (i.e. /etc/passwd, /etc/shadow). - -provision: - how: virtual - image: fedora - -prepare: - - name: Install general dependencies - how: install - package: - - expect - - gcc - - git - - libssh-devel - - python3-devel - - python3-pip - - - name: Setup SSH keys for localhost testing - how: shell - script: - - ssh-keygen -t rsa -f /root/.ssh/id_rsa -N "" -q - - cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys - - chmod 600 /root/.ssh/authorized_keys - - ssh-keyscan -H localhost >> /root/.ssh/known_hosts - - - name: Clone shadow repository - how: shell - script: - - git clone https://github.com/shadow-maint/shadow.git /tmp/shadow-test - - - name: Copy test topology for Fedora CI - how: shell - script: - - cp tests/mhc-fedora-ci.yaml /tmp/shadow-test/tests/system - - - name: Install test dependencies - how: shell - script: - - pip3 install -r /tmp/shadow-test/tests/system/requirements.txt - -execute: - how: tmt - duration: 30m - script: | - cd /tmp/shadow-test/tests/system - pytest --mh-config=mhc-fedora-ci.yaml --mh-lazy-ssh -v diff --git a/shadow-4.13-newidmap-support-passing-pid-as-fd.patch b/shadow-4.13-newidmap-support-passing-pid-as-fd.patch new file mode 100644 index 0000000..37e0d41 --- /dev/null +++ b/shadow-4.13-newidmap-support-passing-pid-as-fd.patch @@ -0,0 +1,441 @@ +From 6974df39a708abf8bafbdfa2b7827e0f70f874cb Mon Sep 17 00:00:00 2001 +From: Serge Hallyn +Date: Mon, 6 Feb 2023 22:49:42 -0600 +Subject: [PATCH] newuidmap and newgidmap: support passing pid as fd + +Closes #635 + +newuidmap and newgidmap currently take an integner pid as +the first argument, determining the process id on which to +act. Accept also "fd:N", where N must be an open file +descriptor to the /proc/pid directory for the process to +act upon. This way, if you + +exec 10 +--- + lib/get_pid.c | 51 +++++++++++++++++++++++++++++++++++++++++++++ + lib/prototypes.h | 2 ++ + man/newgidmap.1.xml | 11 ++++++++++ + man/newuidmap.1.xml | 11 ++++++++++ + src/newgidmap.c | 41 ++++++++++++++---------------------- + src/newuidmap.c | 40 +++++++++++++---------------------- + 6 files changed, 106 insertions(+), 50 deletions(-) + +diff --git a/lib/get_pid.c b/lib/get_pid.c +index 10184bf0..ab91d158 100644 +--- a/lib/get_pid.c ++++ b/lib/get_pid.c +@@ -10,6 +10,9 @@ + + #include "prototypes.h" + #include "defines.h" ++#include ++#include ++#include + + int get_pid (const char *pidstr, pid_t *pid) + { +@@ -29,3 +32,51 @@ int get_pid (const char *pidstr, pid_t *pid) + return 1; + } + ++/* ++ * If use passed in fd:4 as an argument, then return the ++ * value '4', the fd to use. ++ */ ++int get_pidfd_from_fd(const char *pidfdstr) ++{ ++ long long int val; ++ char *endptr; ++ ++ errno = 0; ++ val = strtoll (pidfdstr, &endptr, 10); ++ if ( ('\0' == *pidfdstr) ++ || ('\0' != *endptr) ++ || (ERANGE == errno) ++ || (/*@+longintegral@*/val != (pid_t)val)/*@=longintegral@*/) { ++ return 0; ++ } ++ ++ return (int)val; ++} ++ ++int open_pidfd(const char *pidstr) ++{ ++ int proc_dir_fd; ++ int written; ++ char proc_dir_name[32]; ++ pid_t target; ++ ++ if (get_pid(pidstr, &target) == 0) ++ return -ENOENT; ++ ++ /* max string length is 6 + 10 + 1 + 1 = 18, allocate 32 bytes */ ++ written = snprintf(proc_dir_name, sizeof(proc_dir_name), "/proc/%u/", ++ target); ++ if ((written <= 0) || ((size_t)written >= sizeof(proc_dir_name))) { ++ fprintf(stderr, "snprintf of proc path failed for %u: %s\n", ++ target, strerror(errno)); ++ return -EINVAL; ++ } ++ ++ proc_dir_fd = open(proc_dir_name, O_DIRECTORY); ++ if (proc_dir_fd < 0) { ++ fprintf(stderr, _("Could not open proc directory for target %u: %s\n"), ++ target, strerror(errno)); ++ return -EINVAL; ++ } ++ return proc_dir_fd; ++} +diff --git a/lib/prototypes.h b/lib/prototypes.h +index 400d5b97..21df6f61 100644 +--- a/lib/prototypes.h ++++ b/lib/prototypes.h +@@ -160,6 +160,8 @@ extern int getlong (const char *numstr, /*@out@*/long int *result); + + /* get_pid.c */ + extern int get_pid (const char *pidstr, pid_t *pid); ++extern int get_pidfd_from_fd(const char *pidfdstr); ++extern int open_pidfd(const char *pidstr); + + /* getrange */ + extern int getrange (const char *range, +diff --git a/man/newgidmap.1.xml b/man/newgidmap.1.xml +index e4ebc69e..9b7683eb 100644 +--- a/man/newgidmap.1.xml ++++ b/man/newgidmap.1.xml +@@ -116,6 +116,17 @@ + + Note that newgidmap may be used only once for a given process. + ++ ++ Instead of an integer process id, the first argument may be ++ specified as fd:N, where the integer N ++ is the file descriptor number for the calling process's opened ++ file for /proc/[pid[. In this case, ++ newgidmap will use ++ openat2 ++ to open the gid_map file under that ++ directory, avoiding a TOCTTOU in case the process exits and ++ the pid is immediately reused. ++ + + + +diff --git a/man/newuidmap.1.xml b/man/newuidmap.1.xml +index f5cb5b48..ca917a77 100644 +--- a/man/newuidmap.1.xml ++++ b/man/newuidmap.1.xml +@@ -116,6 +116,17 @@ + + Note that newuidmap may be used only once for a given process. + ++ ++ Instead of an integer process id, the first argument may be ++ specified as fd:N, where the integer N ++ is the file descriptor number for the calling process's opened ++ file for /proc/[pid[. In this case, ++ newuidmap will use ++ openat2 ++ to open the uid_map file under that ++ directory, avoiding a TOCTTOU in case the process exits and ++ the pid is immediately reused. ++ + + + +diff --git a/src/newgidmap.c b/src/newgidmap.c +index 01d0fe90..d6d29725 100644 +--- a/src/newgidmap.c ++++ b/src/newgidmap.c +@@ -69,7 +69,7 @@ static void verify_ranges(struct passwd *pw, int ranges, + + static void usage(void) + { +- fprintf(stderr, _("usage: %s [ ] ... \n"), Prog); ++ fprintf(stderr, _("usage: %s [] [ ] ... \n"), Prog); + exit(EXIT_FAILURE); + } + +@@ -143,15 +143,12 @@ out: + */ + int main(int argc, char **argv) + { +- char proc_dir_name[32]; + char *target_str; +- pid_t target; + int proc_dir_fd; + int ranges; + struct map_range *mappings; + struct stat st; + struct passwd *pw; +- int written; + bool allow_setgroups = false; + + Prog = Basename (argv[0]); +@@ -168,25 +165,19 @@ int main(int argc, char **argv) + /* Find the process that needs its user namespace + * gid mapping set. + */ +- target_str = argv[1]; +- if (!get_pid(target_str, &target)) +- usage(); + +- /* max string length is 6 + 10 + 1 + 1 = 18, allocate 32 bytes */ +- written = snprintf(proc_dir_name, sizeof(proc_dir_name), "/proc/%u/", +- target); +- if ((written <= 0) || (written >= sizeof(proc_dir_name))) { +- fprintf(stderr, "%s: snprintf of proc path failed: %s\n", +- Prog, strerror(errno)); +- } +- +- proc_dir_fd = open(proc_dir_name, O_DIRECTORY); +- if (proc_dir_fd < 0) { +- fprintf(stderr, _("%s: Could not open proc directory for target %u\n"), +- Prog, target); +- return EXIT_FAILURE; ++ target_str = argv[1]; ++ if (strlen(target_str) > 3 && strncmp(target_str, "fd:", 3) == 0) { ++ /* the user passed in a /proc/pid fd for the process */ ++ target_str = &target_str[3]; ++ proc_dir_fd = get_pidfd_from_fd(target_str); ++ if (proc_dir_fd < 0) ++ usage(); ++ } else { ++ proc_dir_fd = open_pidfd(target_str); ++ if (proc_dir_fd < 0) ++ usage(); + } +- + /* Who am i? */ + pw = get_my_pwent (); + if (NULL == pw) { +@@ -200,8 +191,8 @@ int main(int argc, char **argv) + + /* Get the effective uid and effective gid of the target process */ + if (fstat(proc_dir_fd, &st) < 0) { +- fprintf(stderr, _("%s: Could not stat directory for target %u\n"), +- Prog, target); ++ fprintf(stderr, _("%s: Could not stat directory for process\n"), ++ Prog); + return EXIT_FAILURE; + } + +@@ -213,8 +204,8 @@ int main(int argc, char **argv) + (!getdef_bool("GRANT_AUX_GROUP_SUBIDS") && (getgid() != pw->pw_gid)) || + (pw->pw_uid != st.st_uid) || + (getgid() != st.st_gid)) { +- fprintf(stderr, _( "%s: Target %u is owned by a different user: uid:%lu pw_uid:%lu st_uid:%lu, gid:%lu pw_gid:%lu st_gid:%lu\n" ), +- Prog, target, ++ fprintf(stderr, _( "%s: Target process is owned by a different user: uid:%lu pw_uid:%lu st_uid:%lu, gid:%lu pw_gid:%lu st_gid:%lu\n" ), ++ Prog, + (unsigned long int)getuid(), (unsigned long int)pw->pw_uid, (unsigned long int)st.st_uid, + (unsigned long int)getgid(), (unsigned long int)pw->pw_gid, (unsigned long int)st.st_gid); + return EXIT_FAILURE; +diff --git a/src/newuidmap.c b/src/newuidmap.c +index e8798409..e99655c9 100644 +--- a/src/newuidmap.c ++++ b/src/newuidmap.c +@@ -64,7 +64,7 @@ static void verify_ranges(struct passwd *pw, int ranges, + + static void usage(void) + { +- fprintf(stderr, _("usage: %s [ ] ... \n"), Prog); ++ fprintf(stderr, _("usage: %s [|fd:] [ ] ... \n"), Prog); + exit(EXIT_FAILURE); + } + +@@ -73,15 +73,12 @@ static void usage(void) + */ + int main(int argc, char **argv) + { +- char proc_dir_name[32]; + char *target_str; +- pid_t target; + int proc_dir_fd; + int ranges; + struct map_range *mappings; + struct stat st; + struct passwd *pw; +- int written; + + Prog = Basename (argv[0]); + log_set_progname(Prog); +@@ -94,26 +91,20 @@ int main(int argc, char **argv) + if (argc < 2) + usage(); + ++ target_str = argv[1]; + /* Find the process that needs its user namespace + * uid mapping set. + */ +- target_str = argv[1]; +- if (!get_pid(target_str, &target)) +- usage(); +- +- /* max string length is 6 + 10 + 1 + 1 = 18, allocate 32 bytes */ +- written = snprintf(proc_dir_name, sizeof(proc_dir_name), "/proc/%u/", +- target); +- if ((written <= 0) || (written >= sizeof(proc_dir_name))) { +- fprintf(stderr, "%s: snprintf of proc path failed: %s\n", +- Prog, strerror(errno)); +- } +- +- proc_dir_fd = open(proc_dir_name, O_DIRECTORY); +- if (proc_dir_fd < 0) { +- fprintf(stderr, _("%s: Could not open proc directory for target %u\n"), +- Prog, target); +- return EXIT_FAILURE; ++ if (strlen(target_str) > 3 && strncmp(target_str, "fd:", 3) == 0) { ++ /* the user passed in a /proc/pid fd for the process */ ++ target_str = &target_str[3]; ++ proc_dir_fd = get_pidfd_from_fd(target_str); ++ if (proc_dir_fd < 0) ++ usage(); ++ } else { ++ proc_dir_fd = open_pidfd(target_str); ++ if (proc_dir_fd < 0) ++ usage(); + } + + /* Who am i? */ +@@ -129,8 +120,7 @@ int main(int argc, char **argv) + + /* Get the effective uid and effective gid of the target process */ + if (fstat(proc_dir_fd, &st) < 0) { +- fprintf(stderr, _("%s: Could not stat directory for target %u\n"), +- Prog, target); ++ fprintf(stderr, _("%s: Could not stat directory for target process\n"), Prog); + return EXIT_FAILURE; + } + +@@ -142,8 +132,8 @@ int main(int argc, char **argv) + (!getdef_bool("GRANT_AUX_GROUP_SUBIDS") && (getgid() != pw->pw_gid)) || + (pw->pw_uid != st.st_uid) || + (getgid() != st.st_gid)) { +- fprintf(stderr, _( "%s: Target process %u is owned by a different user: uid:%lu pw_uid:%lu st_uid:%lu, gid:%lu pw_gid:%lu st_gid:%lu\n" ), +- Prog, target, ++ fprintf(stderr, _( "%s: Target process is owned by a different user: uid:%lu pw_uid:%lu st_uid:%lu, gid:%lu pw_gid:%lu st_gid:%lu\n" ), ++ Prog, + (unsigned long int)getuid(), (unsigned long int)pw->pw_uid, (unsigned long int)st.st_uid, + (unsigned long int)getgid(), (unsigned long int)pw->pw_gid, (unsigned long int)st.st_gid); + return EXIT_FAILURE; +-- +2.39.2 + +From 7ff33fae6f9cd79c0e012671c37a172e9a681d0b Mon Sep 17 00:00:00 2001 +From: Serge Hallyn +Date: Fri, 24 Feb 2023 13:52:32 -0600 +Subject: [PATCH] get_pidfd_from_fd: return -1 on error, not 0 + +Fixes: 6974df39a: newuidmap and newgidmap: support passing pid as fd +Signed-off-by: Serge Hallyn +--- + lib/get_pid.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/lib/get_pid.c b/lib/get_pid.c +index ab91d158..5b6d9da4 100644 +--- a/lib/get_pid.c ++++ b/lib/get_pid.c +@@ -35,6 +35,7 @@ int get_pid (const char *pidstr, pid_t *pid) + /* + * If use passed in fd:4 as an argument, then return the + * value '4', the fd to use. ++ * On error, return -1. + */ + int get_pidfd_from_fd(const char *pidfdstr) + { +@@ -47,7 +48,7 @@ int get_pidfd_from_fd(const char *pidfdstr) + || ('\0' != *endptr) + || (ERANGE == errno) + || (/*@+longintegral@*/val != (pid_t)val)/*@=longintegral@*/) { +- return 0; ++ return -1; + } + + return (int)val; +-- +2.39.2 + +From 05e2adf509ba0e3779dae66a276b86927a8e1e0e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Vin=C3=ADcius=20dos=20Santos=20Oliveira?= + +Date: Fri, 24 Feb 2023 18:06:02 -0300 +Subject: [PATCH] Validate fds created by the user + +write_mapping() will do the following: + +openat(proc_dir_fd, map_file, O_WRONLY); + +An attacker could create a directory containing a symlink named +"uid_map" pointing to any file owned by root, and thus allow him to +overwrite any root-owned file. +--- + lib/get_pid.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/lib/get_pid.c b/lib/get_pid.c +index 5b6d9da4..8e5e6014 100644 +--- a/lib/get_pid.c ++++ b/lib/get_pid.c +@@ -41,6 +41,8 @@ int get_pidfd_from_fd(const char *pidfdstr) + { + long long int val; + char *endptr; ++ struct stat st; ++ dev_t proc_st_dev, proc_st_rdev; + + errno = 0; + val = strtoll (pidfdstr, &endptr, 10); +@@ -51,6 +53,21 @@ int get_pidfd_from_fd(const char *pidfdstr) + return -1; + } + ++ if (stat("/proc/self/uid_map", &st) < 0) { ++ return -1; ++ } ++ ++ proc_st_dev = st.st_dev; ++ proc_st_rdev = st.st_rdev; ++ ++ if (fstat(val, &st) < 0) { ++ return -1; ++ } ++ ++ if (st.st_dev != proc_st_dev || st.st_rdev != proc_st_rdev) { ++ return -1; ++ } ++ + return (int)val; + } + +-- +2.39.2 + diff --git a/shadow-4.15.0-account-tools-setuid.patch b/shadow-4.15.0-account-tools-setuid.patch new file mode 100644 index 0000000..d162487 --- /dev/null +++ b/shadow-4.15.0-account-tools-setuid.patch @@ -0,0 +1,380 @@ +diff -up shadow-4.15.0/src/chpasswd.c.account-tools-setuid shadow-4.15.0/src/chpasswd.c +--- shadow-4.15.0/src/chpasswd.c.account-tools-setuid 2024-03-08 22:27:04.000000000 +0100 ++++ shadow-4.15.0/src/chpasswd.c 2024-03-11 11:21:57.561150382 +0100 +@@ -443,9 +443,11 @@ int main (int argc, char **argv) + char *cp; + const char *salt; + ++#ifdef ACCT_TOOLS_SETUID + #ifdef USE_PAM + bool use_pam = true; + #endif /* USE_PAM */ ++#endif /* ACCT_TOOLS_SETUID */ + + int errors = 0; + int line = 0; +@@ -469,19 +471,23 @@ int main (int argc, char **argv) + process_root_flag ("-R", argc, argv); + prefix = process_prefix_flag ("-P", argc, argv); + ++#ifdef ACCT_TOOLS_SETUID + #ifdef USE_PAM + if (md5flg || eflg || cflg || prefix[0]) { + use_pam = false; + } + #endif /* USE_PAM */ ++#endif /* ACCT_TOOLS_SETUID */ + + OPENLOG (Prog); + + check_perms (); + ++#ifdef ACCT_TOOLS_SETUID + #ifdef USE_PAM + if (!use_pam) + #endif /* USE_PAM */ ++#endif /* ACCT_TOOLS_SETUID */ + { + is_shadow_pwd = spw_file_present (); + +@@ -543,6 +549,7 @@ int main (int argc, char **argv) + } + newpwd = cp; + ++#ifdef ACCT_TOOLS_SETUID + #ifdef USE_PAM + if (use_pam) { + if (do_pam_passwd_non_interactive (Prog, name, newpwd) != 0) { +@@ -553,6 +560,7 @@ int main (int argc, char **argv) + } + } else + #endif /* USE_PAM */ ++#endif /* ACCT_TOOLS_SETUID */ + { + const struct spwd *sp; + struct spwd newsp; +@@ -672,9 +680,11 @@ int main (int argc, char **argv) + * password database. + */ + if (0 != errors) { ++#ifdef ACCT_TOOLS_SETUID + #ifdef USE_PAM + if (!use_pam) + #endif /* USE_PAM */ ++#endif /* ACCT_TOOLS_SETUID */ + { + fprintf (stderr, + _("%s: error detected, changes ignored\n"), +@@ -683,9 +693,11 @@ int main (int argc, char **argv) + fail_exit (1); + } + ++#ifdef ACCT_TOOLS_SETUID + #ifdef USE_PAM + if (!use_pam) + #endif /* USE_PAM */ ++#endif /* ACCT_TOOLS_SETUID */ + { + /* Save the changes */ + close_files (); +diff -up shadow-4.15.0/src/groupmems.c.account-tools-setuid shadow-4.15.0/src/groupmems.c +--- shadow-4.15.0/src/groupmems.c.account-tools-setuid 2024-03-08 22:27:04.000000000 +0100 ++++ shadow-4.15.0/src/groupmems.c 2024-03-11 11:16:18.365408572 +0100 +@@ -14,9 +14,11 @@ + #include + #include + #include ++#ifdef ACCT_TOOLS_SETUID + #ifdef USE_PAM + #include "pam_defs.h" + #endif /* USE_PAM */ ++#endif /* ACCT_TOOLS_SETUID */ + #include + + #include "alloc.h" +@@ -430,6 +432,7 @@ static void process_flags (int argc, cha + static void check_perms (void) + { + if (!list) { ++#ifdef ACCT_TOOLS_SETUID + #ifdef USE_PAM + pam_handle_t *pamh = NULL; + int retval; +@@ -463,7 +466,8 @@ static void check_perms (void) + fail_exit (1); + } + (void) pam_end (pamh, retval); +-#endif ++#endif /* USE_PAM */ ++#endif /* ACCT_TOOLS_SETUID */ + } + } + +diff -up shadow-4.15.0/src/newusers.c.account-tools-setuid shadow-4.15.0/src/newusers.c +--- shadow-4.15.0/src/newusers.c.account-tools-setuid 2024-03-08 22:27:04.000000000 +0100 ++++ shadow-4.15.0/src/newusers.c 2024-03-11 11:20:07.198909046 +0100 +@@ -59,6 +59,7 @@ + static const char Prog[] = "newusers"; + + static bool rflg = false; /* create a system account */ ++#ifndef ACCT_TOOLS_SETUID + #ifndef USE_PAM + static /*@null@*//*@observer@*/char *crypt_method = NULL; + #define cflg (NULL != crypt_method) +@@ -75,6 +76,7 @@ static long bcrypt_rounds = 13; + static long yescrypt_cost = 5; + #endif /* USE_YESCRYPT */ + #endif /* !USE_PAM */ ++#endif /* !ACCT_TOOLS_SETUID */ + + static bool is_shadow; + #ifdef SHADOWGRP +@@ -97,9 +99,11 @@ NORETURN static void fail_exit (int); + static int add_group (const char *, const char *, gid_t *, gid_t); + static int get_user_id (const char *, uid_t *); + static int add_user (const char *, uid_t, gid_t); ++#ifndef ACCT_TOOLS_SETUID + #ifndef USE_PAM + static int update_passwd (struct passwd *, const char *); + #endif /* !USE_PAM */ ++#endif /* !ACCT_TOOLS_SETUID */ + static int add_passwd (struct passwd *, const char *); + static void process_flags (int argc, char **argv); + static void check_flags (void); +@@ -121,6 +125,7 @@ static void usage (int status) + "Options:\n"), + Prog); + (void) fputs (_(" -b, --badname allow bad names\n"), usageout); ++#ifndef ACCT_TOOLS_SETUID + #ifndef USE_PAM + (void) fprintf (usageout, + _(" -c, --crypt-method METHOD the crypt method (one of %s)\n"), +@@ -136,9 +141,11 @@ static void usage (int status) + #endif + ); + #endif /* !USE_PAM */ ++#endif /* !ACCT_TOOLS_SETUID */ + (void) fputs (_(" -h, --help display this help message and exit\n"), usageout); + (void) fputs (_(" -r, --system create system accounts\n"), usageout); + (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout); ++#ifndef ACCT_TOOLS_SETUID + #ifndef USE_PAM + #if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT) + (void) fputs (_(" -s, --sha-rounds number of rounds for the SHA, BCRYPT\n" +@@ -146,6 +153,7 @@ static void usage (int status) + usageout); + #endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */ + #endif /* !USE_PAM */ ++#endif /* !ACCT_TOOLS_SETUID */ + (void) fputs ("\n", usageout); + + exit (status); +@@ -405,6 +413,7 @@ static int add_user (const char *name, u + return (pw_update (&pwent) == 0) ? -1 : 0; + } + ++#ifndef ACCT_TOOLS_SETUID + #ifndef USE_PAM + /* + * update_passwd - update the password in the passwd entry +@@ -457,6 +466,7 @@ static int update_passwd (struct passwd + return 0; + } + #endif /* !USE_PAM */ ++#endif /* !ACCT_TOOLS_SETUID */ + + /* + * add_passwd - add or update the encrypted password +@@ -465,10 +475,13 @@ static int add_passwd (struct passwd *pw + { + const struct spwd *sp; + struct spwd spent; ++#ifndef ACCT_TOOLS_SETUID + #ifndef USE_PAM + char *cp; + #endif /* !USE_PAM */ ++#endif /* !ACCT_TOOLS_SETUID */ + ++#ifndef ACCT_TOOLS_SETUID + #ifndef USE_PAM + void *crypt_arg = NULL; + if (NULL != crypt_method) { +@@ -505,13 +518,14 @@ static int add_passwd (struct passwd *pw + return update_passwd (pwd, password); + } + #endif /* USE_PAM */ ++#endif /* !ACCT_TOOLS_SETUID */ + + /* + * Do the first and easiest shadow file case. The user already + * exists in the shadow password file. + */ + sp = spw_locate (pwd->pw_name); +-#ifndef USE_PAM ++#if !defined(ACCT_TOOLS_SETUID) && !defined(USE_PAM) + if (NULL != sp) { + spent = *sp; + if ( (NULL != crypt_method) +@@ -547,7 +561,7 @@ static int add_passwd (struct passwd *pw + if (strcmp (pwd->pw_passwd, "x") != 0) { + return update_passwd (pwd, password); + } +-#else /* USE_PAM */ ++#else /* !ACCT_TOOLS_SETUID && !USE_PAM */ + /* + * If there is already a shadow entry, do not touch it. + * If there is already a passwd entry with a password, do not +@@ -558,14 +572,14 @@ static int add_passwd (struct passwd *pw + || (strcmp (pwd->pw_passwd, "x") != 0)) { + return 0; + } +-#endif /* USE_PAM */ ++#endif /* !ACCT_TOOLS_SETUID && !USE_PAM */ + + /* + * Now the really hard case - I need to create an entirely new + * shadow password file entry. + */ + spent.sp_namp = pwd->pw_name; +-#ifndef USE_PAM ++#if !defined(ACCT_TOOLS_SETUID) && !defined(USE_PAM) + if ((crypt_method != NULL) && (0 == strcmp(crypt_method, "NONE"))) { + spent.sp_pwdp = (char *)password; + } else { +@@ -610,35 +624,41 @@ static int add_passwd (struct passwd *pw + static void process_flags (int argc, char **argv) + { + int c; ++#ifndef ACCT_TOOLS_SETUID + #ifndef USE_PAM + #if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT) + int bad_s; + #endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */ + #endif /* !USE_PAM */ ++#endif /* !ACCT_TOOLS_SETUID */ + static struct option long_options[] = { + {"badname", no_argument, NULL, 'b'}, ++#ifndef ACCT_TOOLS_SETUID + #ifndef USE_PAM + {"crypt-method", required_argument, NULL, 'c'}, + #endif /* !USE_PAM */ ++#endif /* !ACCT_TOOLS_SETUID */ + {"help", no_argument, NULL, 'h'}, + {"system", no_argument, NULL, 'r'}, + {"root", required_argument, NULL, 'R'}, ++#ifndef ACCT_TOOLS_SETUID + #ifndef USE_PAM + #if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT) + {"sha-rounds", required_argument, NULL, 's'}, + #endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */ + #endif /* !USE_PAM */ ++#endif /* !ACCT_TOOLS_SETUID */ + {NULL, 0, NULL, '\0'} + }; + + while ((c = getopt_long (argc, argv, +-#ifndef USE_PAM ++#if !defined(ACCT_TOOLS_SETUID) && !defined(USE_PAM) + #if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT) + "c:bhrs:", + #else /* !USE_SHA_CRYPT && !USE_BCRYPT && !USE_YESCRYPT */ + "c:bhr", + #endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */ +-#else /* USE_PAM */ ++#else /* !ACCT_TOOLS_SETUID && !USE_PAM */ + "bhr", + #endif + long_options, NULL)) != -1) { +@@ -646,11 +666,13 @@ static void process_flags (int argc, cha + case 'b': + allow_bad_names = true; + break; ++#ifndef ACCT_TOOLS_SETUID + #ifndef USE_PAM + case 'c': + crypt_method = optarg; + break; + #endif /* !USE_PAM */ ++#endif /* !ACCT_TOOLS_SETUID */ + case 'h': + usage (EXIT_SUCCESS); + break; +@@ -659,6 +681,7 @@ static void process_flags (int argc, cha + break; + case 'R': /* no-op, handled in process_root_flag () */ + break; ++#ifndef ACCT_TOOLS_SETUID + #ifndef USE_PAM + #if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT) + case 's': +@@ -698,6 +721,7 @@ static void process_flags (int argc, cha + break; + #endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */ + #endif /* !USE_PAM */ ++#endif /* !ACCT_TOOLS_SETUID */ + default: + usage (EXIT_FAILURE); + break; +@@ -730,6 +754,7 @@ static void process_flags (int argc, cha + */ + static void check_flags (void) + { ++#ifndef ACCT_TOOLS_SETUID + #ifndef USE_PAM + #if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT) + if (sflg && !cflg) { +@@ -762,6 +787,7 @@ static void check_flags (void) + } + } + #endif /* !USE_PAM */ ++#endif /* !ACCT_TOOLS_SETUID */ + } + + /* +@@ -1052,12 +1078,14 @@ int main (int argc, char **argv) + int line = 0; + uid_t uid; + gid_t gid; ++#ifdef ACCT_TOOLS_SETUID + #ifdef USE_PAM + int *lines = NULL; + char **usernames = NULL; + char **passwords = NULL; + unsigned int nusers = 0; + #endif /* USE_PAM */ ++#endif /* ACCT_TOOLS_SETUID */ + + log_set_progname(Prog); + log_set_logfd(stderr); +@@ -1195,6 +1223,7 @@ int main (int argc, char **argv) + } + newpw = *pw; + ++#ifdef ACCT_TOOLS_SETUID + #ifdef USE_PAM + /* keep the list of user/password for later update by PAM */ + nusers++; +@@ -1211,6 +1240,7 @@ int main (int argc, char **argv) + usernames[nusers-1] = strdup (fields[0]); + passwords[nusers-1] = strdup (fields[1]); + #endif /* USE_PAM */ ++#endif /* ACCT_TOOLS_SETUID */ + if (add_passwd (&newpw, fields[1]) != 0) { + fprintf (stderr, + _("%s: line %d: can't update password\n"), +@@ -1327,6 +1357,7 @@ int main (int argc, char **argv) + nscd_flush_cache ("group"); + sssd_flush_cache (SSSD_DB_PASSWD | SSSD_DB_GROUP); + ++#ifdef ACCT_TOOLS_SETUID + #ifdef USE_PAM + unsigned int i; + /* Now update the passwords using PAM */ +@@ -1339,6 +1370,7 @@ int main (int argc, char **argv) + } + } + #endif /* USE_PAM */ ++#endif /* ACCT_TOOLS_SETUID */ + + exit (EXIT_SUCCESS); + } diff --git a/shadow-4.15.0-date-parsing.patch b/shadow-4.15.0-date-parsing.patch new file mode 100644 index 0000000..272d2df --- /dev/null +++ b/shadow-4.15.0-date-parsing.patch @@ -0,0 +1,69 @@ +Index: shadow-4.5/lib/getdate.y +=================================================================== +--- shadow-4.5.orig/lib/getdate.y ++++ shadow-4.5/lib/getdate.y +@@ -152,6 +152,7 @@ static int yyHaveDay; + static int yyHaveRel; + static int yyHaveTime; + static int yyHaveZone; ++static int yyHaveYear; + static int yyTimezone; + static int yyDay; + static int yyHour; +@@ -293,18 +294,21 @@ date : tUNUMBER '/' tUNUMBER { + yyDay = $3; + yyYear = $5; + } ++ yyHaveYear++; + } + | tUNUMBER tSNUMBER tSNUMBER { + /* ISO 8601 format. yyyy-mm-dd. */ + yyYear = $1; + yyMonth = -$2; + yyDay = -$3; ++ yyHaveYear++; + } + | tUNUMBER tMONTH tSNUMBER { + /* e.g. 17-JUN-1992. */ + yyDay = $1; + yyMonth = $2; + yyYear = -$3; ++ yyHaveYear++; + } + | tMONTH tUNUMBER { + yyMonth = $1; +@@ -314,6 +318,7 @@ date : tUNUMBER '/' tUNUMBER { + yyMonth = $1; + yyDay = $2; + yyYear = $4; ++ yyHaveYear++; + } + | tUNUMBER tMONTH { + yyMonth = $2; +@@ -323,6 +328,7 @@ date : tUNUMBER '/' tUNUMBER { + yyMonth = $2; + yyDay = $1; + yyYear = $3; ++ yyHaveYear++; + } + ; + +@@ -395,7 +401,8 @@ relunit : tUNUMBER tYEAR_UNIT { + + number : tUNUMBER + { +- if ((yyHaveTime != 0) && (yyHaveDate != 0) && (yyHaveRel == 0)) ++ if ((yyHaveTime != 0 || $1 >= 100) && !yyHaveYear ++ && (yyHaveDate != 0) && (yyHaveRel == 0)) + yyYear = $1; + else + { +@@ -802,7 +809,7 @@ yylex (void) + return LookupWord (buff); + } + if (c != '(') +- return *yyInput++; ++ return (unsigned char)*yyInput++; + Count = 0; + do + { diff --git a/shadow-4.15.0-getdef-spurious-error.patch b/shadow-4.15.0-getdef-spurious-error.patch new file mode 100644 index 0000000..9cec295 --- /dev/null +++ b/shadow-4.15.0-getdef-spurious-error.patch @@ -0,0 +1,137 @@ +From ead55e9ba8958504e23e29545f90c4dd925c7462 Mon Sep 17 00:00:00 2001 +From: Serge Hallyn +Date: Wed, 20 Mar 2024 17:39:46 -0500 +Subject: [PATCH] getdef: avoid spurious error messages about unknown + configuration options + +def_find can return NULL for unset, not just unknown, config options. So +move the decision of whether to log an error message about an unknown config +option back into def_find, which knows the difference. Only putdef_str() +will pass a char* srcfile to def_find, so only calls from putdef_str will +cause the message, which was the original intent of fa68441bc4be8. + +closes #967 + +fixes: fa68441bc4be8 ("Improve the login.defs unknown item error message") +Signed-off-by: Serge Hallyn +--- + lib/getdef.c | 30 ++++++++++++++++-------------- + 1 file changed, 16 insertions(+), 14 deletions(-) + +diff --git a/lib/getdef.c b/lib/getdef.c +index 4d4d4e19..ef2ae1f0 100644 +--- a/lib/getdef.c ++++ b/lib/getdef.c +@@ -176,7 +176,7 @@ static const char* def_fname = LOGINDEFS; /* login config defs file */ + static bool def_loaded = false; /* are defs already loaded? */ + + /* local function prototypes */ +-static /*@observer@*/ /*@null@*/struct itemdef *def_find (const char *); ++static /*@observer@*/ /*@null@*/struct itemdef *def_find (const char *, const char *); + static void def_load (void); + + +@@ -195,7 +195,7 @@ static void def_load (void); + def_load (); + } + +- d = def_find (item); ++ d = def_find (item, NULL); + return (NULL == d) ? NULL : d->value; + } + +@@ -214,7 +214,7 @@ bool getdef_bool (const char *item) + def_load (); + } + +- d = def_find (item); ++ d = def_find (item, NULL); + if ((NULL == d) || (NULL == d->value)) { + return false; + } +@@ -240,7 +240,7 @@ int getdef_num (const char *item, int dflt) + def_load (); + } + +- d = def_find (item); ++ d = def_find (item, NULL); + if ((NULL == d) || (NULL == d->value)) { + return dflt; + } +@@ -275,7 +275,7 @@ unsigned int getdef_unum (const char *item, unsigned int dflt) + def_load (); + } + +- d = def_find (item); ++ d = def_find (item, NULL); + if ((NULL == d) || (NULL == d->value)) { + return dflt; + } +@@ -310,7 +310,7 @@ long getdef_long (const char *item, long dflt) + def_load (); + } + +- d = def_find (item); ++ d = def_find (item, NULL); + if ((NULL == d) || (NULL == d->value)) { + return dflt; + } +@@ -342,7 +342,7 @@ unsigned long getdef_ulong (const char *item, unsigned long dflt) + def_load (); + } + +- d = def_find (item); ++ d = def_find (item, NULL); + if ((NULL == d) || (NULL == d->value)) { + return dflt; + } +@@ -375,12 +375,9 @@ int putdef_str (const char *name, const char *value, const char *srcfile) + * Locate the slot to save the value. If this parameter + * is unknown then "def_find" will print an err message. + */ +- d = def_find (name); +- if (NULL == d) { +- if (NULL != srcfile) +- SYSLOG ((LOG_CRIT, "shadow: unknown configuration item '%s' in '%s'", name, srcfile)); ++ d = def_find (name, srcfile); ++ if (NULL == d) + return -1; +- } + + /* + * Save off the value. +@@ -404,9 +401,12 @@ int putdef_str (const char *name, const char *value, const char *srcfile) + * + * Search through a table of configurable items to locate the + * specified configuration option. ++ * ++ * If srcfile is not NULL, and the item is not found, then report an error saying ++ * the unknown item was used in this file. + */ + +-static /*@observer@*/ /*@null@*/struct itemdef *def_find (const char *name) ++static /*@observer@*/ /*@null@*/struct itemdef *def_find (const char *name, const char *srcfile) + { + struct itemdef *ptr; + +@@ -432,6 +432,8 @@ static /*@observer@*/ /*@null@*/struct itemdef *def_find (const char *name) + fprintf (shadow_logfd, + _("configuration error - unknown item '%s' (notify administrator)\n"), + name); ++ if (srcfile != NULL) ++ SYSLOG ((LOG_CRIT, "shadow: unknown configuration item '%s' in '%s'", name, srcfile)); + + out: + return NULL; +@@ -610,7 +612,7 @@ int main (int argc, char **argv) + def_load (); + + for (i = 0; i < NUMDEFS; ++i) { +- d = def_find (def_table[i].name); ++ d = def_find (def_table[i].name, NULL); + if (NULL == d) { + printf ("error - lookup '%s' failed\n", + def_table[i].name); +-- +2.44.0 + diff --git a/shadow-4.15.0-manfix.patch b/shadow-4.15.0-manfix.patch new file mode 100644 index 0000000..34e62f9 --- /dev/null +++ b/shadow-4.15.0-manfix.patch @@ -0,0 +1,162 @@ +diff -up shadow-4.15.0/man/groupmems.8.xml.manfix shadow-4.15.0/man/groupmems.8.xml +--- shadow-4.15.0/man/groupmems.8.xml.manfix 2023-05-26 04:56:11.000000000 +0200 ++++ shadow-4.15.0/man/groupmems.8.xml 2024-02-09 10:42:20.337036378 +0100 +@@ -156,20 +156,10 @@ + + SETUP + +- The groupmems executable should be in mode +- 2710 as user root and in group +- groups. The system administrator can add users to +- group groups to allow or disallow them using the +- groupmems utility to manage their own group +- membership list. ++ In this operating system the groupmems executable ++ is not setuid and regular users cannot use it to manipulate ++ the membership of their own group. + +- +- +- $ groupadd -r groups +- $ chmod 2710 groupmems +- $ chown root:groups groupmems +- $ groupmems -g groups -a gk4 +- + + + +diff -up shadow-4.15.0/man/ja/man5/login.defs.5.manfix shadow-4.15.0/man/ja/man5/login.defs.5 +--- shadow-4.15.0/man/ja/man5/login.defs.5.manfix 2023-03-13 21:58:56.000000000 +0100 ++++ shadow-4.15.0/man/ja/man5/login.defs.5 2024-02-09 10:42:20.337036378 +0100 +@@ -123,10 +123,6 @@ 以下の参照表は、 + shadow パスワード機能のどのプログラムが + どのパラメータを使用するかを示したものである。 + .na +-.IP chfn 12 +-CHFN_AUTH CHFN_RESTRICT +-.IP chsh 12 +-CHFN_AUTH + .IP groupadd 12 + GID_MAX GID_MIN + .IP newusers 12 +diff -up shadow-4.15.0/man/login.defs.5.xml.manfix shadow-4.15.0/man/login.defs.5.xml +--- shadow-4.15.0/man/login.defs.5.xml.manfix 2024-01-22 22:36:43.000000000 +0100 ++++ shadow-4.15.0/man/login.defs.5.xml 2024-02-09 10:45:49.014407259 +0100 +@@ -144,6 +144,17 @@ + long numeric parameters is machine-dependent. + + ++ ++ Please note that the parameters in this configuration file control the ++ behavior of the tools from the shadow-utils component. None of these ++ tools uses the PAM mechanism, and the utilities that use PAM (such as the ++ passwd command) should be configured elsewhere. The only values that ++ affect PAM modules are ENCRYPT_METHOD and SHA_CRYPT_MAX_ROUNDS ++ for pam_unix module, FAIL_DELAY for pam_faildelay module, ++ and UMASK for pam_umask module. Refer to ++ pam(8) for more information. ++ ++ + The following configuration items are provided: + + +@@ -240,16 +251,6 @@ + + + +- chfn +- +- +- CHFN_AUTH +- CHFN_RESTRICT +- LOGIN_STRING +- +- +- +- + chgpasswd + + +@@ -276,14 +277,6 @@ + + + +- +- chsh +- +- +- CHSH_AUTH LOGIN_STRING +- +- +- + + + +@@ -352,34 +345,6 @@ + LASTLOG_UID_MAX + + +- +- login +- +- +- CONSOLE +- CONSOLE_GROUPS DEFAULT_HOME +- ENV_HZ ENV_PATH ENV_SUPATH +- ENV_TZ ENVIRON_FILE +- ERASECHAR FAIL_DELAY +- FAILLOG_ENAB +- FAKE_SHELL +- FTMP_FILE +- HUSHLOGIN_FILE +- ISSUE_FILE +- KILLCHAR +- LASTLOG_ENAB LASTLOG_UID_MAX +- LOGIN_RETRIES +- LOGIN_STRING +- LOGIN_TIMEOUT LOG_OK_LOGINS LOG_UNKFAIL_ENAB +- MAIL_CHECK_ENAB MAIL_DIR MAIL_FILE +- MOTD_FILE NOLOGINS_FILE PORTTIME_CHECKS_ENAB +- QUOTAS_ENAB +- TTYGROUP TTYPERM TTYTYPE_FILE +- ULIMIT UMASK +- USERGROUPS_ENAB +- +- +- + + + newgrp / sg +@@ -451,32 +416,6 @@ + + + +- +- su +- +- +- CONSOLE +- CONSOLE_GROUPS DEFAULT_HOME +- ENV_HZ ENVIRON_FILE +- ENV_PATH ENV_SUPATH +- ENV_TZ LOGIN_STRING MAIL_CHECK_ENAB +- MAIL_DIR MAIL_FILE QUOTAS_ENAB +- SULOG_FILE SU_NAME +- SU_WHEEL_ONLY +- SYSLOG_SU_ENAB +- USERGROUPS_ENAB +- +- +- +- +- sulogin +- +- +- ENV_HZ +- ENV_TZ +- +- +- + + useradd + diff --git a/shadow-4.15.1-audit-update.patch b/shadow-4.15.1-audit-update.patch new file mode 100644 index 0000000..a738d60 --- /dev/null +++ b/shadow-4.15.1-audit-update.patch @@ -0,0 +1,2062 @@ +diff -up shadow-4.15.1/lib/audit_help.c.audit-update shadow-4.15.1/lib/audit_help.c +--- shadow-4.15.1/lib/audit_help.c.audit-update 2024-03-01 02:50:52.000000000 +0100 ++++ shadow-4.15.1/lib/audit_help.c 2024-05-20 11:52:05.639758532 +0200 +@@ -48,7 +48,7 @@ void audit_help_open (void) + * This function will log a message to the audit system using a predefined + * message format. Parameter usage is as follows: + * +- * type - type of message: AUDIT_USER_CHAUTHTOK for changing any account ++ * type - type of message: AUDIT_USER_MGMT for changing any account + * attributes. + * pgname - program's name + * op - operation. "adding user", "changing finger info", "deleting group" +@@ -68,6 +68,39 @@ void audit_logger (int type, MAYBE_UNUSE + } + } + ++/* ++ * This function will log a message to the audit system using a predefined ++ * message format. Parameter usage is as follows: ++ * ++ * type - type of message: AUDIT_USER_MGMT for changing any account ++ * attributes. ++ * pgname - program's name ++ * op - operation. "adding user", "changing finger info", "deleting group" ++ * name - user's account or group name. If not available use NULL. ++ * id - uid or gid that the operation is being performed on. This is used ++ * only when user is NULL. ++ * grp - group name associated with event ++ */ ++void audit_logger_with_group (int type, MAYBE_UNUSED const char *pgname, ++ const char *op, const char *name, unsigned int id, ++ const char *grp, shadow_audit_result result) ++{ ++ int len; ++ char enc_group[(GROUP_NAME_MAX_LENGTH*2)+1], buf[1024]; ++ if (audit_fd < 0) { ++ return; ++ } ++ len = strnlen(grp, sizeof(enc_group)/2); ++ if (audit_value_needs_encoding(grp, len)) { ++ snprintf(buf, sizeof(buf), "%s grp=%s", op, ++ audit_encode_value(enc_group, grp, len)); ++ } else { ++ snprintf(buf, sizeof(buf), "%s grp=\"%s\"", op, grp); ++ } ++ audit_log_acct_message (audit_fd, type, NULL, buf, name, id, ++ NULL, NULL, NULL, (int) result); ++} ++ + void audit_logger_message (const char *message, shadow_audit_result result) + { + if (audit_fd < 0) { +diff -up shadow-4.15.1/lib/cleanup_group.c.audit-update shadow-4.15.1/lib/cleanup_group.c +--- shadow-4.15.1/lib/cleanup_group.c.audit-update 2024-03-01 02:50:52.000000000 +0100 ++++ shadow-4.15.1/lib/cleanup_group.c 2024-05-20 11:52:05.639758532 +0200 +@@ -62,7 +62,7 @@ void cleanup_report_mod_group (void *cle + gr_dbname (), + info->action)); + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_ACCT, log_get_progname(), ++ audit_logger (AUDIT_GRP_MGMT, log_get_progname(), + info->audit_msg, + info->name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); +@@ -80,7 +80,7 @@ void cleanup_report_mod_gshadow (void *c + sgr_dbname (), + info->action)); + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_ACCT, log_get_progname(), ++ audit_logger (AUDIT_GRP_MGMT, log_get_progname(), + info->audit_msg, + info->name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); +@@ -101,7 +101,7 @@ void cleanup_report_add_group_group (voi + SYSLOG ((LOG_ERR, "failed to add group %s to %s", name, gr_dbname ())); + #ifdef WITH_AUDIT + audit_logger (AUDIT_ADD_GROUP, log_get_progname(), +- "adding group to /etc/group", ++ "adding-group", + name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + #endif +@@ -120,8 +120,8 @@ void cleanup_report_add_group_gshadow (v + + SYSLOG ((LOG_ERR, "failed to add group %s to %s", name, sgr_dbname ())); + #ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_GROUP, log_get_progname(), +- "adding group to /etc/gshadow", ++ audit_logger (AUDIT_GRP_MGMT, log_get_progname(), ++ "adding-shadow-group", + name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + #endif +@@ -143,8 +143,8 @@ void cleanup_report_del_group_group (voi + "failed to remove group %s from %s", + name, gr_dbname ())); + #ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_GROUP, log_get_progname(), +- "removing group from /etc/group", ++ audit_logger (AUDIT_DEL_GROUP, log_get_progname(), ++ "removing-group", + name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + #endif +@@ -166,8 +166,8 @@ void cleanup_report_del_group_gshadow (v + "failed to remove group %s from %s", + name, sgr_dbname ())); + #ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_GROUP, log_get_progname(), +- "removing group from /etc/gshadow", ++ audit_logger (AUDIT_GRP_MGMT, log_get_progname(), ++ "removing-shadow-group", + name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + #endif +@@ -187,7 +187,7 @@ void cleanup_unlock_group (MAYBE_UNUSED + log_get_progname(), gr_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ())); + #ifdef WITH_AUDIT +- audit_logger_message ("unlocking group file", ++ audit_logger_message ("unlocking-group", + SHADOW_AUDIT_FAILURE); + #endif + } +@@ -207,7 +207,7 @@ void cleanup_unlock_gshadow (MAYBE_UNUSE + log_get_progname(), sgr_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ())); + #ifdef WITH_AUDIT +- audit_logger_message ("unlocking gshadow file", ++ audit_logger_message ("unlocking-gshadow", + SHADOW_AUDIT_FAILURE); + #endif + } +diff -up shadow-4.15.1/lib/cleanup_user.c.audit-update shadow-4.15.1/lib/cleanup_user.c +--- shadow-4.15.1/lib/cleanup_user.c.audit-update 2024-03-01 02:50:52.000000000 +0100 ++++ shadow-4.15.1/lib/cleanup_user.c 2024-05-20 11:52:05.639758532 +0200 +@@ -44,7 +44,7 @@ void cleanup_report_mod_passwd (void *cl + pw_dbname (), + info->action)); + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_ACCT, log_get_progname(), ++ audit_logger (AUDIT_USER_MGMT, log_get_progname(), + info->audit_msg, + info->name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); +@@ -65,7 +65,7 @@ void cleanup_report_add_user_passwd (voi + SYSLOG ((LOG_ERR, "failed to add user %s to %s", name, pw_dbname ())); + #ifdef WITH_AUDIT + audit_logger (AUDIT_ADD_USER, log_get_progname(), +- "adding user to /etc/passwd", ++ "adding-user", + name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + #endif +@@ -84,8 +84,8 @@ void cleanup_report_add_user_shadow (voi + + SYSLOG ((LOG_ERR, "failed to add user %s to %s", name, spw_dbname ())); + #ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_USER, log_get_progname(), +- "adding user to /etc/shadow", ++ audit_logger (AUDIT_USER_MGMT, log_get_progname(), ++ "adding-shadow-user", + name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + #endif +@@ -104,7 +104,7 @@ void cleanup_unlock_passwd (MAYBE_UNUSED + log_get_progname(), pw_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ())); + #ifdef WITH_AUDIT +- audit_logger_message ("unlocking passwd file", ++ audit_logger_message ("unlocking-passwd", + SHADOW_AUDIT_FAILURE); + #endif + } +@@ -123,7 +123,7 @@ void cleanup_unlock_shadow (MAYBE_UNUSED + log_get_progname(), spw_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ())); + #ifdef WITH_AUDIT +- audit_logger_message ("unlocking shadow file", ++ audit_logger_message ("unlocking-shadow", + SHADOW_AUDIT_FAILURE); + #endif + } +diff -up shadow-4.15.1/lib/prototypes.h.audit-update shadow-4.15.1/lib/prototypes.h +--- shadow-4.15.1/lib/prototypes.h.audit-update 2024-03-01 02:50:52.000000000 +0100 ++++ shadow-4.15.1/lib/prototypes.h 2024-05-20 11:52:05.639758532 +0200 +@@ -198,12 +198,21 @@ extern int audit_fd; + extern void audit_help_open (void); + /* Use AUDIT_NO_ID when a name is provided to audit_logger instead of an ID */ + #define AUDIT_NO_ID ((unsigned int) -1) ++#ifndef AUDIT_GRP_MGMT ++#define AUDIT_GRP_MGMT 1132 /* Group account was modified */ ++#endif ++#ifndef AUDIT_GRP_CHAUTHTOK ++#define AUDIT_GRP_CHAUTHTOK 1133 /* Group account password was changed */ ++#endif + typedef enum { + SHADOW_AUDIT_FAILURE = 0, + SHADOW_AUDIT_SUCCESS = 1} shadow_audit_result; + extern void audit_logger (int type, const char *pgname, const char *op, + const char *name, unsigned int id, + shadow_audit_result result); ++void audit_logger_with_group (int type, MAYBE_UNUSED const char *pgname, ++ const char *op, const char *name, unsigned int id, ++ const char *grp, shadow_audit_result result); + void audit_logger_message (const char *message, shadow_audit_result result); + #endif + +diff -up shadow-4.15.1/src/chage.c.audit-update shadow-4.15.1/src/chage.c +--- shadow-4.15.1/src/chage.c.audit-update 2024-03-08 22:27:04.000000000 +0100 ++++ shadow-4.15.1/src/chage.c 2024-05-20 11:52:05.639758532 +0200 +@@ -110,8 +110,8 @@ fail_exit (int code) + + #ifdef WITH_AUDIT + if (E_SUCCESS != code) { +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "change age", user_name, user_uid, 0); ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "change-age", user_name, user_uid, SHADOW_AUDIT_FAILURE); + } + #endif + +@@ -846,10 +846,7 @@ int main (int argc, char **argv) + fprintf (stderr, _("%s: Permission denied.\n"), Prog); + fail_exit (E_NOPERM); + } +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "display aging info", user_name, user_uid, 1); +-#endif ++ /* Displaying fields is not of interest to audit */ + list_fields (); + fail_exit (E_SUCCESS); + } +@@ -868,39 +865,39 @@ int main (int argc, char **argv) + } + #ifdef WITH_AUDIT + else { +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "change all aging information", +- user_name, user_uid, 1); ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "change-all-aging-information", ++ user_name, user_uid, SHADOW_AUDIT_SUCCESS); + } + #endif + } else { + #ifdef WITH_AUDIT + if (Mflg) { +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "change max age", user_name, user_uid, 1); ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "change-max-age", user_name, user_uid, SHADOW_AUDIT_SUCCESS); + } + if (mflg) { +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "change min age", user_name, user_uid, 1); ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "change-min-age", user_name, user_uid, 1); + } + if (dflg) { +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "change last change date", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "change-last-change-date", + user_name, user_uid, 1); + } + if (Wflg) { +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "change passwd warning", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "change-passwd-warning", + user_name, user_uid, 1); + } + if (Iflg) { +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "change inactive days", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "change-inactive-days", + user_name, user_uid, 1); + } + if (Eflg) { +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "change passwd expiration", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "change-passwd-expiration", + user_name, user_uid, 1); + } + #endif +diff -up shadow-4.15.1/src/gpasswd.c.audit-update shadow-4.15.1/src/gpasswd.c +--- shadow-4.15.1/src/gpasswd.c.audit-update 2024-03-08 22:27:04.000000000 +0100 ++++ shadow-4.15.1/src/gpasswd.c 2024-05-20 11:52:05.640758536 +0200 +@@ -125,7 +125,7 @@ static void usage (int status) + (void) fputs (_(" -d, --delete USER remove USER from GROUP\n"), usageout); + (void) fputs (_(" -h, --help display this help message and exit\n"), usageout); + (void) fputs (_(" -Q, --root CHROOT_DIR directory to chroot into\n"), usageout); +- (void) fputs (_(" -r, --remove-password remove the GROUP's password\n"), usageout); ++ (void) fputs (_(" -r, --delete-password remove the GROUP's password\n"), usageout); + (void) fputs (_(" -R, --restrict restrict access to GROUP to its members\n"), usageout); + (void) fputs (_(" -M, --members USER,... set the list of members of GROUP\n"), usageout); + #ifdef SHADOWGRP +@@ -384,20 +384,14 @@ static void open_files (void) + + static void log_gpasswd_failure (const char *suffix) + { +-#ifdef WITH_AUDIT +- char buf[1024]; +-#endif +- + if (aflg) { + SYSLOG ((LOG_ERR, + "%s failed to add user %s to group %s%s", + myname, user, group, suffix)); + #ifdef WITH_AUDIT +- SNPRINTF(buf, "%s failed to add user %s to group %s%s", +- myname, user, group, suffix); +- audit_logger (AUDIT_USER_ACCT, Prog, +- buf, +- group, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_USER_MGMT, Prog, ++ "add-user-to-group", ++ user, AUDIT_NO_ID, group, + SHADOW_AUDIT_FAILURE); + #endif + } else if (dflg) { +@@ -405,11 +399,9 @@ static void log_gpasswd_failure (const c + "%s failed to remove user %s from group %s%s", + myname, user, group, suffix)); + #ifdef WITH_AUDIT +- SNPRINTF(buf, "%s failed to remove user %s from group %s%s", +- myname, user, group, suffix); +- audit_logger (AUDIT_USER_ACCT, Prog, +- buf, +- group, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_USER_MGMT, Prog, ++ "delete-user-from-group", ++ user, AUDIT_NO_ID, group, + SHADOW_AUDIT_FAILURE); + #endif + } else if (rflg) { +@@ -417,11 +409,9 @@ static void log_gpasswd_failure (const c + "%s failed to remove password of group %s%s", + myname, group, suffix)); + #ifdef WITH_AUDIT +- SNPRINTF(buf, "%s failed to remove password of group %s%s", +- myname, group, suffix); +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- buf, +- group, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_GRP_CHAUTHTOK, Prog, ++ "delete-group-password", ++ myname, AUDIT_NO_ID, group, + SHADOW_AUDIT_FAILURE); + #endif + } else if (Rflg) { +@@ -429,11 +419,9 @@ static void log_gpasswd_failure (const c + "%s failed to restrict access to group %s%s", + myname, group, suffix)); + #ifdef WITH_AUDIT +- SNPRINTF(buf, "%s failed to restrict access to group %s%s", +- myname, group, suffix); +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- buf, +- group, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_GRP_MGMT, Prog, ++ "restrict-group", ++ myname, AUDIT_NO_ID, group, + SHADOW_AUDIT_FAILURE); + #endif + } else if (Aflg || Mflg) { +@@ -443,11 +431,9 @@ static void log_gpasswd_failure (const c + "%s failed to set the administrators of group %s to %s%s", + myname, group, admins, suffix)); + #ifdef WITH_AUDIT +- SNPRINTF(buf, "%s failed to set the administrators of group %s to %s%s", +- myname, group, admins, suffix); +- audit_logger (AUDIT_USER_ACCT, Prog, +- buf, +- group, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_GRP_MGMT, Prog, ++ "set-admins-of-group", ++ admins, AUDIT_NO_ID, group, + SHADOW_AUDIT_FAILURE); + #endif + } +@@ -457,11 +443,9 @@ static void log_gpasswd_failure (const c + "%s failed to set the members of group %s to %s%s", + myname, group, members, suffix)); + #ifdef WITH_AUDIT +- SNPRINTF(buf, "%s failed to set the members of group %s to %s%s", +- myname, group, members, suffix); +- audit_logger (AUDIT_USER_ACCT, Prog, +- buf, +- group, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_USER_MGMT, Prog, ++ "add-users-to-group", ++ members, AUDIT_NO_ID, group, + SHADOW_AUDIT_FAILURE); + #endif + } +@@ -470,11 +454,9 @@ static void log_gpasswd_failure (const c + "%s failed to change password of group %s%s", + myname, group, suffix)); + #ifdef WITH_AUDIT +- SNPRINTF(buf, "%s failed to change password of group %s%s", +- myname, group, suffix); +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- buf, +- group, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_GRP_CHAUTHTOK, Prog, ++ "change-password", ++ myname, AUDIT_NO_ID, group, + SHADOW_AUDIT_FAILURE); + #endif + } +@@ -514,11 +496,9 @@ static void log_gpasswd_success (const c + "user %s added by %s to group %s%s", + user, myname, group, suffix)); + #ifdef WITH_AUDIT +- SNPRINTF(buf, "user %s added by %s to group %s%s", +- user, myname, group, suffix); +- audit_logger (AUDIT_USER_ACCT, Prog, +- buf, +- group, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_USER_MGMT, Prog, ++ "add-user-to-group", ++ user, AUDIT_NO_ID, group, + SHADOW_AUDIT_SUCCESS); + #endif + } else if (dflg) { +@@ -526,11 +506,9 @@ static void log_gpasswd_success (const c + "user %s removed by %s from group %s%s", + user, myname, group, suffix)); + #ifdef WITH_AUDIT +- SNPRINTF(buf, "user %s removed by %s from group %s%s", +- user, myname, group, suffix); +- audit_logger (AUDIT_USER_ACCT, Prog, +- buf, +- group, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_USER_MGMT, Prog, ++ "delete-user-from-group", ++ user, AUDIT_NO_ID, group, + SHADOW_AUDIT_SUCCESS); + #endif + } else if (rflg) { +@@ -540,9 +518,9 @@ static void log_gpasswd_success (const c + #ifdef WITH_AUDIT + SNPRINTF(buf, "password of group %s removed by %s%s", + group, myname, suffix); +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- buf, +- group, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_GRP_CHAUTHTOK, Prog, ++ "delete-group-password", ++ myname, AUDIT_NO_ID, group, + SHADOW_AUDIT_SUCCESS); + #endif + } else if (Rflg) { +@@ -552,9 +530,9 @@ static void log_gpasswd_success (const c + #ifdef WITH_AUDIT + SNPRINTF(buf, "access to group %s restricted by %s%s", + group, myname, suffix); +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- buf, +- group, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_GRP_MGMT, Prog, ++ "restrict-group", ++ myname, AUDIT_NO_ID, group, + SHADOW_AUDIT_SUCCESS); + #endif + } else if (Aflg || Mflg) { +@@ -564,11 +542,9 @@ static void log_gpasswd_success (const c + "administrators of group %s set by %s to %s%s", + group, myname, admins, suffix)); + #ifdef WITH_AUDIT +- SNPRINTF(buf, "administrators of group %s set by %s to %s%s", +- group, myname, admins, suffix); +- audit_logger (AUDIT_USER_ACCT, Prog, +- buf, +- group, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_GRP_MGMT, Prog, ++ "set-admins-of-group", ++ admins, AUDIT_NO_ID, group, + SHADOW_AUDIT_SUCCESS); + #endif + } +@@ -578,11 +554,9 @@ static void log_gpasswd_success (const c + "members of group %s set by %s to %s%s", + group, myname, members, suffix)); + #ifdef WITH_AUDIT +- SNPRINTF(buf, "members of group %s set by %s to %s%s", +- group, myname, members, suffix); +- audit_logger (AUDIT_USER_ACCT, Prog, +- buf, +- group, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_USER_MGMT, Prog, ++ "add-users-to-group", ++ members, AUDIT_NO_ID, group, + SHADOW_AUDIT_SUCCESS); + #endif + } +@@ -591,11 +565,9 @@ static void log_gpasswd_success (const c + "password of group %s changed by %s%s", + group, myname, suffix)); + #ifdef WITH_AUDIT +- SNPRINTF(buf, "password of group %s changed by %s%s", +- group, myname, suffix); +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- buf, +- group, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_GRP_CHAUTHTOK, Prog, ++ "change-password", ++ myname, AUDIT_NO_ID, group, + SHADOW_AUDIT_SUCCESS); + #endif + } +diff -up shadow-4.15.1/src/groupadd.c.audit-update shadow-4.15.1/src/groupadd.c +--- shadow-4.15.1/src/groupadd.c.audit-update 2024-03-08 22:27:04.000000000 +0100 ++++ shadow-4.15.1/src/groupadd.c 2024-05-20 11:52:05.640758536 +0200 +@@ -115,6 +115,15 @@ usage (int status) + exit (status); + } + ++static void fail_exit(int status) ++{ ++#ifdef WITH_AUDIT ++ audit_logger(AUDIT_ADD_GROUP, Prog, "add-group", group_name, ++ AUDIT_NO_ID, SHADOW_AUDIT_FAILURE); ++#endif ++ exit (status); ++} ++ + /* + * new_grent - initialize the values in a group file entry + * +@@ -211,7 +220,7 @@ static void grp_update (void) + fprintf (stderr, + _("%s: failed to prepare the new %s entry '%s'\n"), + Prog, gr_dbname (), grp.gr_name); +- exit (E_GRP_UPDATE); ++ fail_exit (E_GRP_UPDATE); + } + #ifdef SHADOWGRP + /* +@@ -221,7 +230,7 @@ static void grp_update (void) + fprintf (stderr, + _("%s: failed to prepare the new %s entry '%s'\n"), + Prog, sgr_dbname (), sgrp.sg_name); +- exit (E_GRP_UPDATE); ++ fail_exit (E_GRP_UPDATE); + } + #endif /* SHADOWGRP */ + } +@@ -245,7 +254,7 @@ static void check_new_name (void) + fprintf (stderr, _("%s: '%s' is not a valid group name\n"), + Prog, group_name); + +- exit (E_BAD_ARG); ++ fail_exit (E_BAD_ARG); + } + + /* +@@ -261,11 +270,11 @@ static void close_files (void) + fprintf (stderr, + _("%s: failure while writing changes to %s\n"), + Prog, gr_dbname ()); +- exit (E_GRP_UPDATE); ++ fail_exit (E_GRP_UPDATE); + } + #ifdef WITH_AUDIT + audit_logger (AUDIT_ADD_GROUP, Prog, +- "adding group to /etc/group", ++ "add-group", + group_name, group_id, SHADOW_AUDIT_SUCCESS); + #endif + SYSLOG ((LOG_INFO, "group added to %s: name=%s, GID=%u", +@@ -282,11 +291,11 @@ static void close_files (void) + fprintf (stderr, + _("%s: failure while writing changes to %s\n"), + Prog, sgr_dbname ()); +- exit (E_GRP_UPDATE); ++ fail_exit (E_GRP_UPDATE); + } + #ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_GROUP, Prog, +- "adding group to /etc/gshadow", ++ audit_logger (AUDIT_GRP_MGMT, Prog, ++ "add-shadow-group", + group_name, group_id, SHADOW_AUDIT_SUCCESS); + #endif + SYSLOG ((LOG_INFO, "group added to %s: name=%s", +@@ -299,10 +308,6 @@ static void close_files (void) + #endif /* SHADOWGRP */ + + /* Report success at the system level */ +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_GROUP, Prog, +- "", group_name, group_id, SHADOW_AUDIT_SUCCESS); +-#endif + SYSLOG ((LOG_INFO, "new group: name=%s, GID=%u", + group_name, (unsigned int) group_id)); + del_cleanup (cleanup_report_add_group); +@@ -320,7 +325,7 @@ static void open_files (void) + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, gr_dbname ()); +- exit (E_GRP_UPDATE); ++ fail_exit (E_GRP_UPDATE); + } + add_cleanup (cleanup_unlock_group, NULL); + +@@ -330,7 +335,7 @@ static void open_files (void) + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, sgr_dbname ()); +- exit (E_GRP_UPDATE); ++ fail_exit (E_GRP_UPDATE); + } + add_cleanup (cleanup_unlock_gshadow, NULL); + } +@@ -346,7 +351,7 @@ static void open_files (void) + if (gr_open (O_CREAT | O_RDWR) == 0) { + fprintf (stderr, _("%s: cannot open %s: %s\n"), Prog, gr_dbname (), strerror(errno)); + SYSLOG ((LOG_WARN, "cannot open %s: %s", gr_dbname (), strerror(errno))); +- exit (E_GRP_UPDATE); ++ fail_exit (E_GRP_UPDATE); + } + + #ifdef SHADOWGRP +@@ -356,7 +361,7 @@ static void open_files (void) + _("%s: cannot open %s: %s\n"), + Prog, sgr_dbname (), strerror(errno)); + SYSLOG ((LOG_WARN, "cannot open %s: %s", sgr_dbname (), strerror(errno))); +- exit (E_GRP_UPDATE); ++ fail_exit (E_GRP_UPDATE); + } + } + #endif /* SHADOWGRP */ +@@ -493,7 +498,7 @@ static void check_flags (void) + fprintf (stderr, + _("%s: group '%s' already exists\n"), + Prog, group_name); +- exit (E_NAME_IN_USE); ++ fail_exit (E_NAME_IN_USE); + } + + if (gflg && (prefix_getgrgid (group_id) != NULL)) { +@@ -512,7 +517,7 @@ static void check_flags (void) + fprintf (stderr, + _("%s: GID '%lu' already exists\n"), + Prog, (unsigned long) group_id); +- exit (E_GID_IN_USE); ++ fail_exit (E_GID_IN_USE); + } + } + } +@@ -540,7 +545,7 @@ static void check_perms (void) + fprintf (stderr, + _("%s: Cannot determine your user name.\n"), + Prog); +- exit (1); ++ fail_exit (1); + } + + retval = pam_start (Prog, pampw->pw_name, &conv, &pamh); +@@ -560,7 +565,7 @@ static void check_perms (void) + if (NULL != pamh) { + (void) pam_end (pamh, retval); + } +- exit (1); ++ fail_exit (1); + } + (void) pam_end (pamh, retval); + #endif /* USE_PAM */ +@@ -591,7 +596,7 @@ int main (int argc, char **argv) + fprintf (stderr, + _("%s: Cannot setup cleanup service.\n"), + Prog); +- exit (1); ++ fail_exit (1); + } + + /* +@@ -618,7 +623,7 @@ int main (int argc, char **argv) + + if (!gflg) { + if (find_new_gid (rflg, &group_id, NULL) < 0) { +- exit (E_GID_IN_USE); ++ fail_exit (E_GID_IN_USE); + } + } + +diff -up shadow-4.15.1/src/groupdel.c.audit-update shadow-4.15.1/src/groupdel.c +--- shadow-4.15.1/src/groupdel.c.audit-update 2024-03-08 22:27:04.000000000 +0100 ++++ shadow-4.15.1/src/groupdel.c 2024-05-20 11:52:05.640758536 +0200 +@@ -87,6 +87,15 @@ usage (int status) + exit (status); + } + ++static void fail_exit(int status) ++{ ++#ifdef WITH_AUDIT ++ audit_logger(AUDIT_GRP_MGMT, Prog, "delete-group", group_name, ++ AUDIT_NO_ID, SHADOW_AUDIT_FAILURE); ++#endif ++ exit (status); ++} ++ + /* + * grp_update - update group file entries + * +@@ -113,7 +122,7 @@ static void grp_update (void) + fprintf (stderr, + _("%s: cannot remove entry '%s' from %s\n"), + Prog, group_name, gr_dbname ()); +- exit (E_GRP_UPDATE); ++ fail_exit (E_GRP_UPDATE); + } + + #ifdef SHADOWGRP +@@ -125,7 +134,7 @@ static void grp_update (void) + fprintf (stderr, + _("%s: cannot remove entry '%s' from %s\n"), + Prog, group_name, sgr_dbname ()); +- exit (E_GRP_UPDATE); ++ fail_exit (E_GRP_UPDATE); + } + } + #endif /* SHADOWGRP */ +@@ -144,12 +153,12 @@ static void close_files (void) + fprintf (stderr, + _("%s: failure while writing changes to %s\n"), + Prog, gr_dbname ()); +- exit (E_GRP_UPDATE); ++ fail_exit (E_GRP_UPDATE); + } + + #ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_GROUP, Prog, +- "removing group from /etc/group", ++ "delete-group", + group_name, group_id, SHADOW_AUDIT_SUCCESS); + #endif + SYSLOG ((LOG_INFO, +@@ -168,12 +177,12 @@ static void close_files (void) + fprintf (stderr, + _("%s: failure while writing changes to %s\n"), + Prog, sgr_dbname ()); +- exit (E_GRP_UPDATE); ++ fail_exit (E_GRP_UPDATE); + } + + #ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_GROUP, Prog, +- "removing group from /etc/gshadow", ++ audit_logger (AUDIT_GRP_MGMT, Prog, ++ "delete-shadow-group", + group_name, group_id, SHADOW_AUDIT_SUCCESS); + #endif + SYSLOG ((LOG_INFO, +@@ -186,11 +195,6 @@ static void close_files (void) + } + #endif /* SHADOWGRP */ + +- /* Report success at the system level */ +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_GROUP, Prog, +- "", group_name, group_id, SHADOW_AUDIT_SUCCESS); +-#endif + SYSLOG ((LOG_INFO, "group '%s' removed\n", group_name)); + del_cleanup (cleanup_report_del_group); + } +@@ -207,7 +211,7 @@ static void open_files (void) + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, gr_dbname ()); +- exit (E_GRP_UPDATE); ++ fail_exit (E_GRP_UPDATE); + } + add_cleanup (cleanup_unlock_group, NULL); + #ifdef SHADOWGRP +@@ -216,7 +220,7 @@ static void open_files (void) + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, sgr_dbname ()); +- exit (E_GRP_UPDATE); ++ fail_exit (E_GRP_UPDATE); + } + add_cleanup (cleanup_unlock_gshadow, NULL); + } +@@ -234,7 +238,7 @@ static void open_files (void) + _("%s: cannot open %s\n"), + Prog, gr_dbname ()); + SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ())); +- exit (E_GRP_UPDATE); ++ fail_exit (E_GRP_UPDATE); + } + #ifdef SHADOWGRP + if (is_shadow_grp) { +@@ -243,7 +247,7 @@ static void open_files (void) + _("%s: cannot open %s\n"), + Prog, sgr_dbname ()); + SYSLOG ((LOG_WARN, "cannot open %s", sgr_dbname ())); +- exit (E_GRP_UPDATE); ++ fail_exit (E_GRP_UPDATE); + } + } + #endif /* SHADOWGRP */ +@@ -284,7 +288,7 @@ static void group_busy (gid_t gid) + fprintf (stderr, + _("%s: cannot remove the primary group of user '%s'\n"), + Prog, pwd->pw_name); +- exit (E_GROUP_BUSY); ++ fail_exit (E_GROUP_BUSY); + } + + /* +@@ -368,7 +372,7 @@ int main (int argc, char **argv) + fprintf (stderr, + _("%s: Cannot setup cleanup service.\n"), + Prog); +- exit (1); ++ fail_exit (1); + } + + process_flags (argc, argv); +@@ -382,7 +386,7 @@ int main (int argc, char **argv) + fprintf (stderr, + _("%s: Cannot determine your user name.\n"), + Prog); +- exit (1); ++ fail_exit (1); + } + + retval = pam_start (Prog, pampw->pw_name, &conv, &pamh); +@@ -403,7 +407,7 @@ int main (int argc, char **argv) + if (NULL != pamh) { + (void) pam_end (pamh, retval); + } +- exit (1); ++ fail_exit (1); + } + (void) pam_end (pamh, retval); + #endif /* USE_PAM */ +@@ -423,7 +427,7 @@ int main (int argc, char **argv) + fprintf (stderr, + _("%s: group '%s' does not exist\n"), + Prog, group_name); +- exit (E_NOTFOUND); ++ fail_exit (E_NOTFOUND); + } + + group_id = grp->gr_gid; +@@ -447,7 +451,7 @@ int main (int argc, char **argv) + _("%s: %s is the NIS master\n"), + Prog, nis_master); + } +- exit (E_NOTFOUND); ++ fail_exit (E_NOTFOUND); + } + #endif + +diff -up shadow-4.15.1/src/groupmod.c.audit-update shadow-4.15.1/src/groupmod.c +--- shadow-4.15.1/src/groupmod.c.audit-update 2024-03-08 22:27:04.000000000 +0100 ++++ shadow-4.15.1/src/groupmod.c 2024-05-20 11:52:05.640758536 +0200 +@@ -474,7 +474,7 @@ static void close_files (void) + exit (E_GRP_UPDATE); + } + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_ACCT, Prog, ++ audit_logger (AUDIT_GRP_MGMT, Prog, + info_group.audit_msg, + group_name, AUDIT_NO_ID, + SHADOW_AUDIT_SUCCESS); +@@ -497,7 +497,14 @@ static void close_files (void) + exit (E_GRP_UPDATE); + } + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_ACCT, Prog, ++ /* If both happened, log password change as its more important */ ++ if (pflg) ++ audit_logger (AUDIT_GRP_CHAUTHTOK, Prog, ++ info_gshadow.audit_msg, ++ group_name, AUDIT_NO_ID, ++ SHADOW_AUDIT_SUCCESS); ++ else ++ audit_logger (AUDIT_GRP_MGMT, Prog, + info_gshadow.audit_msg, + group_name, AUDIT_NO_ID, + SHADOW_AUDIT_SUCCESS); +@@ -520,7 +527,7 @@ static void close_files (void) + exit (E_GRP_UPDATE); + } + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_ACCT, Prog, ++ audit_logger (AUDIT_GRP_MGMT, Prog, + info_passwd.audit_msg, + group_name, AUDIT_NO_ID, + SHADOW_AUDIT_SUCCESS); +@@ -535,8 +542,8 @@ static void close_files (void) + } + + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_ACCT, Prog, +- "modifying group", ++ audit_logger (AUDIT_GRP_MGMT, Prog, ++ "modify-group", + group_name, AUDIT_NO_ID, + SHADOW_AUDIT_SUCCESS); + #endif +diff -up shadow-4.15.1/src/newgrp.c.audit-update shadow-4.15.1/src/newgrp.c +--- shadow-4.15.1/src/newgrp.c.audit-update 2024-03-08 22:27:04.000000000 +0100 ++++ shadow-4.15.1/src/newgrp.c 2024-05-20 11:52:05.640758536 +0200 +@@ -188,10 +188,10 @@ static void check_perms (const struct gr + if (grp->gr_passwd[0] == '\0' || + strcmp (cpasswd, grp->gr_passwd) != 0) { + #ifdef WITH_AUDIT +- SNPRINTF(audit_buf, "authentication new-gid=%lu", ++ SNPRINTF(audit_buf, "authentication new_gid=%lu", + (unsigned long) grp->gr_gid); + audit_logger (AUDIT_GRP_AUTH, Prog, +- audit_buf, NULL, getuid (), 0); ++ audit_buf, NULL, getuid (), SHADOW_AUDIT_FAILURE); + #endif + SYSLOG ((LOG_INFO, + "Invalid password for group '%s' from '%s'", +@@ -201,10 +201,10 @@ static void check_perms (const struct gr + goto failure; + } + #ifdef WITH_AUDIT +- SNPRINTF(audit_buf, "authentication new-gid=%lu", ++ SNPRINTF(audit_buf, "authentication new_gid=%lu", + (unsigned long) grp->gr_gid); + audit_logger (AUDIT_GRP_AUTH, Prog, +- audit_buf, NULL, getuid (), 1); ++ audit_buf, NULL, getuid (), SHADOW_AUDIT_SUCCESS); + #endif + } + +@@ -215,16 +215,6 @@ failure: + * harm. -- JWP + */ + closelog (); +-#ifdef WITH_AUDIT +- if (groupname) { +- SNPRINTF(audit_buf, "changing new-group=%s", groupname); +- audit_logger (AUDIT_CHGRP_ID, Prog, +- audit_buf, NULL, getuid (), 0); +- } else { +- audit_logger (AUDIT_CHGRP_ID, Prog, +- "changing", NULL, getuid (), 0); +- } +-#endif + exit (EXIT_FAILURE); + } + +@@ -298,13 +288,23 @@ static void syslog_sg (const char *name, + is_newgrp ? "newgrp" : "sg", strerror (errno)); + #ifdef WITH_AUDIT + if (group) { +- SNPRINTF(audit_buf, +- "changing new-group=%s", group); ++ char enc_group[(GROUP_NAME_MAX_LENGTH*2)+1]; ++ int len = strnlen(group, sizeof(enc_group)/2); ++ if (audit_value_needs_encoding(group, len)) { ++ snprintf (audit_buf, sizeof(audit_buf), ++ "changing new_group=%s", ++ audit_encode_value(enc_group, ++ group, len)); ++ } else { ++ snprintf (audit_buf, sizeof(audit_buf), ++ "changing new_group=\"%s\"", ++ group); ++ } + audit_logger (AUDIT_CHGRP_ID, Prog, +- audit_buf, NULL, getuid (), 0); ++ audit_buf, NULL, getuid (), SHADOW_AUDIT_FAILURE); + } else { + audit_logger (AUDIT_CHGRP_ID, Prog, +- "changing", NULL, getuid (), 0); ++ "changing", NULL, getuid (), SHADOW_AUDIT_FAILURE); + } + #endif + exit (EXIT_FAILURE); +@@ -440,7 +440,7 @@ int main (int argc, char **argv) + Prog); + #ifdef WITH_AUDIT + audit_logger (AUDIT_CHGRP_ID, Prog, +- "changing", NULL, getuid (), 0); ++ "changing", NULL, getuid (), SHADOW_AUDIT_FAILURE); + #endif + SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)", + (unsigned long) getuid ())); +@@ -556,12 +556,22 @@ int main (int argc, char **argv) + perror ("getgroups"); + #ifdef WITH_AUDIT + if (group) { +- SNPRINTF(audit_buf, "changing new-group=%s", group); ++ char enc_group[(GROUP_NAME_MAX_LENGTH*2)+1]; ++ int len = strnlen(group, sizeof(enc_group)/2); ++ if (audit_value_needs_encoding(group, len)) { ++ snprintf (audit_buf, sizeof(audit_buf), ++ "changing new_group=%s", ++ audit_encode_value(enc_group, ++ group, len)); ++ } else { ++ snprintf (audit_buf, sizeof(audit_buf), ++ "changing new_group=\"%s\"", group); ++ } + audit_logger (AUDIT_CHGRP_ID, Prog, +- audit_buf, NULL, getuid (), 0); ++ audit_buf, NULL, getuid (), SHADOW_AUDIT_FAILURE); + } else { + audit_logger (AUDIT_CHGRP_ID, Prog, +- "changing", NULL, getuid (), 0); ++ "changing", NULL, getuid (), SHADOW_AUDIT_FAILURE); + } + #endif + exit (EXIT_FAILURE); +@@ -715,9 +725,9 @@ int main (int argc, char **argv) + if (setgid (gid) != 0) { + perror ("setgid"); + #ifdef WITH_AUDIT +- SNPRINTF(audit_buf, "changing new-gid=%lu", (unsigned long) gid); ++ SNPRINTF(audit_buf, "changing new_gid=%lu", (unsigned long) gid); + audit_logger (AUDIT_CHGRP_ID, Prog, +- audit_buf, NULL, getuid (), 0); ++ audit_buf, NULL, getuid (), SHADOW_AUDIT_FAILURE); + #endif + exit (EXIT_FAILURE); + } +@@ -725,9 +735,9 @@ int main (int argc, char **argv) + if (setuid (getuid ()) != 0) { + perror ("setuid"); + #ifdef WITH_AUDIT +- SNPRINTF(audit_buf, "changing new-gid=%lu", (unsigned long) gid); ++ SNPRINTF(audit_buf, "changing new_gid=%lu", (unsigned long) gid); + audit_logger (AUDIT_CHGRP_ID, Prog, +- audit_buf, NULL, getuid (), 0); ++ audit_buf, NULL, getuid (), SHADOW_AUDIT_FAILURE); + #endif + exit (EXIT_FAILURE); + } +@@ -740,9 +750,9 @@ int main (int argc, char **argv) + closelog (); + execl (SHELL, "sh", "-c", command, (char *) NULL); + #ifdef WITH_AUDIT +- SNPRINTF(audit_buf, "changing new-gid=%lu", (unsigned long) gid); ++ SNPRINTF(audit_buf, "changing new_gid=%lu", (unsigned long) gid); + audit_logger (AUDIT_CHGRP_ID, Prog, +- audit_buf, NULL, getuid (), 0); ++ audit_buf, NULL, getuid (), SHADOW_AUDIT_FAILURE); + #endif + perror (SHELL); + exit ((errno == ENOENT) ? E_CMD_NOTFOUND : E_CMD_NOEXEC); +@@ -806,9 +816,9 @@ int main (int argc, char **argv) + } + + #ifdef WITH_AUDIT +- SNPRINTF(audit_buf, "changing new-gid=%lu", (unsigned long) gid); ++ SNPRINTF(audit_buf, "changing new_gid=%lu", (unsigned long) gid); + audit_logger (AUDIT_CHGRP_ID, Prog, +- audit_buf, NULL, getuid (), 1); ++ audit_buf, NULL, getuid (), SHADOW_AUDIT_SUCCESS); + #endif + /* + * Exec the login shell and go away. We are trying to get back to +@@ -832,12 +842,22 @@ int main (int argc, char **argv) + closelog (); + #ifdef WITH_AUDIT + if (NULL != group) { +- SNPRINTF(audit_buf, "changing new-group=%s", group); ++ char enc_group[(GROUP_NAME_MAX_LENGTH*2)+1]; ++ int len = strnlen(group, sizeof(enc_group)/2); ++ if (audit_value_needs_encoding(group, len)) { ++ snprintf (audit_buf, sizeof(audit_buf), ++ "changing new_group=%s", ++ audit_encode_value(enc_group, ++ group, len)); ++ } else { ++ snprintf (audit_buf, sizeof(audit_buf), ++ "changing new_group=\"%s\"", group); ++ } + audit_logger (AUDIT_CHGRP_ID, Prog, +- audit_buf, NULL, getuid (), 0); ++ audit_buf, NULL, getuid (), SHADOW_AUDIT_FAILURE); + } else { + audit_logger (AUDIT_CHGRP_ID, Prog, +- "changing", NULL, getuid (), 0); ++ "changing", NULL, getuid (), SHADOW_AUDIT_FAILURE); + } + #endif + exit (EXIT_FAILURE); +diff -up shadow-4.15.1/src/useradd.c.audit-update shadow-4.15.1/src/useradd.c +--- shadow-4.15.1/src/useradd.c.audit-update 2024-05-20 11:52:05.635758519 +0200 ++++ shadow-4.15.1/src/useradd.c 2024-05-20 11:52:05.640758536 +0200 +@@ -245,6 +245,8 @@ static FILE *fmkstemp(char *template); + */ + static void fail_exit (int code) + { ++ int type; ++ + if (home_added && rmdir(prefix_user_home) != 0) { + fprintf(stderr, + _("%s: %s was created, but could not be removed\n"), +@@ -255,38 +257,22 @@ static void fail_exit (int code) + if (spw_locked && spw_unlock() == 0) { + fprintf(stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname()); + SYSLOG((LOG_ERR, "failed to unlock %s", spw_dbname())); +-#ifdef WITH_AUDIT +- audit_logger(AUDIT_ADD_USER, Prog, "unlocking shadow file", +- user_name, AUDIT_NO_ID, SHADOW_AUDIT_FAILURE); +-#endif + /* continue */ + } + if (pw_locked && pw_unlock() == 0) { + fprintf(stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname()); + SYSLOG((LOG_ERR, "failed to unlock %s", pw_dbname())); +-#ifdef WITH_AUDIT +- audit_logger(AUDIT_ADD_USER, Prog, "unlocking passwd file", +- user_name, AUDIT_NO_ID, SHADOW_AUDIT_FAILURE); +-#endif + /* continue */ + } + if (gr_locked && gr_unlock() == 0) { + fprintf(stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname()); + SYSLOG((LOG_ERR, "failed to unlock %s", gr_dbname())); +-#ifdef WITH_AUDIT +- audit_logger(AUDIT_ADD_USER, Prog, "unlocking group file", +- user_name, AUDIT_NO_ID, SHADOW_AUDIT_FAILURE); +-#endif + /* continue */ + } + #ifdef SHADOWGRP + if (sgr_locked && sgr_unlock() == 0) { + fprintf(stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname()); + SYSLOG((LOG_ERR, "failed to unlock %s", sgr_dbname())); +-# ifdef WITH_AUDIT +- audit_logger(AUDIT_ADD_USER, Prog, "unlocking gshadow file", +- user_name, AUDIT_NO_ID, SHADOW_AUDIT_FAILURE); +-# endif + /* continue */ + } + #endif +@@ -294,27 +280,23 @@ static void fail_exit (int code) + if (sub_uid_locked && sub_uid_unlock() == 0) { + fprintf(stderr, _("%s: failed to unlock %s\n"), Prog, sub_uid_dbname()); + SYSLOG((LOG_ERR, "failed to unlock %s", sub_uid_dbname())); +-# ifdef WITH_AUDIT +- audit_logger(AUDIT_ADD_USER, Prog, +- "unlocking subordinate user file", +- user_name, AUDIT_NO_ID, SHADOW_AUDIT_FAILURE); +-# endif + /* continue */ + } + if (sub_gid_locked && sub_gid_unlock() == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_gid_dbname()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sub_gid_dbname())); +-# ifdef WITH_AUDIT +- audit_logger(AUDIT_ADD_USER, Prog, +- "unlocking subordinate group file", +- user_name, AUDIT_NO_ID, SHADOW_AUDIT_FAILURE); +-# endif + /* continue */ + } + #endif /* ENABLE_SUBIDS */ + + #ifdef WITH_AUDIT +- audit_logger(AUDIT_ADD_USER, Prog, "adding user", ++ if (code == E_PW_UPDATE || code >= E_GRP_UPDATE) ++ type = AUDIT_USER_MGMT; ++ else ++ type = AUDIT_ADD_USER; ++ ++ audit_logger (type, Prog, ++ "add-user", + user_name, AUDIT_NO_ID, SHADOW_AUDIT_FAILURE); + #endif + SYSLOG((LOG_INFO, "failed adding user '%s', exit code: %d", user_name, code)); +@@ -727,7 +709,7 @@ static int set_defaults (void) + } + #ifdef WITH_AUDIT + audit_logger (AUDIT_USYS_CONFIG, Prog, +- "changing useradd defaults", ++ "changing-useradd-defaults", + NULL, AUDIT_NO_ID, + SHADOW_AUDIT_SUCCESS); + #endif +@@ -1056,12 +1038,6 @@ static void grp_update (void) + _("%s: Out of memory. Cannot update %s.\n"), + Prog, gr_dbname ()); + SYSLOG ((LOG_ERR, "failed to prepare the new %s entry '%s'", gr_dbname (), user_name)); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_USER, Prog, +- "adding user to group", +- user_name, AUDIT_NO_ID, +- SHADOW_AUDIT_FAILURE); +-#endif + fail_exit (E_GRP_UPDATE); /* XXX */ + } + +@@ -1075,18 +1051,12 @@ static void grp_update (void) + _("%s: failed to prepare the new %s entry '%s'\n"), + Prog, gr_dbname (), ngrp->gr_name); + SYSLOG ((LOG_ERR, "failed to prepare the new %s entry '%s'", gr_dbname (), user_name)); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_USER, Prog, +- "adding user to group", +- user_name, AUDIT_NO_ID, +- SHADOW_AUDIT_FAILURE); +-#endif + fail_exit (E_GRP_UPDATE); + } + #ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_USER, Prog, +- "adding user to group", +- user_name, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_USER_MGMT, Prog, ++ "add-user-to-group", ++ user_name, AUDIT_NO_ID, ngrp->gr_name, + SHADOW_AUDIT_SUCCESS); + #endif + SYSLOG ((LOG_INFO, +@@ -1131,12 +1101,6 @@ static void grp_update (void) + _("%s: Out of memory. Cannot update %s.\n"), + Prog, sgr_dbname ()); + SYSLOG ((LOG_ERR, "failed to prepare the new %s entry '%s'", sgr_dbname (), user_name)); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_USER, Prog, +- "adding user to shadow group", +- user_name, AUDIT_NO_ID, +- SHADOW_AUDIT_FAILURE); +-#endif + fail_exit (E_GRP_UPDATE); /* XXX */ + } + +@@ -1150,18 +1114,13 @@ static void grp_update (void) + _("%s: failed to prepare the new %s entry '%s'\n"), + Prog, sgr_dbname (), nsgrp->sg_name); + SYSLOG ((LOG_ERR, "failed to prepare the new %s entry '%s'", sgr_dbname (), user_name)); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_USER, Prog, +- "adding user to shadow group", +- user_name, AUDIT_NO_ID, +- SHADOW_AUDIT_FAILURE); +-#endif ++ + fail_exit (E_GRP_UPDATE); + } + #ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_USER, Prog, +- "adding user to shadow group", +- user_name, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_USER_MGMT, Prog, ++ "add-to-shadow-group", ++ user_name, AUDIT_NO_ID, nsgrp->sg_name, + SHADOW_AUDIT_SUCCESS); + #endif + SYSLOG ((LOG_INFO, +@@ -1556,7 +1515,7 @@ static void process_flags (int argc, cha + Prog, user_name); + #ifdef WITH_AUDIT + audit_logger (AUDIT_ADD_USER, Prog, +- "adding user", ++ "add-user", + user_name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + #endif +@@ -1656,7 +1615,7 @@ static void close_files (void) + SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ())); + #ifdef WITH_AUDIT + audit_logger (AUDIT_ADD_USER, Prog, +- "unlocking shadow file", ++ "unlocking-shadow-file", + user_name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + #endif +@@ -1669,7 +1628,7 @@ static void close_files (void) + SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ())); + #ifdef WITH_AUDIT + audit_logger (AUDIT_ADD_USER, Prog, +- "unlocking passwd file", ++ "unlocking-passwd-file", + user_name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + #endif +@@ -1686,7 +1645,7 @@ static void close_files (void) + SYSLOG ((LOG_ERR, "failed to unlock %s", sub_uid_dbname ())); + #ifdef WITH_AUDIT + audit_logger (AUDIT_ADD_USER, Prog, +- "unlocking subordinate user file", ++ "unlocking-subordinate-user-file", + user_name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + #endif +@@ -1700,7 +1659,7 @@ static void close_files (void) + SYSLOG ((LOG_ERR, "failed to unlock %s", sub_gid_dbname ())); + #ifdef WITH_AUDIT + audit_logger (AUDIT_ADD_USER, Prog, +- "unlocking subordinate group file", ++ "unlocking-subordinate-group-file", + user_name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + #endif +@@ -1963,7 +1922,7 @@ static void grp_add (void) + Prog, gr_dbname (), grp.gr_name); + #ifdef WITH_AUDIT + audit_logger (AUDIT_ADD_GROUP, Prog, +- "adding group", ++ "add-group", + grp.gr_name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + #endif +@@ -1979,7 +1938,7 @@ static void grp_add (void) + Prog, sgr_dbname (), sgrp.sg_name); + #ifdef WITH_AUDIT + audit_logger (AUDIT_ADD_GROUP, Prog, +- "adding group", ++ "add-group", + grp.gr_name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + #endif +@@ -1989,7 +1948,7 @@ static void grp_add (void) + SYSLOG ((LOG_INFO, "new group: name=%s, GID=%u", user_name, user_gid)); + #ifdef WITH_AUDIT + audit_logger (AUDIT_ADD_GROUP, Prog, +- "adding group", ++ "add-group", + grp.gr_name, AUDIT_NO_ID, + SHADOW_AUDIT_SUCCESS); + #endif +@@ -2191,11 +2150,6 @@ static void usr_update (unsigned long su + fprintf (stderr, + _("%s: failed to prepare the new %s entry '%s'\n"), + Prog, spw_dbname (), spent.sp_namp); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_USER, Prog, +- "adding shadow password", +- user_name, user_id, SHADOW_AUDIT_FAILURE); +-#endif + fail_exit (E_PW_UPDATE); + } + #ifdef ENABLE_SUBIDS +@@ -2222,7 +2176,7 @@ static void usr_update (unsigned long su + * and we can use the real ID thereafter. + */ + audit_logger (AUDIT_ADD_USER, Prog, +- "adding user", ++ "add-user", + user_name, AUDIT_NO_ID, + SHADOW_AUDIT_SUCCESS); + #endif +@@ -2317,10 +2271,6 @@ static void create_home (void) + if (mkdir(path, 0) != 0) { + fprintf(stderr, _("%s: cannot create directory %s\n"), + Prog, path); +-#ifdef WITH_AUDIT +- audit_logger(AUDIT_ADD_USER, Prog, "adding home directory", +- user_name, user_id, SHADOW_AUDIT_FAILURE); +-#endif + fail_exit(E_HOMEDIR); + } + if (chown(path, 0, 0) < 0) { +@@ -2345,7 +2295,7 @@ static void create_home (void) + } + home_added = true; + #ifdef WITH_AUDIT +- audit_logger(AUDIT_ADD_USER, Prog, "adding home directory", ++ audit_logger(AUDIT_USER_MGMT, Prog, "add-home-dir", + user_name, user_id, SHADOW_AUDIT_SUCCESS); + #endif + #ifdef WITH_SELINUX +@@ -2586,12 +2536,6 @@ int main (int argc, char **argv) + */ + if (prefix_getpwnam (user_name) != NULL) { /* local, no need for xgetpwnam */ + fprintf (stderr, _("%s: user '%s' already exists\n"), Prog, user_name); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_USER, Prog, +- "adding user", +- user_name, AUDIT_NO_ID, +- SHADOW_AUDIT_FAILURE); +-#endif + fail_exit (E_NAME_IN_USE); + } + +@@ -2607,12 +2551,6 @@ int main (int argc, char **argv) + fprintf (stderr, + _("%s: group %s exists - if you want to add this user to that group, use -g.\n"), + Prog, user_name); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_USER, Prog, +- "adding group", +- user_name, AUDIT_NO_ID, +- SHADOW_AUDIT_FAILURE); +-#endif + fail_exit (E_NAME_IN_USE); + } + } +@@ -2642,12 +2580,6 @@ int main (int argc, char **argv) + fprintf (stderr, + _("%s: UID %lu is not unique\n"), + Prog, (unsigned long) user_id); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_USER, Prog, +- "adding user", +- user_name, user_id, +- SHADOW_AUDIT_FAILURE); +-#endif + fail_exit (E_UID_IN_USE); + } + } +@@ -2722,9 +2654,9 @@ int main (int argc, char **argv) + _("%s: warning: the user name %s to %s SELinux user mapping failed.\n"), + Prog, user_name, user_selinux); + #ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_USER, Prog, +- "adding SELinux user mapping", +- user_name, user_id, 0); ++ audit_logger (AUDIT_ROLE_ASSIGN, Prog, ++ "add-selinux-user-mapping", ++ user_name, user_id, SHADOW_AUDIT_FAILURE); + #endif /* WITH_AUDIT */ + fail_exit (E_SE_UPDATE); + } +diff -up shadow-4.15.1/src/userdel.c.audit-update shadow-4.15.1/src/userdel.c +--- shadow-4.15.1/src/userdel.c.audit-update 2024-03-08 22:27:04.000000000 +0100 ++++ shadow-4.15.1/src/userdel.c 2024-05-20 11:52:05.641758539 +0200 +@@ -206,9 +206,9 @@ static void update_groups (void) + * Update the DBM group file with the new entry as well. + */ + #ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_USER, Prog, +- "deleting user from group", +- user_name, user_id, SHADOW_AUDIT_SUCCESS); ++ audit_logger_with_group (AUDIT_USER_MGMT, Prog, ++ "deleting-user-from-group", ++ user_name, user_id, ngrp->gr_name, SHADOW_AUDIT_SUCCESS); + #endif /* WITH_AUDIT */ + SYSLOG ((LOG_INFO, "delete '%s' from group '%s'\n", + user_name, ngrp->gr_name)); +@@ -267,9 +267,9 @@ static void update_groups (void) + exit (E_GRP_UPDATE); + } + #ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_USER, Prog, +- "deleting user from shadow group", +- user_name, user_id, SHADOW_AUDIT_SUCCESS); ++ audit_logger_with_group (AUDIT_USER_MGMT, Prog, ++ "deleting-user-from-shadow-group", ++ user_name, user_id, nsgrp->sg_name, SHADOW_AUDIT_SUCCESS); + #endif /* WITH_AUDIT */ + SYSLOG ((LOG_INFO, "delete '%s' from shadow group '%s'\n", + user_name, nsgrp->sg_name)); +@@ -345,9 +345,9 @@ static void remove_usergroup (void) + } + + #ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_GROUP, Prog, +- "deleting group", +- user_name, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_DEL_GROUP, Prog, ++ "delete-group", ++ user_name, AUDIT_NO_ID, user_name, + SHADOW_AUDIT_SUCCESS); + #endif /* WITH_AUDIT */ + SYSLOG ((LOG_INFO, +@@ -363,9 +363,9 @@ static void remove_usergroup (void) + fail_exit (E_GRP_UPDATE); + } + #ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_GROUP, Prog, +- "deleting shadow group", +- user_name, AUDIT_NO_ID, ++ audit_logger_with_group (AUDIT_GRP_MGMT, Prog, ++ "delete-shadow-group", ++ user_name, AUDIT_NO_ID, user_name, + SHADOW_AUDIT_SUCCESS); + #endif /* WITH_AUDIT */ + SYSLOG ((LOG_INFO, +@@ -527,7 +527,7 @@ static void fail_exit (int code) + + #ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, +- "deleting user", ++ "delete-user", + user_name, user_id, SHADOW_AUDIT_FAILURE); + #endif /* WITH_AUDIT */ + +@@ -546,22 +546,12 @@ static void open_files (void) + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, pw_dbname ()); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_USER, Prog, +- "locking password file", +- user_name, user_id, SHADOW_AUDIT_FAILURE); +-#endif /* WITH_AUDIT */ + fail_exit (E_PW_UPDATE); + } + pw_locked = true; + if (pw_open (O_CREAT | O_RDWR) == 0) { + fprintf (stderr, + _("%s: cannot open %s\n"), Prog, pw_dbname ()); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_USER, Prog, +- "opening password file", +- user_name, user_id, SHADOW_AUDIT_FAILURE); +-#endif /* WITH_AUDIT */ + fail_exit (E_PW_UPDATE); + } + if (is_shadow_pwd) { +@@ -569,11 +559,6 @@ static void open_files (void) + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, spw_dbname ()); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_USER, Prog, +- "locking shadow password file", +- user_name, user_id, SHADOW_AUDIT_FAILURE); +-#endif /* WITH_AUDIT */ + fail_exit (E_PW_UPDATE); + } + spw_locked = true; +@@ -581,11 +566,6 @@ static void open_files (void) + fprintf (stderr, + _("%s: cannot open %s\n"), + Prog, spw_dbname ()); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_USER, Prog, +- "opening shadow password file", +- user_name, user_id, SHADOW_AUDIT_FAILURE); +-#endif /* WITH_AUDIT */ + fail_exit (E_PW_UPDATE); + } + } +@@ -593,21 +573,11 @@ static void open_files (void) + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, gr_dbname ()); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_USER, Prog, +- "locking group file", +- user_name, user_id, SHADOW_AUDIT_FAILURE); +-#endif /* WITH_AUDIT */ + fail_exit (E_GRP_UPDATE); + } + gr_locked = true; + if (gr_open (O_CREAT | O_RDWR) == 0) { + fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ()); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_USER, Prog, +- "opening group file", +- user_name, user_id, SHADOW_AUDIT_FAILURE); +-#endif /* WITH_AUDIT */ + fail_exit (E_GRP_UPDATE); + } + #ifdef SHADOWGRP +@@ -616,22 +586,12 @@ static void open_files (void) + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, sgr_dbname ()); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_USER, Prog, +- "locking shadow group file", +- user_name, user_id, SHADOW_AUDIT_FAILURE); +-#endif /* WITH_AUDIT */ + fail_exit (E_GRP_UPDATE); + } + sgr_locked= true; + if (sgr_open (O_CREAT | O_RDWR) == 0) { + fprintf (stderr, _("%s: cannot open %s\n"), + Prog, sgr_dbname ()); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_USER, Prog, +- "opening shadow group file", +- user_name, user_id, SHADOW_AUDIT_FAILURE); +-#endif /* WITH_AUDIT */ + fail_exit (E_GRP_UPDATE); + } + } +@@ -642,22 +602,12 @@ static void open_files (void) + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, sub_uid_dbname ()); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_USER, Prog, +- "locking subordinate user file", +- user_name, user_id, SHADOW_AUDIT_FAILURE); +-#endif /* WITH_AUDIT */ + fail_exit (E_SUB_UID_UPDATE); + } + sub_uid_locked = true; + if (sub_uid_open (O_CREAT | O_RDWR) == 0) { + fprintf (stderr, + _("%s: cannot open %s\n"), Prog, sub_uid_dbname ()); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_USER, Prog, +- "opening subordinate user file", +- user_name, user_id, SHADOW_AUDIT_FAILURE); +-#endif /* WITH_AUDIT */ + fail_exit (E_SUB_UID_UPDATE); + } + } +@@ -666,22 +616,12 @@ static void open_files (void) + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, sub_gid_dbname ()); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_USER, Prog, +- "locking subordinate group file", +- user_name, user_id, SHADOW_AUDIT_FAILURE); +-#endif /* WITH_AUDIT */ + fail_exit (E_SUB_GID_UPDATE); + } + sub_gid_locked = true; + if (sub_gid_open (O_CREAT | O_RDWR) == 0) { + fprintf (stderr, + _("%s: cannot open %s\n"), Prog, sub_gid_dbname ()); +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_DEL_USER, Prog, +- "opening subordinate group file", +- user_name, user_id, SHADOW_AUDIT_FAILURE); +-#endif /* WITH_AUDIT */ + fail_exit (E_SUB_GID_UPDATE); + } + } +@@ -726,7 +666,7 @@ static void update_user (void) + #endif /* ENABLE_SUBIDS */ + #ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, +- "deleting user entries", ++ "delete-user", + user_name, user_id, SHADOW_AUDIT_SUCCESS); + #endif /* WITH_AUDIT */ + SYSLOG ((LOG_INFO, "delete user '%s'\n", user_name)); +@@ -824,7 +764,7 @@ static int remove_mailbox (void) + SYSLOG ((LOG_ERR, "Cannot remove %s: %s", mailfile, strerror (errno))); + #ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, +- "deleting mail file", ++ "delete-mail-file", + user_name, user_id, SHADOW_AUDIT_FAILURE); + #endif /* WITH_AUDIT */ + free(mailfile); +@@ -840,7 +780,7 @@ static int remove_mailbox (void) + SYSLOG ((LOG_ERR, "Cannot remove %s: %s", mailfile, strerror (errno))); + #ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, +- "deleting mail file", ++ "delete-mail-file", + user_name, user_id, SHADOW_AUDIT_FAILURE); + #endif /* WITH_AUDIT */ + errors = 1; +@@ -849,8 +789,8 @@ static int remove_mailbox (void) + #ifdef WITH_AUDIT + else + { +- audit_logger (AUDIT_DEL_USER, Prog, +- "deleting mail file", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "delete-mail-file", + user_name, user_id, SHADOW_AUDIT_SUCCESS); + } + #endif /* WITH_AUDIT */ +@@ -867,7 +807,7 @@ static int remove_mailbox (void) + mailfile, strerror (errno))); + #ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, +- "deleting mail file", ++ "delete-mail-file", + user_name, user_id, SHADOW_AUDIT_FAILURE); + #endif /* WITH_AUDIT */ + free(mailfile); +@@ -883,7 +823,7 @@ static int remove_mailbox (void) + SYSLOG ((LOG_ERR, "Cannot remove %s: %s", mailfile, strerror (errno))); + #ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, +- "deleting mail file", ++ "delete-mail-file", + user_name, user_id, SHADOW_AUDIT_FAILURE); + #endif /* WITH_AUDIT */ + errors = 1; +@@ -892,8 +832,8 @@ static int remove_mailbox (void) + #ifdef WITH_AUDIT + else + { +- audit_logger (AUDIT_DEL_USER, Prog, +- "deleting mail file", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "delete-mail-file", + user_name, user_id, SHADOW_AUDIT_SUCCESS); + } + #endif /* WITH_AUDIT */ +@@ -1104,7 +1044,7 @@ int main (int argc, char **argv) + Prog, user_name); + #ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, +- "deleting user not found", ++ "deleting-user-not-found", + user_name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + #endif /* WITH_AUDIT */ +@@ -1154,7 +1094,7 @@ int main (int argc, char **argv) + if (!fflg) { + #ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, +- "deleting user logged in", ++ "deleting-user-logged-in", + user_name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + #endif /* WITH_AUDIT */ +@@ -1248,8 +1188,8 @@ int main (int argc, char **argv) + #ifdef WITH_AUDIT + else + { +- audit_logger (AUDIT_DEL_USER, Prog, +- "deleting home directory", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "deleting-home-directory", + user_name, user_id, SHADOW_AUDIT_SUCCESS); + } + #endif /* WITH_AUDIT */ +@@ -1257,7 +1197,7 @@ int main (int argc, char **argv) + #ifdef WITH_AUDIT + if (0 != errors) { + audit_logger (AUDIT_DEL_USER, Prog, +- "deleting home directory", ++ "deleting-home-directory", + user_name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); + } +@@ -1270,8 +1210,8 @@ int main (int argc, char **argv) + _("%s: warning: the user name %s to SELinux user mapping removal failed.\n"), + Prog, user_name); + #ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_USER, Prog, +- "removing SELinux user mapping", ++ audit_logger (AUDIT_ROLE_REMOVE, Prog, ++ "delete-selinux-user-mapping", + user_name, user_id, SHADOW_AUDIT_FAILURE); + #endif /* WITH_AUDIT */ + fail_exit (E_SE_UPDATE); +diff -up shadow-4.15.1/src/usermod.c.audit-update shadow-4.15.1/src/usermod.c +--- shadow-4.15.1/src/usermod.c.audit-update 2024-05-20 11:52:05.638758529 +0200 ++++ shadow-4.15.1/src/usermod.c 2024-05-20 11:56:51.962509443 +0200 +@@ -440,7 +440,7 @@ static char *new_pw_passwd (char *pw_pas + + #ifdef WITH_AUDIT + audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "updating passwd", user_newname, user_newid, 0); ++ "updating-passwd", user_newname, user_newid, 1); + #endif + SYSLOG ((LOG_INFO, "lock user '%s' password", user_newname)); + strcpy (buf, "!"); +@@ -457,14 +457,14 @@ static char *new_pw_passwd (char *pw_pas + + #ifdef WITH_AUDIT + audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "updating password", user_newname, user_newid, 0); ++ "updating-password", user_newname, user_newid, 1); + #endif + SYSLOG ((LOG_INFO, "unlock user '%s' password", user_newname)); + memmove(pw_pass, pw_pass + 1, strlen(pw_pass)); + } else if (pflg) { + #ifdef WITH_AUDIT + audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing password", user_newname, user_newid, 1); ++ "updating-password", user_newname, user_newid, 1); + #endif + SYSLOG ((LOG_INFO, "change user '%s' password", user_newname)); + pw_pass = xstrdup (user_pass); +@@ -492,8 +492,8 @@ static void new_pwent (struct passwd *pw + fail_exit (E_NAME_IN_USE); + } + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing name", user_newname, user_newid, 1); ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "changing-name", user_newname, user_newid, 1); + #endif + SYSLOG ((LOG_INFO, + "change user name '%s' to '%s'", +@@ -512,8 +512,8 @@ static void new_pwent (struct passwd *pw + + if (uflg) { + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing uid", user_newname, user_newid, 1); ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "changing-uid", user_newname, user_newid, 1); + #endif + SYSLOG ((LOG_INFO, + "change user '%s' UID from '%d' to '%d'", +@@ -522,8 +522,8 @@ static void new_pwent (struct passwd *pw + } + if (gflg) { + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing primary group", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "changing-primary-group", + user_newname, user_newid, 1); + #endif + SYSLOG ((LOG_INFO, +@@ -533,16 +533,16 @@ static void new_pwent (struct passwd *pw + } + if (cflg) { + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing comment", user_newname, user_newid, 1); ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "changing-comment", user_newname, user_newid, 1); + #endif + pwent->pw_gecos = user_newcomment; + } + + if (dflg) { + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing home directory", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "changing-home-dir", + user_newname, user_newid, 1); + #endif + SYSLOG ((LOG_INFO, +@@ -558,8 +558,8 @@ static void new_pwent (struct passwd *pw + } + if (sflg) { + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing user shell", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "changing-shell", + user_newname, user_newid, 1); + #endif + SYSLOG ((LOG_INFO, +@@ -589,8 +589,8 @@ static void new_spent (struct spwd *spen + + if (fflg) { + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing inactive days", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "changing-inactive-days", + user_newname, user_newid, 1); + #endif + SYSLOG ((LOG_INFO, +@@ -604,8 +604,8 @@ static void new_spent (struct spwd *spen + date_to_str (sizeof(new_exp), new_exp, user_newexpire * DAY); + date_to_str (sizeof(old_exp), old_exp, user_expire * DAY); + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing expiration date", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "changing-expiration-date", + user_newname, user_newid, 1); + #endif + SYSLOG ((LOG_INFO, +@@ -690,9 +690,9 @@ fail_exit (int code) + #endif /* ENABLE_SUBIDS */ + + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "modifying account", +- user_name, AUDIT_NO_ID, 0); ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "modify-account", ++ user_name, AUDIT_NO_ID, SHADOW_AUDIT_FAILURE); + #endif + exit (code); + } +@@ -762,9 +762,12 @@ update_group(const struct group *grp) + user_newname); + changed = true; + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing group member", +- user_newname, AUDIT_NO_ID, 1); ++ audit_logger_with_group ( ++ AUDIT_USER_MGMT, Prog, ++ "update-member-in-group", ++ user_newname, AUDIT_NO_ID, ++ ngrp->gr_name, ++ SHADOW_AUDIT_SUCCESS); + #endif + SYSLOG ((LOG_INFO, + "change '%s' to '%s' in group '%s'", +@@ -778,9 +781,11 @@ update_group(const struct group *grp) + ngrp->gr_mem = del_list (ngrp->gr_mem, user_name); + changed = true; + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "removing group member", +- user_name, AUDIT_NO_ID, 1); ++ audit_logger_with_group (AUDIT_USER_MGMT, Prog, ++ "delete-user-from-group", ++ user_name, AUDIT_NO_ID, ++ ngrp->gr_name, ++ SHADOW_AUDIT_SUCCESS); + #endif + SYSLOG ((LOG_INFO, + "delete '%s' from group '%s'", +@@ -793,9 +798,11 @@ update_group(const struct group *grp) + ngrp->gr_mem = add_list (ngrp->gr_mem, user_newname); + changed = true; + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "adding user to group", +- user_name, AUDIT_NO_ID, 1); ++ audit_logger_with_group (AUDIT_USER_MGMT, Prog, ++ "add-user-to-group", ++ user_name, AUDIT_NO_ID, ++ ngrp->gr_name, ++ SHADOW_AUDIT_SUCCESS); + #endif + SYSLOG ((LOG_INFO, "add '%s' to group '%s'", + user_newname, ngrp->gr_name)); +@@ -888,9 +895,10 @@ update_gshadow(const struct sgrp *sgrp) + nsgrp->sg_adm = add_list (nsgrp->sg_adm, user_newname); + changed = true; + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing admin name in shadow group", +- user_name, AUDIT_NO_ID, 1); ++ audit_logger_with_group (AUDIT_GRP_MGMT, Prog, ++ "update-admin-name-in-shadow-group", ++ user_name, AUDIT_NO_ID, nsgrp->sg_name, ++ SHADOW_AUDIT_SUCCESS); + #endif + SYSLOG ((LOG_INFO, + "change admin '%s' to '%s' in shadow group '%s'", +@@ -910,9 +918,10 @@ update_gshadow(const struct sgrp *sgrp) + user_newname); + changed = true; + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing member in shadow group", +- user_name, AUDIT_NO_ID, 1); ++ audit_logger_with_group (AUDIT_USER_MGMT, Prog, ++ "update-member-in-shadow-group", ++ user_name, AUDIT_NO_ID, ++ nsgrp->sg_name, 1); + #endif + SYSLOG ((LOG_INFO, + "change '%s' to '%s' in shadow group '%s'", +@@ -926,9 +935,10 @@ update_gshadow(const struct sgrp *sgrp) + nsgrp->sg_mem = del_list (nsgrp->sg_mem, user_name); + changed = true; + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "removing user from shadow group", +- user_name, AUDIT_NO_ID, 1); ++ audit_logger_with_group (AUDIT_USER_MGMT, Prog, ++ "delete-user-from-shadow-group", ++ user_name, AUDIT_NO_ID, ++ nsgrp->sg_name, 1); + #endif + SYSLOG ((LOG_INFO, + "delete '%s' from shadow group '%s'", +@@ -941,9 +951,10 @@ update_gshadow(const struct sgrp *sgrp) + nsgrp->sg_mem = add_list (nsgrp->sg_mem, user_newname); + changed = true; + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "adding user to shadow group", +- user_newname, AUDIT_NO_ID, 1); ++ audit_logger_with_group (AUDIT_USER_MGMT, Prog, ++ "add-user-to-shadow-group", ++ user_newname, AUDIT_NO_ID, ++ nsgrp->sg_name, 1); + #endif + SYSLOG ((LOG_INFO, "add '%s' to shadow group '%s'", + user_newname, nsgrp->sg_name)); +@@ -1852,8 +1863,8 @@ static void move_home (void) + + #ifdef WITH_AUDIT + if (uflg || gflg) { +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing home directory owner", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "updating-home-dir-owner", + user_newname, user_newid, 1); + } + #endif +@@ -1871,8 +1882,8 @@ static void move_home (void) + fail_exit (E_HOMEDIR); + } + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "moving home directory", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "moving-home-dir", + user_newname, user_newid, 1); + #endif + return; +@@ -1899,9 +1910,9 @@ static void move_home (void) + Prog, prefix_user_home); + } + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, ++ audit_logger (AUDIT_USER_MGMT, + Prog, +- "moving home directory", ++ "moving-home-dir", + user_newname, + user_newid, + 1); +@@ -2125,8 +2136,8 @@ static void move_mailbox (void) + } + #ifdef WITH_AUDIT + else { +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing mail file owner", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "updating-mail-file-owner", + user_newname, user_newid, 1); + } + #endif +@@ -2149,8 +2160,8 @@ static void move_mailbox (void) + } + #ifdef WITH_AUDIT + else { +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing mail file name", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "updating-mail-file-name", + user_newname, user_newid, 1); + } + +@@ -2347,8 +2358,8 @@ int main (int argc, char **argv) + _("%s: warning: the user name %s to %s SELinux user mapping failed.\n"), + Prog, user_name, user_selinux); + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "modifying User mapping ", ++ audit_logger (AUDIT_ROLE_ASSIGN, Prog, ++ "changing-selinux-user-mapping ", + user_name, user_id, + SHADOW_AUDIT_FAILURE); + #endif /* WITH_AUDIT */ +@@ -2360,8 +2371,8 @@ int main (int argc, char **argv) + _("%s: warning: the user name %s to SELinux user mapping removal failed.\n"), + Prog, user_name); + #ifdef WITH_AUDIT +- audit_logger (AUDIT_ADD_USER, Prog, +- "removing SELinux user mapping", ++ audit_logger (AUDIT_ROLE_REMOVE, Prog, ++ "delete-selinux-user-mapping", + user_name, user_id, + SHADOW_AUDIT_FAILURE); + #endif /* WITH_AUDIT */ +@@ -2404,8 +2415,8 @@ int main (int argc, char **argv) + */ + #ifdef WITH_AUDIT + if (uflg || gflg) { +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing home directory owner", ++ audit_logger (AUDIT_USER_MGMT, Prog, ++ "updating-home-dir-owner", + user_newname, user_newid, 1); + } + #endif diff --git a/shadow-4.15.1-sast-fixes.patch b/shadow-4.15.1-sast-fixes.patch new file mode 100644 index 0000000..e674ebf --- /dev/null +++ b/shadow-4.15.1-sast-fixes.patch @@ -0,0 +1,1413 @@ +From 4c16416ebc5f0958d58a1ea1e7890eafd9f8bb75 Mon Sep 17 00:00:00 2001 +From: Iker Pedrosa +Date: Wed, 15 May 2024 12:25:51 +0200 +Subject: [PATCH 01/16] port: fix OVERRUN (CWE-119) + +``` +shadow-4.15.0/lib/port.c:154:2: alias: Assigning: "port.pt_names" = "ttys". "port.pt_names" now points to element 0 of "ttys" (which consists of 65 8-byte elements). +shadow-4.15.0/lib/port.c:155:2: cond_const: Checking "j < 64" implies that "j" is 64 on the false branch. +shadow-4.15.0/lib/port.c:175:2: overrun-local: Overrunning array of 65 8-byte elements at element index 65 (byte offset 527) by dereferencing pointer "port.pt_names + (j + 1)". +173| *cp = '\0'; +174| cp++; +175|-> port.pt_names[j + 1] = NULL; +176| +177| /* +``` + +Resolves: https://issues.redhat.com/browse/RHEL-35383 + +Signed-off-by: Iker Pedrosa +Reviewed-by: Alejandro Colomar +--- + lib/port.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/port.c b/lib/port.c +index 05b95651..60ff8989 100644 +--- a/lib/port.c ++++ b/lib/port.c +@@ -168,7 +168,7 @@ again: + } + *cp = '\0'; + cp++; +- port.pt_names[j + 1] = NULL; ++ port.pt_names[j] = NULL; + + /* + * Get the list of user names. It is the second colon +-- +2.45.1 + + +From f8fc6371f69930bbd5801284256e182ba35ced2a Mon Sep 17 00:00:00 2001 +From: Alejandro Colomar +Date: Fri, 17 May 2024 14:05:31 +0200 +Subject: [PATCH 02/16] src/useradd.c: set_defaults(): Fix order of clean-ups + +Resources should be freed in the inverse order of the allocation. +This refactor prepares for the following commits, which fix some leaks. + +Reviewed-by: Iker Pedrosa +Signed-off-by: Alejandro Colomar +--- + src/useradd.c | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/src/useradd.c b/src/useradd.c +index 88d8ab7f..56a74559 100644 +--- a/src/useradd.c ++++ b/src/useradd.c +@@ -745,10 +745,9 @@ static int set_defaults (void) + def_create_mail_spool, def_log_init)); + ret = 0; + setdef_err: +- free(new_file); +- if (prefix[0]) { ++ if (prefix[0]) + free(default_file); +- } ++ free(new_file); + + return ret; + } +-- +2.45.1 + + +From 37ae8827a0869ee4a723954c3c9e7c48165d9b50 Mon Sep 17 00:00:00 2001 +From: Alejandro Colomar +Date: Fri, 17 May 2024 14:28:50 +0200 +Subject: [PATCH 03/16] src/useradd.c: set_defaults(): Rename goto label + +This will help add other labels in the following commits. + +Reviewed-by: Iker Pedrosa +Signed-off-by: Alejandro Colomar +--- + src/useradd.c | 24 +++++++++++++----------- + 1 file changed, 13 insertions(+), 11 deletions(-) + +diff --git a/src/useradd.c b/src/useradd.c +index 56a74559..bc72e6bc 100644 +--- a/src/useradd.c ++++ b/src/useradd.c +@@ -558,7 +558,7 @@ static int set_defaults (void) + fprintf(stderr, + _("%s: cannot create new defaults file: %s\n"), + Prog, strerror(errno)); +- goto setdef_err; ++ goto err_free_def; + } + } + +@@ -567,7 +567,7 @@ static int set_defaults (void) + fprintf (stderr, + _("%s: cannot create directory for defaults file\n"), + Prog); +- goto setdef_err; ++ goto err_free_def; + } + + ret = mkdir(dirname(new_file_dup), 0755); +@@ -576,7 +576,7 @@ static int set_defaults (void) + _("%s: cannot create directory for defaults file\n"), + Prog); + free(new_file_dup); +- goto setdef_err; ++ goto err_free_def; + } + free(new_file_dup); + +@@ -588,7 +588,7 @@ static int set_defaults (void) + fprintf (stderr, + _("%s: cannot create new defaults file\n"), + Prog); +- goto setdef_err; ++ goto err_free_def; + } + + ofp = fdopen (ofd, "w"); +@@ -596,7 +596,7 @@ static int set_defaults (void) + fprintf (stderr, + _("%s: cannot open new defaults file\n"), + Prog); +- goto setdef_err; ++ goto err_free_def; + } + + /* +@@ -623,7 +623,7 @@ static int set_defaults (void) + _("%s: line too long in %s: %s..."), + Prog, default_file, buf); + (void) fclose (ifp); +- goto setdef_err; ++ goto err_free_def; + } + } + +@@ -702,9 +702,10 @@ static int set_defaults (void) + (void) fflush (ofp); + if ( (ferror (ofp) != 0) + || (fsync (fileno (ofp)) != 0) +- || (fclose (ofp) != 0)) { ++ || (fclose (ofp) != 0)) ++ { + unlink (new_file); +- goto setdef_err; ++ goto err_free_def; + } + + /* +@@ -718,7 +719,7 @@ static int set_defaults (void) + _("%s: Cannot create backup file (%s): %s\n"), + Prog, buf, strerror (err)); + unlink (new_file); +- goto setdef_err; ++ goto err_free_def; + } + + /* +@@ -729,7 +730,7 @@ static int set_defaults (void) + fprintf (stderr, + _("%s: rename: %s: %s\n"), + Prog, new_file, strerror (err)); +- goto setdef_err; ++ goto err_free_def; + } + #ifdef WITH_AUDIT + audit_logger (AUDIT_USYS_CONFIG, Prog, +@@ -744,7 +745,8 @@ static int set_defaults (void) + def_inactive, def_expire, def_template, + def_create_mail_spool, def_log_init)); + ret = 0; +- setdef_err: ++ ++err_free_def: + if (prefix[0]) + free(default_file); + free(new_file); +-- +2.45.1 + + +From 701fe4cf1aeac9e66fa949369c91d135dbf375d2 Mon Sep 17 00:00:00 2001 +From: Alejandro Colomar +Date: Fri, 17 May 2024 13:10:46 +0200 +Subject: [PATCH 04/16] src/useradd.c: set_defaults(): Do not free(3) the + result of asprintf(3) if it failed +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +See asprintf(3): + +RETURN VALUE + When successful, these functions return the number of bytes + printed, just like sprintf(3). If memory allocation wasn’t possi‐ + ble, or some other error occurs, these functions will return -1, + and the contents of strp are undefined. + +Reviewed-by: Iker Pedrosa +Signed-off-by: Alejandro Colomar +--- + src/useradd.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/useradd.c b/src/useradd.c +index bc72e6bc..6a3edfe3 100644 +--- a/src/useradd.c ++++ b/src/useradd.c +@@ -558,7 +558,7 @@ static int set_defaults (void) + fprintf(stderr, + _("%s: cannot create new defaults file: %s\n"), + Prog, strerror(errno)); +- goto err_free_def; ++ goto err_free_new; + } + } + +@@ -749,6 +749,7 @@ static int set_defaults (void) + err_free_def: + if (prefix[0]) + free(default_file); ++err_free_new: + free(new_file); + + return ret; +-- +2.45.1 + + +From a74c4b6ae124a55cd272e574e0d056102f331e17 Mon Sep 17 00:00:00 2001 +From: Alejandro Colomar +Date: Fri, 17 May 2024 13:14:31 +0200 +Subject: [PATCH 05/16] src/useradd.c: De-duplicate code + +Reviewed-by: Iker Pedrosa +Signed-off-by: Alejandro Colomar +--- + src/useradd.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/src/useradd.c b/src/useradd.c +index 6a3edfe3..ad2676c1 100644 +--- a/src/useradd.c ++++ b/src/useradd.c +@@ -571,14 +571,13 @@ static int set_defaults (void) + } + + ret = mkdir(dirname(new_file_dup), 0755); ++ free(new_file_dup); + if (-1 == ret && EEXIST != errno) { + fprintf (stderr, + _("%s: cannot create directory for defaults file\n"), + Prog); +- free(new_file_dup); + goto err_free_def; + } +- free(new_file_dup); + + /* + * Create a temporary file to copy the new output to. +-- +2.45.1 + + +From e7d1508e076bbf4053faacc0370c6fe43d9c8f04 Mon Sep 17 00:00:00 2001 +From: Alejandro Colomar +Date: Fri, 17 May 2024 13:40:58 +0200 +Subject: [PATCH 06/16] src/useradd.c: Add fmkstemp() to fix file-descriptor + leak + +This function creates a temporary file, and returns a FILE pointer to +it. This avoids dealing with both a file descriptor and a FILE pointer, +and correctly deallocating the resources on error. + +The code before this patch was leaking the file descriptor if fdopen(3) +failed. + +Reviewed-by: Iker Pedrosa +Signed-off-by: Alejandro Colomar +--- + src/useradd.c | 34 ++++++++++++++++++++++++---------- + 1 file changed, 24 insertions(+), 10 deletions(-) + +diff --git a/src/useradd.c b/src/useradd.c +index ad2676c1..e0238457 100644 +--- a/src/useradd.c ++++ b/src/useradd.c +@@ -238,6 +238,9 @@ static void create_home (void); + static void create_mail (void); + static void check_uid_range(int rflg, uid_t user_id); + ++static FILE *fmkstemp(char *template); ++ ++ + /* + * fail_exit - undo as much as possible + */ +@@ -524,7 +527,6 @@ static void show_defaults (void) + */ + static int set_defaults (void) + { +- int ofd; + int ret = -1; + bool out_group = false; + bool out_groups = false; +@@ -582,15 +584,7 @@ static int set_defaults (void) + /* + * Create a temporary file to copy the new output to. + */ +- ofd = mkstemp (new_file); +- if (-1 == ofd) { +- fprintf (stderr, +- _("%s: cannot create new defaults file\n"), +- Prog); +- goto err_free_def; +- } +- +- ofp = fdopen (ofd, "w"); ++ ofp = fmkstemp(new_file); + if (NULL == ofp) { + fprintf (stderr, + _("%s: cannot open new defaults file\n"), +@@ -2752,3 +2746,23 @@ int main (int argc, char **argv) + return E_SUCCESS; + } + ++ ++static FILE * ++fmkstemp(char *template) ++{ ++ int fd; ++ FILE *fp; ++ ++ fd = mkstemp(template); ++ if (fd == -1) ++ return NULL; ++ ++ fp = fdopen(fd, "w"); ++ if (fp == NULL) { ++ close(fd); ++ unlink(template); ++ return NULL; ++ } ++ ++ return fp; ++} +-- +2.45.1 + + +From 1ee066ae1e5b39ac42120ad0f6f8af0f102db952 Mon Sep 17 00:00:00 2001 +From: Alejandro Colomar +Date: Fri, 17 May 2024 13:52:07 +0200 +Subject: [PATCH 07/16] src/useradd.c: set_defaults(): Fix FILE* leak + +Report: +> shadow-4.15.0/src/useradd.c:575:2: alloc_fn: Storage is returned from allocation function "fdopen". +> shadow-4.15.0/src/useradd.c:575:2: var_assign: Assigning: "ofp" = storage returned from "fdopen(ofd, "w")". +> shadow-4.15.0/src/useradd.c:734:2: leaked_storage: Variable "ofp" going out of scope leaks the storage it points to. +> 732| } +> 733| +> 734|-> return ret; +> 735| } +> 736| + +Link: +Reported-by: Iker Pedrosa +Reviewed-by: Iker Pedrosa +Signed-off-by: Alejandro Colomar +--- + src/useradd.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/useradd.c b/src/useradd.c +index e0238457..347334a6 100644 +--- a/src/useradd.c ++++ b/src/useradd.c +@@ -615,7 +615,8 @@ static int set_defaults (void) + fprintf (stderr, + _("%s: line too long in %s: %s..."), + Prog, default_file, buf); +- (void) fclose (ifp); ++ fclose(ifp); ++ fclose(ofp); + goto err_free_def; + } + } +-- +2.45.1 + + +From 151f14ad69de8100d25c1974947d53ae40d1448a Mon Sep 17 00:00:00 2001 +From: Alejandro Colomar +Date: Thu, 16 May 2024 13:52:15 +0200 +Subject: [PATCH 08/16] src/usermod.c: Reduce scope of local variables + +Signed-off-by: Alejandro Colomar +--- + src/usermod.c | 22 +++++++++++----------- + 1 file changed, 11 insertions(+), 11 deletions(-) + +diff --git a/src/usermod.c b/src/usermod.c +index 0fcf0325..57b58f5b 100644 +--- a/src/usermod.c ++++ b/src/usermod.c +@@ -687,11 +687,8 @@ fail_exit (int code) + + static void update_group (void) + { +- bool is_member; +- bool was_member; +- bool changed; +- const struct group *grp; +- struct group *ngrp; ++ bool changed; ++ const struct group *grp; + + changed = false; + +@@ -700,6 +697,9 @@ static void update_group (void) + * the user is a member of. + */ + while ((grp = gr_next ()) != NULL) { ++ bool is_member; ++ bool was_member; ++ struct group *ngrp; + /* + * See if the user specified this group as one of their + * concurrent groups. +@@ -799,12 +799,8 @@ static void update_group (void) + #ifdef SHADOWGRP + static void update_gshadow (void) + { +- bool is_member; +- bool was_member; +- bool was_admin; +- bool changed; +- const struct sgrp *sgrp; +- struct sgrp *nsgrp; ++ bool changed; ++ const struct sgrp *sgrp; + + changed = false; + +@@ -813,6 +809,10 @@ static void update_gshadow (void) + * that the user is a member of. + */ + while ((sgrp = sgr_next ()) != NULL) { ++ bool is_member; ++ bool was_member; ++ bool was_admin; ++ struct sgrp *nsgrp; + + /* + * See if the user was a member of this group +-- +2.45.1 + + +From b089a63ab38f69c32d099320fe8181802f7f4092 Mon Sep 17 00:00:00 2001 +From: Alejandro Colomar +Date: Thu, 16 May 2024 13:49:34 +0200 +Subject: [PATCH 09/16] src/usermod.c: Rename update_group() => + update_group_file() + +Signed-off-by: Alejandro Colomar +--- + src/usermod.c | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/src/usermod.c b/src/usermod.c +index 57b58f5b..aaa83d7d 100644 +--- a/src/usermod.c ++++ b/src/usermod.c +@@ -178,7 +178,7 @@ NORETURN static void usage (int status); + static void new_pwent (struct passwd *); + static void new_spent (struct spwd *); + NORETURN static void fail_exit (int); +-static void update_group (void); ++static void update_group_file(void); + + #ifdef SHADOWGRP + static void update_gshadow (void); +@@ -685,7 +685,8 @@ fail_exit (int code) + } + + +-static void update_group (void) ++static void ++update_group_file(void) + { + bool changed; + const struct group *grp; +@@ -950,7 +951,7 @@ static void update_gshadow (void) + */ + static void grp_update (void) + { +- update_group (); ++ update_group_file(); + #ifdef SHADOWGRP + if (is_shadow_grp) { + update_gshadow (); +-- +2.45.1 + + +From 81bc78ec5cdd59790bc7c591c9d1f66bd4d7b78e Mon Sep 17 00:00:00 2001 +From: Alejandro Colomar +Date: Fri, 17 May 2024 02:11:22 +0200 +Subject: [PATCH 10/16] src/usermod.c: Rename update_gshadow() => + update_gshadow_file() + +Signed-off-by: Alejandro Colomar +--- + src/usermod.c | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/src/usermod.c b/src/usermod.c +index aaa83d7d..3048f801 100644 +--- a/src/usermod.c ++++ b/src/usermod.c +@@ -181,7 +181,7 @@ NORETURN static void fail_exit (int); + static void update_group_file(void); + + #ifdef SHADOWGRP +-static void update_gshadow (void); ++static void update_gshadow_file(void); + #endif + static void grp_update (void); + +@@ -798,7 +798,8 @@ update_group_file(void) + } + + #ifdef SHADOWGRP +-static void update_gshadow (void) ++static void ++update_gshadow_file(void) + { + bool changed; + const struct sgrp *sgrp; +@@ -954,7 +955,7 @@ static void grp_update (void) + update_group_file(); + #ifdef SHADOWGRP + if (is_shadow_grp) { +- update_gshadow (); ++ update_gshadow_file(); + } + #endif + } +-- +2.45.1 + + +From 61964aa06b9e6e0643a6519f64290f18ac04867f Mon Sep 17 00:00:00 2001 +From: Alejandro Colomar +Date: Thu, 16 May 2024 13:54:06 +0200 +Subject: [PATCH 11/16] src/usermod.c: update_group_file(): Fix RESOURCE_LEAK + (CWE-772) + +Report: +> shadow-4.15.0/src/usermod.c:734:3: alloc_fn: Storage is returned from allocation function "__gr_dup". +> shadow-4.15.0/src/usermod.c:734:3: var_assign: Assigning: "ngrp" = storage returned from "__gr_dup(grp)". +> shadow-4.15.0/src/usermod.c:815:1: leaked_storage: Variable "ngrp" going out of scope leaks the storage it points to. +> 813| gr_free(ngrp); +> 814| } +> 815|-> } +> 816| +> 817| #ifdef SHADOWGRP + +Link: https://issues.redhat.com/browse/RHEL-35383 +Reported-by: Iker Pedrosa +Signed-off-by: Alejandro Colomar +--- + src/usermod.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/usermod.c b/src/usermod.c +index 3048f801..e0cfdd83 100644 +--- a/src/usermod.c ++++ b/src/usermod.c +@@ -780,9 +780,8 @@ update_group_file(void) + SYSLOG ((LOG_INFO, "add '%s' to group '%s'", + user_newname, ngrp->gr_name)); + } +- if (!changed) { +- continue; +- } ++ if (!changed) ++ goto free_ngrp; + + changed = false; + if (gr_update (ngrp) == 0) { +@@ -793,6 +792,7 @@ update_group_file(void) + fail_exit (E_GRP_UPDATE); + } + ++free_ngrp: + gr_free(ngrp); + } + } +-- +2.45.1 + + +From 71a3238b7996285fc3c8dec841244ba95d663fa5 Mon Sep 17 00:00:00 2001 +From: Alejandro Colomar +Date: Fri, 17 May 2024 02:15:15 +0200 +Subject: [PATCH 12/16] src/usermod.c: update_gshadow_file(): Fix RESOURCE_LEAK + (CWE-772) + +Report: +> shadow-4.15.0/src/usermod.c:864:3: alloc_fn: Storage is returned from allocation function "__sgr_dup". +> shadow-4.15.0/src/usermod.c:864:3: var_assign: Assigning: "nsgrp" = storage returned from "__sgr_dup(sgrp)". +> shadow-4.15.0/src/usermod.c:964:1: leaked_storage: Variable "nsgrp" going out of scope leaks the storage it points to. +> 962| free (nsgrp); +> 963| } +> 964|-> } +> 965| #endif /* SHADOWGRP */ +> 966| + +Link: https://issues.redhat.com/browse/RHEL-35383 +Reported-by: Iker Pedrosa +Signed-off-by: Alejandro Colomar +--- + src/usermod.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/usermod.c b/src/usermod.c +index e0cfdd83..bb5d3535 100644 +--- a/src/usermod.c ++++ b/src/usermod.c +@@ -921,9 +921,8 @@ update_gshadow_file(void) + SYSLOG ((LOG_INFO, "add '%s' to shadow group '%s'", + user_newname, nsgrp->sg_name)); + } +- if (!changed) { +- continue; +- } ++ if (!changed) ++ goto free_nsgrp; + + changed = false; + +@@ -939,6 +938,7 @@ update_gshadow_file(void) + fail_exit (E_GRP_UPDATE); + } + ++free_nsgrp: + free (nsgrp); + } + } +-- +2.45.1 + + +From 68d42a8fbe42b89cf13d3f672ad8502dbaf05835 Mon Sep 17 00:00:00 2001 +From: Alejandro Colomar +Date: Thu, 16 May 2024 14:02:54 +0200 +Subject: [PATCH 13/16] src/usermod.c: update_group_file(): Reduce scope of + local variable + +After _every_ iteration, 'changed' is always 'false'. We don't need to +have it outside of the loop. + +See: + +$ grepc update_group_file . \ +| grep -e changed -e goto -e continue -e break -e free_ngrp -e '{' -e '}' \ +| pcre2grep -v -M '{\n\t*}'; +{ + bool changed; + changed = false; + while ((grp = gr_next ()) != NULL) { + if (!was_member && !is_member) { + continue; + } + if (was_member) { + if ((!Gflg) || is_member) { + if (lflg) { + changed = true; + } + } else { + changed = true; + } + } else if (is_member) { + changed = true; + } + if (!changed) + goto free_ngrp; + changed = false; +free_ngrp: + } +} + +This was already true in the commit that introduced the code: + +$ git show 45c6603cc:src/usermod.c \ +| grepc update_group \ +| grep -e changed -e goto -e break -e continue -e '\' -e '{' -e '}' \ +| pcre2grep -v -M '{\n\t*}'; +{ + int changed; + changed = 0; + while ((grp = gr_next())) { + * See if the user specified this group as one of their + if (!was_member && !is_member) + continue; + if (was_member && (!Gflg || is_member)) { + if (lflg) { + changed = 1; + } + } else if (was_member && Gflg && !is_member) { + changed = 1; + } else if (!was_member && Gflg && is_member) { + changed = 1; + } + if (!changed) + continue; + changed = 0; + } +} + +Fixes: 45c6603cc86c ("[svn-upgrade] Integrating new upstream version, shadow (19990709)") +Signed-off-by: Alejandro Colomar +--- + src/usermod.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/usermod.c b/src/usermod.c +index bb5d3535..30f47b8a 100644 +--- a/src/usermod.c ++++ b/src/usermod.c +@@ -688,19 +688,20 @@ fail_exit (int code) + static void + update_group_file(void) + { +- bool changed; + const struct group *grp; + +- changed = false; +- + /* + * Scan through the entire group file looking for the groups that + * the user is a member of. + */ + while ((grp = gr_next ()) != NULL) { ++ bool changed; + bool is_member; + bool was_member; + struct group *ngrp; ++ ++ changed = false; ++ + /* + * See if the user specified this group as one of their + * concurrent groups. +@@ -783,7 +784,6 @@ update_group_file(void) + if (!changed) + goto free_ngrp; + +- changed = false; + if (gr_update (ngrp) == 0) { + fprintf (stderr, + _("%s: failed to prepare the new %s entry '%s'\n"), +-- +2.45.1 + + +From da77a82ecbc90e89808f143e7fa2abb7650f50d7 Mon Sep 17 00:00:00 2001 +From: Alejandro Colomar +Date: Fri, 17 May 2024 02:19:46 +0200 +Subject: [PATCH 14/16] src/usermod.c: update_gshadow_file(): Reduce scope of + local variable + +After _every_ iteration, 'changed' is always 'false'. We don't need to +have it outside of the loop. + +See: + +$ grepc update_gshadow_file . \ +| grep -e changed -e goto -e continue -e break -e free_ngrp -e '{' -e '}' \ +| pcre2grep -v -M '{\n\t*}'; +{ + bool changed; + changed = false; + while ((sgrp = sgr_next ()) != NULL) { + if (!was_member && !was_admin && !is_member) { + continue; + } + if (was_admin && lflg) { + changed = true; + } + if (was_member) { + if ((!Gflg) || is_member) { + if (lflg) { + changed = true; + } + } else { + changed = true; + } + } else if (is_member) { + changed = true; + } + if (!changed) + goto free_nsgrp; + changed = false; + } +} + +This was already true in the commit that introduced the code: + +$ git show 45c6603cc:src/usermod.c \ +| grepc update_gshadow \ +| grep -e changed -e goto -e break -e continue -e '\' -e '{' -e '}' \ +| pcre2grep -v -M '{\n\t*}'; +{ + int changed; + changed = 0; + while ((sgrp = sgr_next())) { + * See if the user was a member of this group + * See if the user was an administrator of this group + * See if the user specified this group as one of their + if (!was_member && !was_admin && !is_member) + continue; + if (was_admin && lflg) { + changed = 1; + } + if (was_member && (!Gflg || is_member)) { + if (lflg) { + changed = 1; + } + } else if (was_member && Gflg && !is_member) { + changed = 1; + } else if (!was_member && Gflg && is_member) { + changed = 1; + } + if (!changed) + continue; + changed = 0; + } +} + +Fixes: 45c6603cc86c ("[svn-upgrade] Integrating new upstream version, shadow (19990709)") +Signed-off-by: Alejandro Colomar +--- + src/usermod.c | 8 +++----- + 1 file changed, 3 insertions(+), 5 deletions(-) + +diff --git a/src/usermod.c b/src/usermod.c +index 30f47b8a..7b1e0581 100644 +--- a/src/usermod.c ++++ b/src/usermod.c +@@ -801,21 +801,21 @@ free_ngrp: + static void + update_gshadow_file(void) + { +- bool changed; + const struct sgrp *sgrp; + +- changed = false; +- + /* + * Scan through the entire shadow group file looking for the groups + * that the user is a member of. + */ + while ((sgrp = sgr_next ()) != NULL) { ++ bool changed; + bool is_member; + bool was_member; + bool was_admin; + struct sgrp *nsgrp; + ++ changed = false; ++ + /* + * See if the user was a member of this group + */ +@@ -924,8 +924,6 @@ update_gshadow_file(void) + if (!changed) + goto free_nsgrp; + +- changed = false; +- + /* + * Update the group entry to reflect the changes. + */ +-- +2.45.1 + + +From adf37cccd0fa4ce7d05644514b0af57fe71905c3 Mon Sep 17 00:00:00 2001 +From: Alejandro Colomar +Date: Thu, 16 May 2024 14:12:09 +0200 +Subject: [PATCH 15/16] src/usermod.c: update_group(): Add helper function + +Keep the while loop in the outer function, and move the iteration code +to this new helper. This makes it a bit more readable. + +Cc: Iker Pedrosa +Signed-off-by: Alejandro Colomar +--- + src/usermod.c | 167 ++++++++++++++++++++++++++------------------------ + 1 file changed, 87 insertions(+), 80 deletions(-) + +diff --git a/src/usermod.c b/src/usermod.c +index 7b1e0581..4ea11376 100644 +--- a/src/usermod.c ++++ b/src/usermod.c +@@ -179,6 +179,7 @@ static void new_pwent (struct passwd *); + static void new_spent (struct spwd *); + NORETURN static void fail_exit (int); + static void update_group_file(void); ++static void update_group(const struct group *grp); + + #ifdef SHADOWGRP + static void update_gshadow_file(void); +@@ -694,109 +695,115 @@ update_group_file(void) + * Scan through the entire group file looking for the groups that + * the user is a member of. + */ +- while ((grp = gr_next ()) != NULL) { +- bool changed; +- bool is_member; +- bool was_member; +- struct group *ngrp; ++ while ((grp = gr_next()) != NULL) ++ update_group(grp); ++} + +- changed = false; + +- /* +- * See if the user specified this group as one of their +- * concurrent groups. +- */ +- was_member = is_on_list (grp->gr_mem, user_name); +- is_member = Gflg && ( (was_member && aflg) +- || is_on_list (user_groups, grp->gr_name)); ++static void ++update_group(const struct group *grp) ++{ ++ bool changed; ++ bool is_member; ++ bool was_member; ++ struct group *ngrp; + +- if (!was_member && !is_member) { +- continue; +- } ++ changed = false; + +- /* +- * If rflg+Gflg is passed in AKA -rG invert is_member flag, which removes +- * mentioned groups while leaving the others. +- */ +- if (Gflg && rflg) { +- is_member = !is_member; +- } ++ /* ++ * See if the user specified this group as one of their ++ * concurrent groups. ++ */ ++ was_member = is_on_list (grp->gr_mem, user_name); ++ is_member = Gflg && ( (was_member && aflg) ++ || is_on_list (user_groups, grp->gr_name)); + +- ngrp = __gr_dup (grp); +- if (NULL == ngrp) { +- fprintf (stderr, +- _("%s: Out of memory. Cannot update %s.\n"), +- Prog, gr_dbname ()); +- fail_exit (E_GRP_UPDATE); +- } ++ if (!was_member && !is_member) ++ return; + +- if (was_member) { +- if ((!Gflg) || is_member) { +- /* User was a member and is still a member +- * of this group. +- * But the user might have been renamed. +- */ +- if (lflg) { +- ngrp->gr_mem = del_list (ngrp->gr_mem, +- user_name); +- ngrp->gr_mem = add_list (ngrp->gr_mem, +- user_newname); +- changed = true; +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing group member", +- user_newname, AUDIT_NO_ID, 1); +-#endif +- SYSLOG ((LOG_INFO, +- "change '%s' to '%s' in group '%s'", +- user_name, user_newname, +- ngrp->gr_name)); +- } +- } else { +- /* User was a member but is no more a +- * member of this group. +- */ +- ngrp->gr_mem = del_list (ngrp->gr_mem, user_name); ++ /* ++ * If rflg+Gflg is passed in AKA -rG invert is_member flag, which removes ++ * mentioned groups while leaving the others. ++ */ ++ if (Gflg && rflg) { ++ is_member = !is_member; ++ } ++ ++ ngrp = __gr_dup (grp); ++ if (NULL == ngrp) { ++ fprintf (stderr, ++ _("%s: Out of memory. Cannot update %s.\n"), ++ Prog, gr_dbname ()); ++ fail_exit (E_GRP_UPDATE); ++ } ++ ++ if (was_member) { ++ if ((!Gflg) || is_member) { ++ /* User was a member and is still a member ++ * of this group. ++ * But the user might have been renamed. ++ */ ++ if (lflg) { ++ ngrp->gr_mem = del_list (ngrp->gr_mem, ++ user_name); ++ ngrp->gr_mem = add_list (ngrp->gr_mem, ++ user_newname); + changed = true; + #ifdef WITH_AUDIT + audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "removing group member", +- user_name, AUDIT_NO_ID, 1); ++ "changing group member", ++ user_newname, AUDIT_NO_ID, 1); + #endif + SYSLOG ((LOG_INFO, +- "delete '%s' from group '%s'", +- user_name, ngrp->gr_name)); ++ "change '%s' to '%s' in group '%s'", ++ user_name, user_newname, ++ ngrp->gr_name)); + } +- } else if (is_member) { +- /* User was not a member but is now a member this +- * group. ++ } else { ++ /* User was a member but is no more a ++ * member of this group. + */ +- ngrp->gr_mem = add_list (ngrp->gr_mem, user_newname); ++ ngrp->gr_mem = del_list (ngrp->gr_mem, user_name); + changed = true; + #ifdef WITH_AUDIT + audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "adding user to group", +- user_name, AUDIT_NO_ID, 1); ++ "removing group member", ++ user_name, AUDIT_NO_ID, 1); + #endif +- SYSLOG ((LOG_INFO, "add '%s' to group '%s'", +- user_newname, ngrp->gr_name)); ++ SYSLOG ((LOG_INFO, ++ "delete '%s' from group '%s'", ++ user_name, ngrp->gr_name)); + } +- if (!changed) +- goto free_ngrp; ++ } else if (is_member) { ++ /* User was not a member but is now a member this ++ * group. ++ */ ++ ngrp->gr_mem = add_list (ngrp->gr_mem, user_newname); ++ changed = true; ++#ifdef WITH_AUDIT ++ audit_logger (AUDIT_USER_CHAUTHTOK, Prog, ++ "adding user to group", ++ user_name, AUDIT_NO_ID, 1); ++#endif ++ SYSLOG ((LOG_INFO, "add '%s' to group '%s'", ++ user_newname, ngrp->gr_name)); ++ } ++ if (!changed) ++ goto free_ngrp; + +- if (gr_update (ngrp) == 0) { +- fprintf (stderr, +- _("%s: failed to prepare the new %s entry '%s'\n"), +- Prog, gr_dbname (), ngrp->gr_name); +- SYSLOG ((LOG_WARN, "failed to prepare the new %s entry '%s'", gr_dbname (), ngrp->gr_name)); +- fail_exit (E_GRP_UPDATE); +- } ++ if (gr_update (ngrp) == 0) { ++ fprintf (stderr, ++ _("%s: failed to prepare the new %s entry '%s'\n"), ++ Prog, gr_dbname (), ngrp->gr_name); ++ SYSLOG ((LOG_WARN, "failed to prepare the new %s entry '%s'", gr_dbname (), ngrp->gr_name)); ++ fail_exit (E_GRP_UPDATE); ++ } + + free_ngrp: +- gr_free(ngrp); +- } ++ gr_free(ngrp); + } + ++ + #ifdef SHADOWGRP + static void + update_gshadow_file(void) +-- +2.45.1 + + +From d8e6a8b99b4d844328d875287babf6e13860d464 Mon Sep 17 00:00:00 2001 +From: Alejandro Colomar +Date: Fri, 17 May 2024 02:29:46 +0200 +Subject: [PATCH 16/16] src/usermod.c: update_gshadow(): Add helper function + +Keep the while loop in the outer function, and move the iteration code +to this new helper. This makes it a bit more readable. + +Cc: Iker Pedrosa +Signed-off-by: Alejandro Colomar +--- + src/usermod.c | 223 ++++++++++++++++++++++++++------------------------ + 1 file changed, 116 insertions(+), 107 deletions(-) + +diff --git a/src/usermod.c b/src/usermod.c +index 4ea11376..f8896984 100644 +--- a/src/usermod.c ++++ b/src/usermod.c +@@ -183,6 +183,7 @@ static void update_group(const struct group *grp); + + #ifdef SHADOWGRP + static void update_gshadow_file(void); ++static void update_gshadow(const struct sgrp *sgrp); + #endif + static void grp_update (void); + +@@ -814,141 +815,149 @@ update_gshadow_file(void) + * Scan through the entire shadow group file looking for the groups + * that the user is a member of. + */ +- while ((sgrp = sgr_next ()) != NULL) { +- bool changed; +- bool is_member; +- bool was_member; +- bool was_admin; +- struct sgrp *nsgrp; ++ while ((sgrp = sgr_next()) != NULL) ++ update_gshadow(sgrp); ++} ++#endif /* SHADOWGRP */ + +- changed = false; + +- /* +- * See if the user was a member of this group +- */ +- was_member = is_on_list (sgrp->sg_mem, user_name); ++#ifdef SHADOWGRP ++static void ++update_gshadow(const struct sgrp *sgrp) ++{ ++ bool changed; ++ bool is_member; ++ bool was_member; ++ bool was_admin; ++ struct sgrp *nsgrp; + +- /* +- * See if the user was an administrator of this group +- */ +- was_admin = is_on_list (sgrp->sg_adm, user_name); ++ changed = false; + +- /* +- * See if the user specified this group as one of their +- * concurrent groups. +- */ +- is_member = Gflg && ( (was_member && aflg) +- || is_on_list (user_groups, sgrp->sg_name)); ++ /* ++ * See if the user was a member of this group ++ */ ++ was_member = is_on_list (sgrp->sg_mem, user_name); + +- if (!was_member && !was_admin && !is_member) { +- continue; +- } ++ /* ++ * See if the user was an administrator of this group ++ */ ++ was_admin = is_on_list (sgrp->sg_adm, user_name); + +- /* +- * If rflg+Gflg is passed in AKA -rG invert is_member, to remove targeted +- * groups while leaving the user apart of groups not mentioned +- */ +- if (Gflg && rflg) { +- is_member = !is_member; +- } ++ /* ++ * See if the user specified this group as one of their ++ * concurrent groups. ++ */ ++ is_member = Gflg && ( (was_member && aflg) ++ || is_on_list (user_groups, sgrp->sg_name)); + +- nsgrp = __sgr_dup (sgrp); +- if (NULL == nsgrp) { +- fprintf (stderr, +- _("%s: Out of memory. Cannot update %s.\n"), +- Prog, sgr_dbname ()); +- fail_exit (E_GRP_UPDATE); +- } ++ if (!was_member && !was_admin && !is_member) ++ return; + +- if (was_admin && lflg) { +- /* User was an admin of this group but the user +- * has been renamed. +- */ +- nsgrp->sg_adm = del_list (nsgrp->sg_adm, user_name); +- nsgrp->sg_adm = add_list (nsgrp->sg_adm, user_newname); +- changed = true; +-#ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing admin name in shadow group", +- user_name, AUDIT_NO_ID, 1); +-#endif +- SYSLOG ((LOG_INFO, +- "change admin '%s' to '%s' in shadow group '%s'", +- user_name, user_newname, nsgrp->sg_name)); +- } +- +- if (was_member) { +- if ((!Gflg) || is_member) { +- /* User was a member and is still a member +- * of this group. +- * But the user might have been renamed. +- */ +- if (lflg) { +- nsgrp->sg_mem = del_list (nsgrp->sg_mem, +- user_name); +- nsgrp->sg_mem = add_list (nsgrp->sg_mem, +- user_newname); +- changed = true; ++ /* ++ * If rflg+Gflg is passed in AKA -rG invert is_member, to remove targeted ++ * groups while leaving the user apart of groups not mentioned ++ */ ++ if (Gflg && rflg) { ++ is_member = !is_member; ++ } ++ ++ nsgrp = __sgr_dup (sgrp); ++ if (NULL == nsgrp) { ++ fprintf (stderr, ++ _("%s: Out of memory. Cannot update %s.\n"), ++ Prog, sgr_dbname ()); ++ fail_exit (E_GRP_UPDATE); ++ } ++ ++ if (was_admin && lflg) { ++ /* User was an admin of this group but the user ++ * has been renamed. ++ */ ++ nsgrp->sg_adm = del_list (nsgrp->sg_adm, user_name); ++ nsgrp->sg_adm = add_list (nsgrp->sg_adm, user_newname); ++ changed = true; + #ifdef WITH_AUDIT +- audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "changing member in shadow group", +- user_name, AUDIT_NO_ID, 1); ++ audit_logger (AUDIT_USER_CHAUTHTOK, Prog, ++ "changing admin name in shadow group", ++ user_name, AUDIT_NO_ID, 1); + #endif +- SYSLOG ((LOG_INFO, +- "change '%s' to '%s' in shadow group '%s'", +- user_name, user_newname, +- nsgrp->sg_name)); +- } +- } else { +- /* User was a member but is no more a +- * member of this group. +- */ +- nsgrp->sg_mem = del_list (nsgrp->sg_mem, user_name); ++ SYSLOG ((LOG_INFO, ++ "change admin '%s' to '%s' in shadow group '%s'", ++ user_name, user_newname, nsgrp->sg_name)); ++ } ++ ++ if (was_member) { ++ if ((!Gflg) || is_member) { ++ /* User was a member and is still a member ++ * of this group. ++ * But the user might have been renamed. ++ */ ++ if (lflg) { ++ nsgrp->sg_mem = del_list (nsgrp->sg_mem, ++ user_name); ++ nsgrp->sg_mem = add_list (nsgrp->sg_mem, ++ user_newname); + changed = true; + #ifdef WITH_AUDIT + audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "removing user from shadow group", +- user_name, AUDIT_NO_ID, 1); ++ "changing member in shadow group", ++ user_name, AUDIT_NO_ID, 1); + #endif + SYSLOG ((LOG_INFO, +- "delete '%s' from shadow group '%s'", +- user_name, nsgrp->sg_name)); ++ "change '%s' to '%s' in shadow group '%s'", ++ user_name, user_newname, ++ nsgrp->sg_name)); + } +- } else if (is_member) { +- /* User was not a member but is now a member this +- * group. ++ } else { ++ /* User was a member but is no more a ++ * member of this group. + */ +- nsgrp->sg_mem = add_list (nsgrp->sg_mem, user_newname); ++ nsgrp->sg_mem = del_list (nsgrp->sg_mem, user_name); + changed = true; + #ifdef WITH_AUDIT + audit_logger (AUDIT_USER_CHAUTHTOK, Prog, +- "adding user to shadow group", +- user_newname, AUDIT_NO_ID, 1); ++ "removing user from shadow group", ++ user_name, AUDIT_NO_ID, 1); + #endif +- SYSLOG ((LOG_INFO, "add '%s' to shadow group '%s'", +- user_newname, nsgrp->sg_name)); ++ SYSLOG ((LOG_INFO, ++ "delete '%s' from shadow group '%s'", ++ user_name, nsgrp->sg_name)); + } +- if (!changed) +- goto free_nsgrp; +- +- /* +- * Update the group entry to reflect the changes. ++ } else if (is_member) { ++ /* User was not a member but is now a member this ++ * group. + */ +- if (sgr_update (nsgrp) == 0) { +- fprintf (stderr, +- _("%s: failed to prepare the new %s entry '%s'\n"), +- Prog, sgr_dbname (), nsgrp->sg_name); +- SYSLOG ((LOG_WARN, "failed to prepare the new %s entry '%s'", +- sgr_dbname (), nsgrp->sg_name)); +- fail_exit (E_GRP_UPDATE); +- } ++ nsgrp->sg_mem = add_list (nsgrp->sg_mem, user_newname); ++ changed = true; ++#ifdef WITH_AUDIT ++ audit_logger (AUDIT_USER_CHAUTHTOK, Prog, ++ "adding user to shadow group", ++ user_newname, AUDIT_NO_ID, 1); ++#endif ++ SYSLOG ((LOG_INFO, "add '%s' to shadow group '%s'", ++ user_newname, nsgrp->sg_name)); ++ } ++ if (!changed) ++ goto free_nsgrp; + +-free_nsgrp: +- free (nsgrp); ++ /* ++ * Update the group entry to reflect the changes. ++ */ ++ if (sgr_update (nsgrp) == 0) { ++ fprintf (stderr, ++ _("%s: failed to prepare the new %s entry '%s'\n"), ++ Prog, sgr_dbname (), nsgrp->sg_name); ++ SYSLOG ((LOG_WARN, "failed to prepare the new %s entry '%s'", ++ sgr_dbname (), nsgrp->sg_name)); ++ fail_exit (E_GRP_UPDATE); + } ++ ++free_nsgrp: ++ free (nsgrp); + } + #endif /* SHADOWGRP */ + ++ + /* + * grp_update - add user to secondary group set + * +-- +2.45.1 + diff --git a/shadow-4.15.1-useradd-fix-write-full-return.patch b/shadow-4.15.1-useradd-fix-write-full-return.patch new file mode 100644 index 0000000..64e2ef8 --- /dev/null +++ b/shadow-4.15.1-useradd-fix-write-full-return.patch @@ -0,0 +1,34 @@ +From 8903b94c86c978e8abef623358fd3e4629c06967 Mon Sep 17 00:00:00 2001 +From: Iker Pedrosa +Date: Mon, 9 Sep 2024 10:36:17 +0200 +Subject: [PATCH] useradd: fix write_full() return value + +write_full() returns -1 on error and useradd was checking another value. + +Closes: https://github.com/shadow-maint/shadow/issues/1072 +Fixes: f45498a6c286 ("libmisc/write_full.c: Improve write_full()") + +Reported-by: +Suggested-by: +Reviewed-by: Alejandro Colomar +Reviewed-by: Iker Pedrosa +--- + src/useradd.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/useradd.c b/src/useradd.c +index 02c500d0..d64fd892 100644 +--- a/src/useradd.c ++++ b/src/useradd.c +@@ -2042,7 +2042,7 @@ static void lastlog_reset (uid_t uid) + return; + } + if ( (lseek (fd, offset_uid, SEEK_SET) != offset_uid) +- || (write_full (fd, &ll, sizeof (ll)) != (ssize_t) sizeof (ll)) ++ || (write_full (fd, &ll, sizeof (ll)) == -1) + || (fsync (fd) != 0)) { + fprintf (stderr, + _("%s: failed to reset the lastlog entry of UID %lu: %s\n"), +-- +2.46.0 + diff --git a/shadow-utils-configure-gshadow.patch b/shadow-utils-configure-gshadow.patch new file mode 100644 index 0000000..a983ce0 --- /dev/null +++ b/shadow-utils-configure-gshadow.patch @@ -0,0 +1,20 @@ +The missing #include causes the configure check to fail +spuriously, resulting in HAVE_SHADOWGRP not being defined. + +Submitted upstream: + +diff --git a/configure.ac b/configure.ac +index 924254a0c8171802..6c7d9839979e037d 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -116,6 +116,10 @@ if test "$ac_cv_header_shadow_h" = "yes"; then + ac_cv_libc_shadowgrp, + AC_RUN_IFELSE([AC_LANG_SOURCE([ + #include ++ #ifdef HAVE_GSHADOW_H ++ #include ++ #endif ++ int + main() + { + struct sgrp *sg = sgetsgent("test:x::"); diff --git a/shadow-utils.spec b/shadow-utils.spec index 7bc7e8b..78fe9b9 100644 --- a/shadow-utils.spec +++ b/shadow-utils.spec @@ -1,12 +1,12 @@ Summary: Utilities for managing accounts and shadow password files Name: shadow-utils -Version: 4.20.0 -Release: 1%{?dist} +Version: 4.15.1 +Release: 4%{?dist} Epoch: 2 License: BSD-3-Clause AND GPL-2.0-or-later URL: https://github.com/shadow-maint/shadow -Source0: https://github.com/shadow-maint/shadow/releases/download/4.20.0/shadow-4.20.0.tar.xz -Source1: https://github.com/shadow-maint/shadow/releases/download/4.20.0/shadow-4.20.0.tar.xz.asc +Source0: https://github.com/shadow-maint/shadow/releases/download/v%{version}/shadow-%{version}.tar.xz +Source1: https://github.com/shadow-maint/shadow/releases/download/v%{version}/shadow-%{version}.tar.xz.asc Source2: shadow-utils.useradd Source3: shadow-utils.login.defs Source4: shadow-bsd.txt @@ -16,10 +16,22 @@ Source7: passwd.pamd ### Globals ### %global includesubiddir %{_includedir}/shadow -# Fail linking if there are undefined symbols. -%global _ld_strict_symbol_defs 1 ### Patches ### +# Misc manual page changes - non-upstreamable +Patch0: shadow-4.15.0-manfix.patch +# Date parsing improvement - could be upstreamed +Patch1: shadow-4.15.0-date-parsing.patch +# Several patches already available upstream. PRs:#994, #996, #997 +Patch2: shadow-4.15.1-sast-fixes.patch +# Audit message changes - partially upstreamed +Patch3: shadow-4.15.1-audit-update.patch +# Probably non-upstreamable +Patch4: shadow-4.15.0-account-tools-setuid.patch +# https://github.com/shadow-maint/shadow/commit/ead55e9ba8958504e23e29545f90c4dd925c7462 +Patch5: shadow-4.15.0-getdef-spurious-error.patch +# https://github.com/shadow-maint/shadow/commit/8903b94c86c978e8abef623358fd3e4629c06967 +Patch6: shadow-4.15.1-useradd-fix-write-full-return.patch ### Dependencies ### Requires: audit-libs >= 1.6.5 @@ -41,15 +53,12 @@ BuildRequires: git BuildRequires: itstool BuildRequires: libacl-devel BuildRequires: libattr-devel -BuildRequires: libcmocka-devel BuildRequires: libeconf-devel BuildRequires: libselinux-devel >= 1.25.2-1 BuildRequires: libsemanage-devel BuildRequires: libtool -BuildRequires: libxcrypt-devel BuildRequires: libxslt BuildRequires: make -BuildRequires: pam BuildRequires: pam-devel ### Provides ### @@ -57,19 +66,6 @@ Provides: shadow = %{epoch}:%{version}-%{release} Provides: passwd = 0.80-18 Obsoletes: passwd <= 0.80-19 -%if "%{_sbindir}" == "%{_bindir}" -# Compat symlinks for Requires in other packages. -# We rely on filesystem to create the symlinks for us. -Requires: filesystem(unmerged-sbin-symlinks) -Provides: /usr/sbin/adduser -Provides: /usr/sbin/useradd -Provides: /usr/sbin/userdel -Provides: /usr/sbin/usermod -Provides: /usr/sbin/groupadd -Provides: /usr/sbin/groupdel -Provides: /usr/sbin/groupmod -%endif - %description The shadow-utils package includes the necessary programs for converting UNIX password files to the shadow password format, plus @@ -77,14 +73,16 @@ programs for managing user and group accounts. The pwconv command converts passwords to the shadow password format. The pwunconv command unconverts shadow passwords and generates a passwd file (a standard UNIX password file). The pwck command checks the integrity of password -and shadow files. The useradd, userdel, and usermod commands are used -for managing user accounts. The groupadd, groupdel, and groupmod -commands are used for managing group accounts. +and shadow files. The lastlog command prints out the last login times +for all users. The useradd, userdel, and usermod commands are used for +managing user accounts. The groupadd, groupdel, and groupmod commands +are used for managing group accounts. ### Subpackages ### %package subid Summary: A library to manage subordinate uid and gid ranges +License: BSD and GPLv2+ %description subid Utility library that provides a way to manage subid ranges. @@ -92,13 +90,14 @@ Utility library that provides a way to manage subid ranges. %package subid-devel Summary: Development package for shadow-utils-subid +License: BSD and GPLv2+ Requires: shadow-utils-subid = %{epoch}:%{version}-%{release} %description subid-devel Development files for shadow-utils-subid. %prep -%autosetup -p 1 -S git -n shadow-4.20.0 +%autosetup -p 1 -S git -n shadow-%{version} iconv -f ISO88591 -t utf-8 doc/HOWTO > doc/HOWTO.utf8 cp -f doc/HOWTO.utf8 doc/HOWTO @@ -106,38 +105,45 @@ cp -f doc/HOWTO.utf8 doc/HOWTO cp -a %{SOURCE4} %{SOURCE5} . cp -a %{SOURCE6} man/login.defs.d/HOME_MODE.xml +# Force regeneration of getdate.c +rm lib/getdate.c + %build +%ifarch sparc64 +#sparc64 need big PIE +export CFLAGS="$RPM_OPT_FLAGS -fPIE" +export LDFLAGS="-pie -Wl,-z,relro -Wl,-z,now" +%else +export CFLAGS="$RPM_OPT_FLAGS -fpie" +export LDFLAGS="-pie -Wl,-z,relro -Wl,-z,now" +%endif + autoreconf %configure \ - --disable-account-tools-setuid \ - --enable-logind=no \ - --enable-man \ --enable-shadowgrp \ - --enable-shared \ + --enable-man \ --with-audit \ - --with-bcrypt \ - --with-group-name-max-length=32 \ --with-libpam \ - --with-selinux \ --with-sha-crypt \ + --with-bcrypt \ --with-yescrypt \ + --with-selinux \ --without-libbsd \ --without-libcrack \ - --without-nscd \ - --without-sssd + --without-sssd \ + --enable-shared \ + --with-group-name-max-length=32 \ + --enable-lastlog \ + --enable-logind=no \ + --disable-account-tools-setuid %make_build -%check -make check - %install %make_install gnulocaledir=$RPM_BUILD_ROOT%{_datadir}/locale MKINSTALLDIRS=`pwd`/mkinstalldirs install -d -m 755 $RPM_BUILD_ROOT%{_sysconfdir}/default install -p -c -m 0644 %{SOURCE3} $RPM_BUILD_ROOT%{_sysconfdir}/login.defs install -p -c -m 0600 %{SOURCE2} $RPM_BUILD_ROOT%{_sysconfdir}/default/useradd install -d -m 755 $RPM_BUILD_ROOT%{_pam_confdir} -install -m 644 %{SOURCE7} $RPM_BUILD_ROOT%{_pam_confdir}/chpasswd -install -m 644 %{SOURCE7} $RPM_BUILD_ROOT%{_pam_confdir}/newusers install -m 644 %{SOURCE7} $RPM_BUILD_ROOT%{_pam_confdir}/passwd @@ -147,28 +153,34 @@ for subdir in $RPM_BUILD_ROOT%{_mandir}/{??,??_??,??_??.*}/man* ; do test -d $subdir && test -e $subdir/useradd.8 && echo ".so man8/useradd.8" > $subdir/adduser.8 done -%if "%{_sbindir}" == "%{_bindir}" -# The installation script doesn't actually use the configured paths :( -mv -v $RPM_BUILD_ROOT/usr/sbin/* $RPM_BUILD_ROOT%{_bindir}/ -%endif - # Remove binaries we don't use. rm $RPM_BUILD_ROOT%{_bindir}/chfn rm $RPM_BUILD_ROOT%{_bindir}/chsh +rm $RPM_BUILD_ROOT%{_bindir}/expiry +rm $RPM_BUILD_ROOT%{_bindir}/groups rm $RPM_BUILD_ROOT%{_bindir}/login rm $RPM_BUILD_ROOT%{_bindir}/su rm $RPM_BUILD_ROOT%{_bindir}/faillog +rm $RPM_BUILD_ROOT%{_sbindir}/logoutd rm $RPM_BUILD_ROOT%{_sbindir}/nologin rm $RPM_BUILD_ROOT%{_mandir}/man1/chfn.* rm $RPM_BUILD_ROOT%{_mandir}/*/man1/chfn.* rm $RPM_BUILD_ROOT%{_mandir}/man1/chsh.* rm $RPM_BUILD_ROOT%{_mandir}/*/man1/chsh.* +rm $RPM_BUILD_ROOT%{_mandir}/man1/expiry.* +rm $RPM_BUILD_ROOT%{_mandir}/*/man1/expiry.* +rm $RPM_BUILD_ROOT%{_mandir}/man1/groups.* +rm $RPM_BUILD_ROOT%{_mandir}/*/man1/groups.* rm $RPM_BUILD_ROOT%{_mandir}/man1/login.* rm $RPM_BUILD_ROOT%{_mandir}/*/man1/login.* rm $RPM_BUILD_ROOT%{_mandir}/man1/su.* rm $RPM_BUILD_ROOT%{_mandir}/*/man1/su.* rm $RPM_BUILD_ROOT%{_mandir}/man5/passwd.* rm $RPM_BUILD_ROOT%{_mandir}/*/man5/passwd.* +rm $RPM_BUILD_ROOT%{_mandir}/man5/suauth.* +rm $RPM_BUILD_ROOT%{_mandir}/*/man5/suauth.* +rm $RPM_BUILD_ROOT%{_mandir}/man8/logoutd.* +rm $RPM_BUILD_ROOT%{_mandir}/*/man8/logoutd.* rm $RPM_BUILD_ROOT%{_mandir}/man8/nologin.* rm $RPM_BUILD_ROOT%{_mandir}/*/man8/nologin.* rm $RPM_BUILD_ROOT%{_mandir}/man3/getspnam.* @@ -180,8 +192,11 @@ rm $RPM_BUILD_ROOT%{_mandir}/*/man8/faillog.* # Remove PAM service files we don't use. rm $RPM_BUILD_ROOT%{_pam_confdir}/chfn +rm $RPM_BUILD_ROOT%{_pam_confdir}/chpasswd rm $RPM_BUILD_ROOT%{_pam_confdir}/chsh +rm $RPM_BUILD_ROOT%{_pam_confdir}/groupmems rm $RPM_BUILD_ROOT%{_pam_confdir}/login +rm $RPM_BUILD_ROOT%{_pam_confdir}/newusers rm $RPM_BUILD_ROOT%{_pam_confdir}/su find $RPM_BUILD_ROOT%{_mandir} -depth -type d -empty -delete @@ -208,12 +223,11 @@ rm -f $RPM_BUILD_ROOT/%{_libdir}/libsubid.a %license gpl-2.0.txt shadow-bsd.txt %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/login.defs %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/default/useradd -%config(noreplace) %{_pam_confdir}/chpasswd -%config(noreplace) %{_pam_confdir}/newusers %config(noreplace) %{_pam_confdir}/passwd %{_bindir}/sg %attr(4755,root,root) %{_bindir}/chage %attr(4755,root,root) %{_bindir}/gpasswd +%{_bindir}/lastlog %attr(4755,root,root) %{_bindir}/newgrp %attr(0755,root,root) %caps(cap_setgid=ep) %{_bindir}/newgidmap %attr(0755,root,root) %caps(cap_setuid=ep) %{_bindir}/newuidmap @@ -251,6 +265,7 @@ rm -f $RPM_BUILD_ROOT/%{_libdir}/libsubid.a %{_mandir}/man8/chgpasswd.8* %{_mandir}/man8/newusers.8* %{_mandir}/man8/*conv.8* +%{_mandir}/man8/lastlog.8* %{_mandir}/man8/vipw.8* %{_mandir}/man8/vigr.8* @@ -264,138 +279,12 @@ rm -f $RPM_BUILD_ROOT/%{_libdir}/libsubid.a %{_libdir}/libsubid.so %changelog -* Wed Jul 29 2026 Iker Pedrosa - 2:4.20.0-1 -- Rebase to version 4.20.0 - -* Wed Jul 22 2026 Iker Pedrosa - 2:4.20.0-rc3-1 -- Rebase to version 4.20.0-rc3 - -* Fri Jul 17 2026 Fedora Release Engineering - 2:4.19.3-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_45_Mass_Rebuild - -* Wed May 27 2026 Iker Pedrosa - 2:4.19.3-3 -- Enable use of PAM for chpasswd and newusers - Resolves: #2461179 and #2283963 - -* Thu Apr 23 2026 Iker Pedrosa - 2:4.19.3-2 -- btrfs: simplify checks improve useradd behavior for non-btrfs - -* Wed Feb 11 2026 Debarshi Ray - 2:4.19.3-1 -- Rebase to version 4.19.3 - Resolves: #2426288 - -* Tue Jan 27 2026 Adam Williamson - 2:4.19.0-6 -- chkhash.c: fix escaping in SHA-256 / SHA-512 / MD5 regexes - -* Mon Jan 26 2026 Iker Pedrosa - 2:4.19.0-5 -- chkhash.c: fix support for ! and * in hashes -- usermod.c: add back optimizations - -* Sat Jan 17 2026 Fedora Release Engineering - 2:4.19.0-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_44_Mass_Rebuild - -* Mon Jan 12 2026 Iker Pedrosa - 2:4.19.0-3 -- useradd: Support config for creating home dirs as Btrfs subvolumes - -* Fri Jan 9 2026 Iker Pedrosa - 2:4.19.0-2 -- Enable unit-tests - -* Fri Jan 9 2026 Iker Pedrosa - 2:4.19.0-1 -- Rebase to version 4.19.0 - Resolves: #2426288 and #2249524 - -* Tue Nov 25 2025 Adam Williamson - 2:4.18.0-7 -- Also revert changes from -4 (last known good was -3) - -* Tue Nov 25 2025 Adam Williamson - 2:4.18.0-6 -- Revert changes from -5 (they were only meant for testing) - -* Tue Nov 25 2025 Iker Pedrosa - 2:4.18.0-5 -- Test CI - -* Fri Oct 31 2025 Iker Pedrosa - 2:4.18.0-4 -- Stop setting SELinux labels in chroot and prefix environments - Resolves: #2249524 - -* Tue Jul 29 2025 Alexey Tikhonov - 2:4.18.0-3 -- Revert "Stop assigning subids by default" - Resolves: #2382662 - -* Fri Jul 25 2025 Fedora Release Engineering - 2:4.18.0-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_43_Mass_Rebuild - -* Tue Jul 22 2025 Iker Pedrosa - 2:4.18.0-1 -- Rebase to version 4.18.0. Resolves: #2374710 - -* Fri Jul 18 2025 Iker Pedrosa - 2:4.17.4-5 -- Stop assigning subids by default - Resolves: CVE-2024-56433 and #2334168 - -* Tue Jul 15 2025 Iker Pedrosa - 2:4.17.4-4 -- FSWC: Migrate to lastlog2 - Link: - Resolves: #2361588 - -* Tue Mar 25 2025 Iker Pedrosa - 2:4.17.4-2 -- Add pam dependency for _pam_confdir missing macro. Resolves: #2354806 - -* Thu Mar 20 2025 Iker Pedrosa - 2:4.17.4-1 -- Rebase to version 4.17.4. Resolves: #2353491 -- Fixes problems with expiration dates - -* Sat Feb 01 2025 Björn Esser - 2:4.17.0-5 -- Add explicit BR: libxcrypt-devel - -* Sun Jan 19 2025 Fedora Release Engineering - 2:4.17.0-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_42_Mass_Rebuild - -* Sun Jan 12 2025 Zbigniew Jędrzejewski-Szmek - 2:4.17.0-3 -- Rebuilt for the bin-sbin merge (2nd attempt) - -* Thu Dec 26 2024 Iker Pedrosa - 2:4.17.0 -- Rebase to version 4.17.0. Resolves: #2293678 - -* Sun Dec 22 2024 Björn Esser - 2:4.17.0~rc1-2 -- Remove potentially dangerous {C,LD}FLAGS shenanigans -- Fail linking if there are undefined symbols at link-time - -* Mon Dec 9 2024 Iker Pedrosa - 2:4.17.0~rc1-1 -- Rebase to version 4.17.0-rc1 - -* Tue Nov 12 2024 Iker Pedrosa - 2:4.16.0-7 -- SPDX license migration for subpackages - -* Wed Oct 16 2024 Iker Pedrosa - 2:4.16.0-6 -- Rebuilt for libeconf soname bump - -* Mon Oct 7 2024 Iker Pedrosa - 2:4.16.0-5 +* Tue Oct 15 2024 Iker Pedrosa - 2:4.15.1-4 - useradd: fix write_full() return value. Resolves: #2313559 -* Fri Sep 13 2024 Iker Pedrosa - 2:4.16.0-3 -- Disable nscd - -* Wed Aug 28 2024 Iker Pedrosa - 2:4.16.0-2 -- Rebase to version 4.16.0 (#2293678) - -* Tue Jul 23 2024 Kevin Fenzi - 2:4.15.1-9 -- Revert chpasswd: use PAM again for now. - -* Sat Jul 20 2024 Fedora Release Engineering - 2:4.15.1-8 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_41_Mass_Rebuild - -* Tue Jul 09 2024 Zbigniew Jędrzejewski-Szmek - 2:4.15.1-7 -- Rebuilt for the bin-sbin merge - -* Thu May 30 2024 Iker Pedrosa - 2:4.15.1-6 -- chpasswd: use PAM again (#2283963) - -* Mon May 20 2024 Iker Pedrosa - 2:4.15.1-5 +* Thu May 23 2024 Iker Pedrosa - 2:4.15.1-3 - Several fixes for issues detected by static analyzer tools -* Thu Apr 11 2024 Zbigniew Jedrzejewski-Szmek - 2:4.15.1-4 -- Fix build with %%_bindir==%%_sbindir -- Add virtual Provides for old file paths used by other packages - * Thu Mar 21 2024 Iker Pedrosa - 2:4.15.1-2 - getdef: avoid spurious error messages about unknown configuration options. Resolves: #2265291 diff --git a/sources b/sources index 7669083..ca5a1c9 100644 --- a/sources +++ b/sources @@ -1,2 +1,2 @@ -SHA512 (shadow-4.20.0.tar.xz) = 0b8afded372e4d37a78f38cb972c0ab877870ef6356cdd1c45be3c708af3d1496c6f87de6bf6a5b1d217d4d52d7e2d15c28b26530949cc6de0231a4028930406 -SHA512 (shadow-4.20.0.tar.xz.asc) = e4b134543768f323df30a1e450c22086d013020928d62bd3207db7f633beea3587b67802da5b575d5a794e8c2bebd7a0abeba6ed69d44bf08adc37df3bf352ac +SHA512 (shadow-4.15.1.tar.xz) = 2667a1d781066adce42e684463329c3f32d28c07b7b79b628525bffcd61c078f6ff430be3d42976d0509d9f9a55cd80f5a50d479b85155476cee7f00f06708d8 +SHA512 (shadow-4.15.1.tar.xz.asc) = 0a39d6a45b7d8df12aade89ed9fc9d481c91297dbd34e85fe831426c1d0051cbcf8478759306b8871cd6b1835604c5836decf398d0165c50ac52fee365561446 diff --git a/tests/mhc-fedora-ci.yaml b/tests/mhc-fedora-ci.yaml deleted file mode 100644 index 8747e67..0000000 --- a/tests/mhc-fedora-ci.yaml +++ /dev/null @@ -1,13 +0,0 @@ -provisioned_topologies: -- shadow -domains: -- id: shadow - hosts: - - hostname: localhost - role: shadow - conn: - type: ssh - host: localhost - user: root - artifacts: - - /var/log/* diff --git a/tests/sanity/Makefile b/tests/sanity/Makefile new file mode 100644 index 0000000..386221b --- /dev/null +++ b/tests/sanity/Makefile @@ -0,0 +1,77 @@ +# Copyright (c) 2006 Red Hat, Inc. All rights reserved. This copyrighted material +# is made available to anyone wishing to use, modify, copy, or +# redistribute it subject to the terms and conditions of the GNU General +# Public License v.2. +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Author: Jakub Hrozek + +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# +# Example Makefile for RHTS # +# This example is geared towards a test for a specific package # +# It does most of the work for you, but may require further coding # +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# + +# The toplevel namespace within which the test lives. +TOPLEVEL_NAMESPACE=CoreOS + +# The name of the package under test: +PACKAGE_NAME=shadow-utils + +# The path of the test below the package: +RELATIVE_PATH=sanity + +# Version of the Test. Used with make tag. +export TESTVERSION=1.1 + +# The combined namespace of the test. +export TEST=/$(TOPLEVEL_NAMESPACE)/$(PACKAGE_NAME)/$(RELATIVE_PATH) + +# A phony target is one that is not really the name of a file. +# It is just a name for some commands to be executed when you +# make an explicit request. There are two reasons to use a +# phony target: to avoid a conflict with a file of the same +# name, and to improve performance. +.PHONY: all install download clean + +# Executables to be built should be added here, they will be generated on the system under test. +BUILT_FILES= + +# Data files, .c files, scripts anything needed to either compile the test and/or run it. +FILES=$(METADATA) Makefile PURPOSE sanity_test.py runtest.sh + +run: $(FILES) build + ./runtest.sh + +build: $(BUILT_FILES) + chmod a+x ./sanity_test.py + chmod a+x ./runtest.sh + +clean: + rm -f *~ *.rpm $(BUILT_FILES) + +# Include Common Makefile +include /usr/share/rhts/lib/rhts-make.include + +# Generate the testinfo.desc here: +$(METADATA): Makefile + @touch $(METADATA) + @echo "Owner: Jakub Hrozek " > $(METADATA) + @echo "Name: $(TEST)" >> $(METADATA) + @echo "Path: $(TEST_DIR)" >> $(METADATA) + @echo "TestVersion: $(TESTVERSION)" >> $(METADATA) + @echo "License: GNU GPL" >> $(METADATA) + @echo "Description: Basic sanity test for shadow-utils" >> $(METADATA) + @echo "TestTime: 5m" >> $(METADATA) + @echo "RunFor: $(PACKAGE_NAME)" >> $(METADATA) + @echo "Requires: $(PACKAGE_NAME)" >> $(METADATA) + @echo "Requires: python" >> $(METADATA) + rhts-lint $(METADATA) + diff --git a/tests/sanity/PURPOSE b/tests/sanity/PURPOSE new file mode 100644 index 0000000..27062e1 --- /dev/null +++ b/tests/sanity/PURPOSE @@ -0,0 +1,10 @@ +This is a basic sanity test for the shadow-utils package. It is implemented +in python on top of the unittesting.py module. + +Its purpose is to ensure that the binaries in the shadow-utils package behave +as expected and its switches/options work correctly. + +For the most part, every binary in the shadow-utils package is represented by +a single class named Test, i.e. TestUsermod etc. There are some +exceptions, like TestUseraddWeirdNameTest though. + diff --git a/tests/sanity/runtest.sh b/tests/sanity/runtest.sh new file mode 100755 index 0000000..cb2a2b5 --- /dev/null +++ b/tests/sanity/runtest.sh @@ -0,0 +1,24 @@ +#!/bin/bash +. /usr/bin/rhts-environment.sh +. /usr/share/beakerlib/beakerlib.sh || exit 1 + +rlJournalStart +rlFileBackup --clean /etc/default/useradd- /etc/default/useradd +setenforce 0 +python sanity_test.py -v +setenforce 1 +rlFileRestore + +EXIT=$? +if [[ $EXIT -eq 0 ]]; then + RESULT="PASS" +else + RESULT="FAIL" +fi + + +rlJournalEnd + +echo "Result: $RESULT" +echo "Exit: $EXIT" +report_result $TEST $RESULT $EXIT diff --git a/tests/sanity/sanity_test.py b/tests/sanity/sanity_test.py new file mode 100755 index 0000000..e9c45c2 --- /dev/null +++ b/tests/sanity/sanity_test.py @@ -0,0 +1,1013 @@ +#!/usr/bin/env python +""" +A script that tests functionality of the shadow-utils package. + +Author: Jakub Hrozek, +License: GNU GPL v2 +Date: 2007 + +TODO: + * tests for password aging + * if something fails, print out the command issued for easier debugging + * test long options variants along with the short ones +""" + +import unittest +import pwd +import grp +import commands +import os +import os.path +import sys +import copy +import tempfile +import rpm +import shutil + +from UserDict import UserDict + +class RedHatVersion(object): + def __init__(self, type=None, version=None, release=None): + self.type = type + self.version = version + self.release = release + self.rhel = False + + def __eq__( self, other): + """ + Don't compare if either of the values is None + so we can do comparisons like 'is it fedora?' or 'is it rhel4?' + """ + ok = (self.type == other.type) + if ok == False: return False + + if self.version and other.version: + ok = (self.version == other.version) + if ok == False: return False + + if (self.release == other.release): + ok = (self.release == other.release) + + return ok + + def __ne__( self, other): + return not self.__eq__(other) + + def __get_fedora_info(self, mi): + return [ (h['version'],h['release']) for h in mi ][0] + + def __get_rhel_info(self, mi): + # The rules for RHEL versions are braindead..releases even more + ver_rpm, rel_rpm = [ (h['version'],h['release']) for h in mi ][0] + rhel_versions = { '3AS' : 3, '4AS' : 4, '5Server' : 5, '5Client' : 5, '6' : 6 } + if ver_rpm[:3] == '5.9' or ver_rpm[:1] == '6': # rhel6 prerelease and release hack + rhel_versions[ver_rpm] = 6 + if ver_rpm in rhel_versions.keys(): + return (rhel_versions[ver_rpm], rel_rpm) + + def is_rhel(self): + return self.rhel + + def get_info(self): + """ + Returns a tuple containing (type, version, release) of RHEL or Fedora. + Type is either RHEL or Fedora. + Returns None if it cannot parse the info + """ + + ts = rpm.TransactionSet() + mi = ts.dbMatch() + mi.pattern('name', rpm.RPMMIRE_GLOB, 'redhat-release*') + + if mi: + self.rhel = True + return ('RHEL',) + self.__get_rhel_info(mi) + else: + mi = ts.dbMatch('name','fedora-release') + self.rhel = False + if mi.count() != 0: + return ('Fedora',) + self.__get_fedora_info(mi) + + return None + + +class UserInfo(UserDict): + fields = { "pw_name" : 0, "pw_passwd" : 1, "pw_uid" : 2, "pw_gid" : 3, + "pw_gecos" : 4, "pw_dir" : 5, "pw_shell" : 6 } + + def __init__(self): + UserDict.__init__(self) + for f in UserInfo.fields: self[f] = None + + def __getitem__(self, key): + return UserDict.__getitem__(self, key) + + def __setitem__(self, key, value): + UserDict.__setitem__(self, key, value) + + def __cmp__(self, other): + return UserDict.__cmp__(self, other) + + def __repr__(self): + return " ; ".join( [ "%s => %s" % (k, v) for k, v in self.data.items() ] ) + + def __parse_info(self, struct): + for f in UserInfo.fields: + self[f] = struct[UserInfo.fields[f]] + + def get_info_uid(self, uid): + self.__parse_info(pwd.getpwuid(uid)) + + def get_info_name(self, name): + try: + self.__parse_info(pwd.getpwnam(name)) + except KeyError: + return None + + def lazy_compare(self, pattern): + """ Compare pattern against self. If any field in pattern is set + to None, it is automatically considered equal with the corresponding + field in self. """ + for field in UserInfo.fields: + if pattern[field] and pattern[field] != self[field]: + return False + + return True + +class GroupInfo(UserDict): + fields = { "gr_name" : 0, "gr_passwd" : 1, + "gr_gid" : 2, "gr_mem" : 3} + + def __init__(self): + UserDict.__init__(self) + for f in GroupInfo.fields: self[f] = None + + def __getitem__(self, key): + return UserDict.__getitem__(self, key) + + def __setitem__(self, key, value): + UserDict.__setitem__(self, key, value) + + def __cmp__(self, other): + return UserDict.__cmp__(self, other) + + def __repr__(self): + return " ; ".join( [ "%s => %s" % (k, v) for k, v in self.data.items() ] ) + + def __parse_info(self, struct): + for f in GroupInfo.fields: + self[f] = struct[GroupInfo.fields[f]] + + def get_info_gid(self, gid): + self.__parse_info(grp.getgrgid(gid)) + + def get_info_name(self, name): + self.__parse_info(grp.getgrnam(name)) + + def lazy_compare(self, pattern): + """ Compare pattern against self. If any field in pattern is set + to None, it is automatically considered equal with the corresponding + field in self. """ + for field in GroupInfo.fields: + if pattern[field] and pattern[field] != self[field]: + return False + + return True + +class LoginDefsParser(UserDict): + "A quick-n-dirty way how to fetch the defaults from /etc/login.defs into a dictionary" + + def __getitem__(self, key): + try: + return UserDict.__getitem__(self, key) + except KeyError: + # if a name-value is not defined in the config file, return defaults + if key == "CREATE_MAIL_SPOOL": + return "yes" + if key == "UMASK": + return "077" + + def __init__(self, path="/etc/login.defs",split=None): + self.path = path + UserDict.__init__(self) + try: + defs = open(path) + except IOError: + print "Could not open the config file %s" % (path) + + for line in defs: + if line.startswith('#'): continue + fields = line.split(split) + if len(fields) != 2: continue # yeah, we're dirty + self.data[fields[0]] = fields[1] + + def serialize(self): + output = open(self.path, "w+") + for k,v in self.data.items(): + output.write("%s=%s" % (k, v)) + + output.write("\n") + output.close() + +class TestUserInfo(unittest.TestCase): + def testLazyCompare(self): + """ (test sanity): Test comparing two UserInfo records """ + a = UserInfo() + a["pw_name"] = "foo" + a["pw_uid"] = 555 + b = copy.deepcopy(a) + c = UserInfo() + + self.assertEqual(a.lazy_compare(b), True) + self.assertEqual(a.lazy_compare(c), True) + + c["pw_name"] = "foo" + c["pw_uid"] = None + self.assertEqual(a.lazy_compare(c), True) + self.assertEqual(c.lazy_compare(a), False) + + c["pw_name"] = "bar" + self.assertNotEqual(a.lazy_compare(c), True) + + def testGetInfoUid(self): + """ (test sanity): Test getting user info based on his UID """ + a = UserInfo() + a.get_info_uid(0) + self.assertEqual(a["pw_name"], "root") + + def testGetInfoName(self): + """ (test sanity): Test getting user info based on his name """ + a = UserInfo() + a.get_info_name("root") + self.assertEqual(a["pw_uid"], 0) + +class ShadowUtilsTestBase: + """ Handy routines """ + def getDefaults(self): + # get the default values for so we can compare against that + (status, defaults_str) = commands.getstatusoutput('useradd -D') + if status != 0: + raise RuntimeError("Could not get the default values for useradd") + return dict([ rec.split("=") for rec in defaults_str.split("\n") ]) + + def getDefaultUserInfo(self, username): + expected = UserInfo() + defaults = self.getDefaults() + + expected["pw_name"] = username + expected["pw_dir"] = defaults["HOME"] + "/" + username + expected["pw_shell"] = defaults["SHELL"] + + return expected + +class TestUseradd(ShadowUtilsTestBase, unittest.TestCase): + def setUp(self): + self.username = "test-shadow-utils-useradd" + + def tearDown(self): + commands.getstatusoutput("userdel -r %s" % (self.username)) + + def testBasicAdd(self): + """ useradd: Tests basic adding of a user """ + expected = self.getDefaultUserInfo(self.username) + + runme = "useradd %s" % (self.username) + (status, output) = commands.getstatusoutput(runme) + self.failUnlessEqual(status, 0, output) + + created = UserInfo() + created.get_info_name(self.username) + self.assertEqual(created.lazy_compare(expected), True, "FAIL: Could not add a user\nIssued command: %s" % (runme)) + + def testExistingUser(self): + """ useradd: Test that user with an existing name cannot be added """ + (status, output) = commands.getstatusoutput("useradd %s" % (self.username)) + self.failUnlessEqual(status, 0, output) + self.assertNotEqual(commands.getstatusoutput("useradd %s" % (self.username))[0], 0, "FAIL: User that already exists added") + + def testCustomUID(self): + """ useradd: Adding an user with a specific UID """ + UID = 23456 # FIXME - test for a free UID slot first + + expected = self.getDefaultUserInfo(self.username) + expected["pw_uid"] = UID + + runme = "useradd %s -u %d" % (self.username, UID) + (status, output) = commands.getstatusoutput(runme) + self.failUnlessEqual(status, 0, "Issued command: %s\n" % (runme) + "Got from useradd: %s\n" % (output)) + + created = UserInfo() + created.get_info_name(self.username) + self.assertEqual(created.lazy_compare(expected), True, "FAIL: Could not add a user with a specific UID\nIssued command: %s" % (runme)) + + def testNegativeUID(self): + """ useradd: Tests that user cannot have a negative UID assigned """ + self.assertNotEqual(commands.getstatusoutput("useradd %s --uid -5" % (self.username))[0], 0, "FAIL: User with UID < 0 added") + + def testCustomExistingUID(self): + """ useradd: Adding a user with a specific existing UID """ + UID = 32112 + + expected = self.getDefaultUserInfo(self.username) + expected["pw_uid"] = UID + + (status_u, output_u) = commands.getstatusoutput("useradd %s -u %d" % (self.username, UID)) + + # must fail without -o flag + (status_u_no_o, output_u_no_o) = commands.getstatusoutput("useradd foo -u %d" % (UID)) + + # must pass with -o flag + (status_o, output_o) = commands.getstatusoutput("useradd foo -u %d -o" % (UID)) + + # clean up + (status, output) = commands.getstatusoutput("userdel -r foo") + + self.failUnlessEqual(status_u, 0, "FAIL: cannot add an user with a specified UID\n"+output_u) + self.assertEqual(status_o, 0, "FAIL: cannot add an user with an existing UID using the -o flag\n"+output_o) + self.failUnlessEqual(status, 0, output) + self.assertNotEqual(status_u_no_o, 0, "FAIL: user with an existing UID added\n"+output_u_no_o) + + def testCustomGID(self): + """ useradd: Adding an user with a specific GID """ + GID = 100 # users group should be everywhere - should we test before? + expected = self.getDefaultUserInfo(self.username) + expected["pw_gid"] = GID + + (status, output) = commands.getstatusoutput("useradd %s -g %d" % (self.username, GID)) + self.failUnlessEqual(status, 0, output) + + created = UserInfo() + created.get_info_name(self.username) + self.assertEqual(created.lazy_compare(expected), True, "FAIL: Could not add a user with a specific GID") + + def testCustomShell(self): + """ useradd: Adding an user with a specific login shell """ + shell = "/bin/ksh" + expected = self.getDefaultUserInfo(self.username) + expected["pw_shell"] = shell + + (status, output) = commands.getstatusoutput("useradd %s -s %s" % (self.username, shell)) + self.failUnlessEqual(status, 0, output) + + created = UserInfo() + created.get_info_name(self.username) + self.assertEqual(created.lazy_compare(expected), True, "FAIL: Could not add a user with a specific shell") + + def testCustomHome(self): + """ useradd: Adding an user with a specific home directory """ + home = "/tmp/useradd-test" + os.mkdir(home) + expected = self.getDefaultUserInfo(self.username) + expected["pw_dir"] = home + + (status, output) = commands.getstatusoutput("useradd %s -d %s" % (self.username, home)) + shutil.rmtree(home) + self.failUnlessEqual(status, 0, output) + + created = UserInfo() + created.get_info_name(self.username) + self.assertEqual(created.lazy_compare(expected), True, "FAIL: Could not add a user with a specific home") + + def testSystemAccount(self): + """ useradd: Adding a system user (UID < UID_MIN from /etc/login.defs) """ + defaults = LoginDefsParser() + + # system account with no home dir + expected = self.getDefaultUserInfo(self.username) + + (status, output) = commands.getstatusoutput("useradd -r %s" % (self.username)) + self.failUnlessEqual(status, 0, output) + + created = UserInfo() + created.get_info_name(self.username) + self.assertEqual(os.path.exists(created["pw_dir"]), False, "FAIL: System user has a home dir created") + self.assertEqual(created["pw_uid"] < defaults['UID_MIN'], True, "FAIL: System user has UID > UID_MIN") + self.assertEqual(created.lazy_compare(expected), True, "FAIL: Could not add a system user") + + def testAddToMoreGroups(self): + """ useradd: Creating an user that belongs to more than one group """ + (status, output) = commands.getstatusoutput("useradd -G bin %s" % (self.username)) + self.failUnlessEqual(status, 0, output) + + gr_bin = GroupInfo() + gr_bin.get_info_name("bin") + self.assertEqual(self.username in gr_bin["gr_mem"], True, "FAIL: User not in supplementary group after usermod -G -a") + + + def testAddWithCommonName(self): + """ useradd: Specifying a comment (user for account name) """ + comment = "zzzzzz" + (status, output) = commands.getstatusoutput("useradd -c %s %s" % (comment, self.username)) + self.failUnlessEqual(status, 0, output) + + created = UserInfo() + created.get_info_name(self.username) + self.assertEqual(created["pw_gecos"], comment, "FAIL: failed to create a user with a GECOS comment") + + def testHomePermissions(self): + """ useradd: Check if permissions on newly created home dir match the umask """ + defaults = LoginDefsParser() + + (status, output) = commands.getstatusoutput("useradd %s" % (self.username)) + self.failUnlessEqual(status, 0, output) + + created = UserInfo() + created.get_info_name(self.username) + + import stat + perm = os.stat(created["pw_dir"])[stat.ST_MODE] + mode = int(oct(perm & 0777)) + + self.assertEqual(defaults["UMASK"], "077", "FAIL: umask setting is not sane - is %s, should be 077" % (defaults["UMASK"])) + self.assertEqual(int(defaults["UMASK"]) + mode , 777, "FAIL: newly-created home dir does not match the umask") + + def testCreateMailSpool(self): + """ useradd: Check whether the mail spool gets created when told to""" + # set up creating of mail spool + defaults = LoginDefsParser("/etc/default/useradd", split="=") + + create_mail = defaults["CREATE_MAIL_SPOOL"] + defaults["CREATE_MAIL_SPOOL"] = "yes" + defaults.serialize() + + login_defs = LoginDefsParser() + + (status, output) = commands.getstatusoutput("useradd %s" % (self.username)) + self.failUnlessEqual(status, 0, output) + + # clean up + defaults["CREATE_MAIL_SPOOL"] = create_mail + defaults.serialize() + self.assertEqual(os.path.exists(login_defs["MAIL_DIR"] + "/" + self.username), True, "FAIL: useradd did not create mail spool") + + def testDefaultMailSettings(self): + """ useradd: Check whether the mail spool is on by default""" + defaults = LoginDefsParser("/etc/default/useradd", split="=") + self.assertEqual(defaults["CREATE_MAIL_SPOOL"], "yes\n") + + def testNoLastlog(self): + """ useradd: Check if the -l option prevents from being added to the lastlog """ + pass # FIXME - add some code here + + +class TestUseraddWeirdNameTest(unittest.TestCase, ShadowUtilsTestBase): + """ Tests addition/removal of usernames that have proven to be problematic in the past. + The reason to separate these from the main useradd test suite is to not run the setUp + and tearDown methods """ + + def addAndRemove(self, username, success=True): + expected = self.getDefaultUserInfo(username) + expected["pw_name"] = username + + (status, output) = commands.getstatusoutput("useradd %s" % (username)) + if success: + self.failUnlessEqual(status, 0, output) + else: + self.failIfEqual(status, 0, output) + return True + + created = UserInfo() + created.get_info_name(username) + self.assertEqual(created.lazy_compare(expected), True, "FAIL: TestUseraddWeirdName::addAndRemove - could not add a user") + + # the cleanup method won't help this time + (status, output) = commands.getstatusoutput("userdel -r %s" % (username)) + self.failUnlessEqual(status, 0, output) + + def testNumericName(self): + """ useradd: Test if an user with a purely numerical name can be added (123) """ + return self.addAndRemove("123") + + def testSambaName(self): + """ useradd: Test if an user with a name with a dollar at the end can be added (joepublic$ ) """ + return self.addAndRemove("joepublic$") + + def testDotInName(self): + """ useradd: Test if an user with a name with a dot in it can be added (joe.public ) """ + return self.addAndRemove("joe.public") + + def testAtInName(self): + """ useradd: Test if an user with an '@' in name can be added (joe@public.com) - should fail """ + return self.addAndRemove("joe@public.com", False) + + def testUppercase(self): + """ useradd: Test if an user with UPPERCASE or Uppercase name can be added """ + return self.addAndRemove("JOEPUBLIC") + return self.addAndRemove("Joepublic") + +class TestUseraddDefaultsChange(unittest.TestCase, ShadowUtilsTestBase): + def testDefaultsChange(self): + """ useradd: Test overriding default settings (shell, home dir, group) with a -D option """ + save = self.getDefaults() + + new_defs = dict() + new_defs["SHELL"] = "/bin/ksh" + new_defs["GROUP"] = "1" + new_defs["HOME"] = "/tmp" + + command = "useradd -D -s%s -g%s -b%s" % (new_defs["SHELL"], new_defs["GROUP"], new_defs["HOME"]) + (status, output) = commands.getstatusoutput(command) + self.failUnlessEqual(status, 0, output) + + overriden = self.getDefaults() + [ self.assertEqual(overriden[k], new_defs[k]) for k in new_defs.keys() ] + + command = "useradd -D -s%s -g%s -b%s" % (save["SHELL"], save["GROUP"], save["HOME"]) + (status, output) = commands.getstatusoutput(command) + self.failUnlessEqual(status, 0, output) + + +class TestUserdel(unittest.TestCase, ShadowUtilsTestBase): + def setUp(self): + self.username = "test-shadow-utils-userdel" + (status, output) = commands.getstatusoutput("useradd %s" % (self.username)) + self.failUnlessEqual(status, 0, output) + + def testRemoveUserGroup(self): + """ userdel: test if userdel removes user's group when he's deleted - regression test for #201379 """ + (status, output) = commands.getstatusoutput("userdel -r %s" % (self.username)) + self.failUnlessEqual(status, 0, output) + + # This would fail if we did not have the group removed + (status, output) = commands.getstatusoutput("useradd %s" % (self.username)) + self.failUnlessEqual(status, 0, output) + + (status, output) = commands.getstatusoutput("userdel -r %s" % (self.username)) + self.failUnlessEqual(status, 0, output) + +class TestUsermod(unittest.TestCase, ShadowUtilsTestBase): + def setUp(self): + self.username = "test-shadow-utils-usermod" + (status, output) = commands.getstatusoutput("useradd %s" % (self.username)) + self.failUnlessEqual(status, 0, output) + + def tearDown(self): + (status, output) = commands.getstatusoutput("userdel -r %s" % (self.username)) + self.failUnlessEqual(status, 0, output) + + def testAppendToSupplementaryGroup(self): + """ usermod: Test if a user can be added to a supplementary group """ + add_group = "additional_group" + (status, output) = commands.getstatusoutput("groupadd %s" % (add_group)) + self.failUnlessEqual(status, 0, output) + + (status_mod, output_mod) = commands.getstatusoutput("usermod -a -G %s %s" % (add_group, self.username)) + add_group_info = GroupInfo() + add_group_info.get_info_name(add_group) + (status, output) = commands.getstatusoutput("groupdel %s" % (add_group)) + + self.failUnlessEqual(status, 0, output) + self.failUnlessEqual(status_mod, 0, output_mod) + self.assertEqual(self.username in add_group_info["gr_mem"], True, "User not in supplementary group after usermod -G --append") + + + def testAppendToSupplementaryGroupLongOption(self): + """ usermod: Test if a user can be added to a supplementary group via --append rather that -a (regression test for 222540) """ + # this is known to not work on older RHELs - test what we are running + rhv = RedHatVersion() + runs = rhv.get_info() + if rhv.is_rhel(): + if runs[1] < 5: + print "This test makes sense for RHEL5+" + return + else: + if runs[1] < 6: + print "This test makes sense for Fedora 6+" + return + + type, release, version = RedHatVersion().get_info() + if RedHatVersion().is_rhel(): + if release < 5 or (release == 5 and version < 2): + print "This test makes sense for RHEL 5.2+" + return + + add_group = "additional_group" + (status, output) = commands.getstatusoutput("groupadd %s" % (add_group)) + self.failUnlessEqual(status, 0, output) + + (status_mod, output_mod) = commands.getstatusoutput("usermod --append -G %s %s" % (add_group, self.username)) + add_group_info = GroupInfo() + add_group_info.get_info_name(add_group) + (status, output) = commands.getstatusoutput("groupdel %s" % (add_group)) + + self.failUnlessEqual(status, 0, output) + self.failUnlessEqual(status_mod, 0, output_mod) + self.assertEqual(self.username in add_group_info["gr_mem"], True, "User not in supplementary group after usermod -G --append") + + + def testNameChange(self): + """ usermod: Test if the comment field (used as the Common Name) can be changed """ + new_comment = "zzzzzz" + + (status, output) = commands.getstatusoutput("usermod -c %s %s" % (new_comment, self.username)) + self.failUnlessEqual(status, 0, output) + + created = UserInfo() + created.get_info_name(self.username) + + self.assertEqual(created["pw_gecos"], new_comment) + + def testHomeChange(self): + """ usermod: Test if user's home directory can be changed """ + new_home = "/tmp" + created = UserInfo() + created.get_info_name(self.username) + old_home = created["pw_dir"] + + (status, output) = commands.getstatusoutput("usermod -d %s %s" % (new_home, self.username)) + self.failUnlessEqual(status, 0, output) + + created.get_info_name(self.username) + self.assertEqual(created["pw_dir"], new_home) + + # revert to old home so we can userdel -r in tearDown + (status, output) = commands.getstatusoutput("usermod -d %s %s" % (old_home, self.username)) + self.failUnlessEqual(status, 0, output) + + # FIXME - test if contents of /home directories are transferred with the -m option + # FIXME - test if new home is created if does not exist before + + def testGIDChange(self): + """ usermod: Test if user's gid can be changed. """ + new_group = "root" + # test non-existing group + (status_fail, output_fail) = commands.getstatusoutput("usermod -g no-such-group %s" % (self.username)) + (status, output) = commands.getstatusoutput("usermod -g %s %s" % (new_group, self.username)) + + created = UserInfo() + created.get_info_name(self.username) + + left = GroupInfo() + if left.get_info_name(self.username) == None: + (status_del, output_del) = commands.getstatusoutput("groupdel %s" % (self.username)) + self.failUnlessEqual(status_del, 0, output_del) + + self.failIfEqual(status_fail, 0, output_fail) + self.failUnlessEqual(status, 0, output) + self.assertEqual(created["pw_gid"], 0) #0 is root group + + def testLoginChange(self): + """ usermod: Test if user's login can be changed """ + new_login = "usermod-login-change" + user = UserInfo() + user.get_info_name(self.username) + uid = user["pw_uid"] # UID won't change even when login does + + # test changing to an existing user name + (status, output) = commands.getstatusoutput("usermod -l root %s" % (self.username)) + self.failIfEqual(status, 0, output) + + (status, output) = commands.getstatusoutput("usermod -l %s %s" % (new_login, self.username)) + self.failUnlessEqual(status, 0, output) + user.get_info_name(new_login) + self.assertEqual(user["pw_uid"], uid) + + # revert so we can userdel -r on tearDown + (status, output) = commands.getstatusoutput("usermod -l %s %s" % (self.username, new_login)) + self.failUnlessEqual(status, 0, output) + + def testShellChange(self): + """ usermod: Test if user's shell can be changed """ + new_shell = "/bin/sh" + + (status, output) = commands.getstatusoutput("usermod -s %s %s" % (new_shell, self.username)) + self.failUnlessEqual(status, 0, output) + + created = UserInfo() + created.get_info_name(self.username) + self.assertEqual(created["pw_shell"], new_shell) + +class TestGroupadd(unittest.TestCase, ShadowUtilsTestBase): + def setUp(self): + self.groupname = "test-shadow-utils-groups" + + def tearDown(self): + commands.getstatusoutput("groupdel %s" % (self.groupname)) + + def testAddGroup(self): + """ groupadd: Basic adding of a group """ + + expected = GroupInfo() + expected["gr_name"] = self.groupname + + (status, output) = commands.getstatusoutput("groupadd %s" % (self.groupname)) + self.failUnlessEqual(status, 0, output) + + created = GroupInfo() + created.get_info_name(self.groupname) + self.assertEqual(created.lazy_compare(expected), True, "FAIL: Could not add a group") + + def testAddSystemGroup(self): + """ groupadd: Adding a system group with gid < MIN_GID """ + + expected = GroupInfo() + expected["gr_name"] = self.groupname + defaults = LoginDefsParser() + + (status, output) = commands.getstatusoutput("groupadd -r %s" % (self.groupname)) + self.failUnlessEqual(status, 0, output) + + created = GroupInfo() + created.get_info_name(self.groupname) + self.assertEqual(created["gr_gid"] < defaults["GID_MIN"], True, "FAIL: System group has gid >= GID_MIN") + self.assertEqual(created.lazy_compare(expected), True, "FAIL: Could not add a system group") + + def testAddExistingGid(self): + """ groupadd: Test if we group with an existing GID can be added """ + (status, output) = commands.getstatusoutput("groupadd %s" % (self.groupname)) + self.failUnlessEqual(status, 0, output) + + gname = "%s-2" % (self.groupname) + + created = GroupInfo() + created.get_info_name(self.groupname) + + # no -o option -> this should fail + (status, output) = commands.getstatusoutput("groupadd -g%s %s" % (created["gr_gid"], gname)) + self.failIfEqual(status, 0, output) + + # override with -o option, should pass now + (status, output) = commands.getstatusoutput("groupadd -g%s -o %s" % (created["gr_gid"], gname)) + self.failUnlessEqual(status, 0, output) + + # test if the new GID is really the same + same_gid = GroupInfo() + same_gid.get_info_name(gname) + self.assertEqual(same_gid["gr_gid"], created["gr_gid"]) + + # clean up + (status, output) = commands.getstatusoutput("groupdel %s" % (gname)) + self.failUnlessEqual(status, 0, output) + + + def testOverrideDefaults(self): + """ groupadd: Test if the defaults can be overriden with the -K option """ + # this is known to not work on older RHELs - test what we are running + rhv = RedHatVersion() + runs = rhv.get_info() + if rhv.is_rhel(): + if runs[1] < 5: + print "This test makes sense for RHEL5+" + return + else: + if runs[1] < 6: + print "This test makes sense for Fedora 6+" + return + + + GID_MIN = 600 + GID_MAX = 625 + + (status, output) = commands.getstatusoutput("groupadd -K GID_MIN=%d -K GID_MAX=%d %s" % + (GID_MIN, GID_MAX, self.groupname)) + self.failUnlessEqual(status, 0, output) + + created = GroupInfo() + created.get_info_name(self.groupname) + self.assertEqual(GID_MIN <= created["gr_gid"] <= GID_MAX, True, "FAIL: created an user with UID of %d" % (created["gr_gid"])) + + + def testFOption(self): + """ groupadd: Tests the -f option of groupadd """ + (status, output) = commands.getstatusoutput("groupadd %s" % (self.groupname)) + self.failUnlessEqual(status, 0, output) + + (status, output) = commands.getstatusoutput("groupadd -f %s" % (self.groupname)) + self.assertEqual(status, 0, output) + +class TestGroupaddInvalidName(unittest.TestCase, ShadowUtilsTestBase): + def testGroupaddInvalidName(self): + """ groupadd: Test adding of a group with an invalid name """ + (status, output) = commands.getstatusoutput("groupadd foo?") + self.assertNotEqual(status, 0, output) + (status, output) = commands.getstatusoutput("groupadd aaaaabbbbbcccccdddddeeeeefffffggg") #33 chars + self.assertNotEqual(status, 0, output) + +class TestGroupaddValidName(unittest.TestCase, ShadowUtilsTestBase): + def testGroupaddValidName(self): + """ groupadd: Test adding and removing of groups with maximal valid name and name ending with $ """ + (status, output) = commands.getstatusoutput("groupadd aaaaabbbbbcccccdddddeeeeefffffgg") #32 chars + self.assertEqual(status, 0, output) + (status, output) = commands.getstatusoutput("groupadd aaaaabbbbbcccccdddddeeeeefffffg\$") #32 chars + self.assertEqual(status, 0, output) + (status, output) = commands.getstatusoutput("groupdel aaaaabbbbbcccccdddddeeeeefffffgg") #32 chars + self.assertEqual(status, 0, output) + (status, output) = commands.getstatusoutput("groupdel aaaaabbbbbcccccdddddeeeeefffffg\$") #32 chars + self.assertEqual(status, 0, output) + + +class TestGroupmod(unittest.TestCase, ShadowUtilsTestBase): + def setUp(self): + self.groupname = "test-shadow-utils-groups" + (status, output) = commands.getstatusoutput("groupadd %s" % (self.groupname)) + self.failUnlessEqual(status, 0, output) + + def tearDown(self): + commands.getstatusoutput("groupdel %s" % (self.groupname)) + + def testChangeGID(self): + """ groupmod: Test changing a gid of a group """ + expected = GroupInfo() + expected["gr_name"] = self.groupname + expected["gr_gid"] = 54321 + + (status, output) = commands.getstatusoutput("groupmod -g%d %s" % (expected["gr_gid"], self.groupname)) + self.failUnlessEqual(status, 0, output) + + created = GroupInfo() + created.get_info_name(self.groupname) + self.assertEqual(created.lazy_compare(expected), True, "FAIL: Could not change GID of an existing group") + + def testChangeGIDToExistingValue(self): + """ groupmod: Test changing GID to an existing value """ + second_name = "%s-2" % (self.groupname) + + created = GroupInfo() + created.get_info_name(self.groupname) + + expected = GroupInfo() + expected["gr_name"] = self.groupname + expected["gr_gid"] = created["gr_gid"] + + (status, output) = commands.getstatusoutput("groupadd %s" % (second_name)) + self.failUnlessEqual(status, 0, output) + + # try to assingn GID of the first group to the second - this should fail without the -o option + (status, output) = commands.getstatusoutput("groupmod -g%d %s" % (created["gr_gid"], second_name)) + self.failIfEqual(status, 0, output) + + # should pass with the -o option + (status, output) = commands.getstatusoutput("groupmod -g%d -o %s" % (created["gr_gid"], second_name)) + self.failUnlessEqual(status, 0, output) + + self.assertEqual(created.lazy_compare(expected), True, "FAIL: Could not change GID of an existing group to an existing one") + + # clean up + commands.getstatusoutput("groupdel %s" % (second_name)) + self.failUnlessEqual(status, 0, output) + + def testChangeGroupName(self): + """ groupmod: Test changing a group's name """ + second_name = "%s-2" % (self.groupname) + + created = GroupInfo() + created.get_info_name(self.groupname) + + (status, output) = commands.getstatusoutput("groupmod -n%s %s" % (second_name, self.groupname)) + self.failUnlessEqual(status, 0, output) + + changed = GroupInfo() + changed.get_info_gid(created["gr_gid"]) + self.assertEqual(changed["gr_name"], second_name) + self.assertEqual(changed["gr_gid"], created["gr_gid"]) + + # change back, so the group could be deleted by tearDown + (status, output) = commands.getstatusoutput("groupmod -n%s %s" % (self.groupname, second_name)) + self.failUnlessEqual(status, 0, output) + + def testChangeGroupNameExisting(self): + """ groupmod: Test changing a group's name to an existing one """ + existing = "bin" + (status, output) = commands.getstatusoutput("groupmod -n%s %s" % (existing, self.groupname)) + self.assertNotEqual(status, 0, output) # man groupmod -> 9: group name already in use + + def testChangeNonExistingGroup(self): + """ groupmod: Test properties of a non-existing group """ + nonexistent = "foobar" + (status, output) = commands.getstatusoutput("groupmod -nspameggs %s" % (nonexistent)) + self.assertNotEqual(status, 0, status) # man groupmod -> 6: specified group doesn't exist + +class TestGroupdel(unittest.TestCase, ShadowUtilsTestBase): + def testCorrectGroupdel(self): + """ groupdel: Basic usage of groupdel """ + self.groupname = "test-shadow-utils-groups" + (status, output) = commands.getstatusoutput("groupadd %s" % (self.groupname)) + self.failUnlessEqual(status, 0, output) + (status, output) = commands.getstatusoutput("groupdel %s" % (self.groupname)) + self.assertEqual(status, 0, output) + + def testGroupdelNoSuchGroup(self): + """ groupdel: Remove non-existing group """ + (status, output) = commands.getstatusoutput("groupdel foobar") + self.assertNotEqual(status, 0, output) + + def testRemovePrimaryGroup(self): + """ groupdel: Remove a primary group of an user """ + username = "test-groupdel-primary" + (status, output) = commands.getstatusoutput("useradd %s" % (username)) + self.failUnlessEqual(status, 0, output) + + (status, output) = commands.getstatusoutput("groupdel %s" % (username)) + self.assertNotEqual(status, 0, output) + + # clean up + (status, output) = commands.getstatusoutput("userdel -r %s" % (username)) + self.failUnlessEqual(status, 0, output) + +class TestPwckGrpck(unittest.TestCase): + def setUp(self): + self.passwd_path = tempfile.mktemp(suffix="test-pwck-passwd") + self.passwd_file = open(self.passwd_path, "w") + self.group_path = tempfile.mktemp(suffix="test-pwck-grp") + self.group_file = open(self.group_path, "w") + self.gshadow_path = tempfile.mktemp(suffix="test-pwck-gshadow") + self.gshadow_file = open(self.gshadow_path, "w") + + def tearDown(self): + self.passwd_file.close() + self.group_file.close() + self.gshadow_file.close() + + os.remove(self.passwd_path) + os.remove(self.group_path) + os.remove(self.gshadow_path) + + def runPwckCheck(self, passwd, group): + self.passwd_file.truncate() + self.group_file.truncate() + + self.passwd_file.write(passwd) + self.passwd_file.flush() + self.group_file.write(group) + self.group_file.flush() + + command = "pwck -r %s %s" % (self.passwd_path, self.group_path) + return commands.getstatusoutput(command) + + def runGrpCheck(self, group, gshadow): + self.group_file.truncate() + self.gshadow_file.truncate() + + self.gshadow_file.write(gshadow) + self.gshadow_file.flush() + + self.group_file.write(group) + self.group_file.flush() + + command = "grpck -r %s %s" % (self.group_path, self.gshadow_path) + return commands.getstatusoutput(command) + + + def testValidEntries(self): + """ pwck: a valid entry """ + status, output = self.runPwckCheck("foo:x:685:0::/dev/null:/bin/bash", "") + rhv = RedHatVersion() + runs = rhv.get_info() + if rhv.is_rhel(): + if runs[1] < 6: + self.assertEqual(status, 0, output) + else: + self.assertNotEqual(status, 0, output) + + def testNumberOfFields(self): + """ pwck: invalid number of fields in the record """ + not_enough = "foo:x:685:685::/dev/null" + too_many = "foo:x:685:685::/dev/null:/bin/bash:comment" + status, output = self.runPwckCheck(not_enough, "") + self.assertNotEqual(status, 0, output) + + status, output = self.runPwckCheck(too_many, "") + self.assertNotEqual(status, 0, output) + + def testUniqueUserName(self): + """ pwck: unique user name in the record """ + duplicate_username = "foo:x:685:685::/dev/null:/bin/bash\nfoo:x:686:686::/dev/null:/bin/bash" + status, output = self.runPwckCheck(duplicate_username, "") + self.assertNotEqual(status, 0, output) + + def testValidID(self): + """ pwck: invalid UID in the records """ + invalid_ids = [ "foo:x:-1:685::/dev/null:/bin/bash", "foo:x:blah:685::/dev/null:/bin/bash", "foo:x:1234567890:685::/dev/null:/bin/bash" ] + for record in invalid_ids: + status, output = self.runPwckCheck(record, "") + self.assertNotEqual(status, 0, record) + + + def testValidPrimaryGroup(self): + """ pwck: invalid primary group """ + invalid_groups = [ "foo:x:685:-1::/dev/null:/bin/bash", "foo:x:685:blah::/dev/null:/bin/bash", "foo:x:685:1234567890::/dev/null:/bin/bash" ] + for record in invalid_groups: + status, output = self.runPwckCheck("", record) + self.assertNotEqual(status, 0, output) + + def testValidHomeDir(self): + """ pwck: invalid home dir """ + for record in [ "foo:x:685:685::123:/bin/bash", "foo:x:685:685::/path/to/nowhere:/bin/bash", "foo:x:685:1234567890::!:/bin/bash" ]: + status, output = self.runPwckCheck(record, "") + self.assertNotEqual(status, 0, output) + + def testBZ164954(self): + """ grpck: regression test for BZ164954 """ + record = "root:x:0:root\nbin:x:1:root,bin,daemon\ndaemon:x:2:root,bin,daemon\nsys:x:3:root,bin,adm\nadm:x:4:root,adm,daemon" + status, output = self.runGrpCheck("", record) + self.assertNotEqual(status, 0, output) + +if __name__ == "__main__": + broken_on_rhel4 = { "TestUseradd" : [ "testCustomUID", "testCustomGID" ] } + + if os.getuid() != 0: + print "This test must be run as root" + sys.exit(1) + + unittest.main() + diff --git a/tests/tests.yml b/tests/tests.yml new file mode 100644 index 0000000..09f4769 --- /dev/null +++ b/tests/tests.yml @@ -0,0 +1,13 @@ +--- +# This first play always runs on the local staging system +- hosts: localhost + roles: + - role: standard-test-beakerlib + tags: + - classic + - atomic + tests: + - sanity + required_packages: + - shadow-utils # sanity test needs shadow-utils + - python # sanity test needs python