]> granicus.if.org Git - shadow/blobdiff - src/groupmod.c
Re-indent.
[shadow] / src / groupmod.c
index 274c3adb7cfec0446da24dcfe50261f3eff64e02..5b139df5f0a629437404efb8a99c307ae72178a9 100644 (file)
@@ -1,5 +1,8 @@
 /*
- * Copyright 1991 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1991 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2000 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2011, Nicolas François
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of Julianne F. Haugh nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
+ * 3. The name of the copyright holders or contributors may not be used to
+ *    endorse or promote products derived from this software without
+ *    specific prior written permission.
  *
- * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #include <config.h>
 #include <grp.h>
 #include <stdio.h>
 #include <sys/types.h>
+#ifdef ACCT_TOOLS_SETUID
 #ifdef USE_PAM
 #include "pam_defs.h"
 #include <pwd.h>
 #endif                         /* USE_PAM */
+#endif                         /* ACCT_TOOLS_SETUID */
 #include "chkname.h"
 #include "defines.h"
 #include "groupio.h"
@@ -53,6 +58,7 @@
 /*
  * exit status values
  */
+/*@-exitarg@*/
 #define E_SUCCESS      0       /* success */
 #define E_USAGE                2       /* invalid command syntax */
 #define E_BAD_ARG      3       /* invalid argument to option */
 /*
  * Global variables
  */
+const char *Prog;
+
 #ifdef SHADOWGRP
-static int is_shadow_grp;
-static int gshadow_locked = 0;
+static bool is_shadow_grp;
 #endif                         /* SHADOWGRP */
-static int group_locked = 0;
-static int passwd_locked = 0;
 static char *group_name;
 static char *group_newname;
 static char *group_passwd;
 static gid_t group_id;
 static gid_t group_newid;
 
-static char *Prog;
+static struct cleanup_info_mod info_passwd;
+static struct cleanup_info_mod info_group;
+#ifdef SHADOWGRP
+static struct cleanup_info_mod info_gshadow;
+#endif
 
-static int
-    oflg = 0,                  /* permit non-unique group ID to be specified with -g */
-    gflg = 0,                  /* new ID value for the group */
-    nflg = 0,                  /* a new name has been specified for the group */
-    pflg = 0;                  /* new encrypted password */
+static bool
+    oflg = false,              /* permit non-unique group ID to be specified with -g */
+    gflg = false,              /* new ID value for the group */
+    nflg = false,              /* a new name has been specified for the group */
+    pflg = false;              /* new encrypted password */
 
 /* local function prototypes */
-static void usage (void);
-static void fail_exit (int);
+static void usage (int status);
 static void new_grent (struct group *);
 
 #ifdef SHADOWGRP
@@ -95,42 +103,32 @@ static void grp_update (void);
 static void check_new_gid (void);
 static void check_new_name (void);
 static void process_flags (int, char **);
-static void close_files (void);
+static void lock_files (void);
+static void prepare_failure_reports (void);
 static void open_files (void);
-static gid_t get_gid (const char *gidstr);
+static void close_files (void);
 static void update_primary_groups (gid_t ogid, gid_t ngid);
 
 /*
  * usage - display usage message and exit
  */
 
-static void usage (void)
+static void usage (int status)
 {
-       fputs (_("Usage: groupmod [options] GROUP\n"
-                "\n"
-                "Options:\n"
-                "  -g, --gid GID                 force use new GID by GROUP\n"
-                "  -h, --help                    display this help message and exit\n"
-                "  -n, --new-name NEW_GROUP      force use NEW_GROUP name by GROUP\n"
-                "  -o, --non-unique              allow using duplicate (non-unique) GID by GROUP\n"
-                "  -p, --password PASSWORD       use encrypted password for the new password\n"
-                "\n"), stderr);
-       exit (E_USAGE);
-}
-
-static void fail_exit (int status)
-{
-       if (group_locked) {
-               gr_unlock ();
-       }
-#ifdef SHADOWGRP
-       if (gshadow_locked) {
-               sgr_unlock ();
-       }
-#endif                         /* SHADOWGRP */
-       if (passwd_locked) {
-               pw_unlock();
-       }
+       FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+       (void) fprintf (usageout,
+                       _("Usage: %s [options] GROUP\n"
+                         "\n"
+                         "Options:\n"),
+                       Prog);
+       (void) fputs (_("  -g, --gid GID                 change the group ID to GID\n"), usageout);
+       (void) fputs (_("  -h, --help                    display this help message and exit\n"), usageout);
+       (void) fputs (_("  -n, --new-name NEW_GROUP      change the name to NEW_GROUP\n"), usageout);
+       (void) fputs (_("  -o, --non-unique              allow to use a duplicate (non-unique) GID\n"), usageout);
+       (void) fputs (_("  -p, --password PASSWORD       change the password to this (encrypted)\n"
+                       "                                PASSWORD\n"), usageout);
+       (void) fputs (_("  -R, --root CHROOT_DIR         directory to chroot into\n"), usageout);
+       (void) fputs ("\n", usageout);
        exit (status);
 }
 
@@ -142,14 +140,27 @@ static void fail_exit (int status)
  */
 static void new_grent (struct group *grent)
 {
-       if (nflg)
+       if (nflg) {
                grent->gr_name = xstrdup (group_newname);
+       }
 
-       if (gflg)
+       if (gflg) {
                grent->gr_gid = group_newid;
+       }
 
-       if (pflg)
+       if (   pflg
+#ifdef SHADOWGRP
+           && (   (!is_shadow_grp)
+               || (strcmp (grent->gr_passwd, SHADOW_PASSWD_STRING) != 0))
+#endif
+               ) {
+               /* Update the password in group if there is no gshadow
+                * file or if the password is currently in group
+                * (gr_passwd != "x").  We do not force the usage of
+                * shadow passwords if it was not the case before.
+                */
                grent->gr_passwd = group_passwd;
+       }
 }
 
 #ifdef SHADOWGRP
@@ -161,18 +172,27 @@ static void new_grent (struct group *grent)
  */
 static void new_sgent (struct sgrp *sgent)
 {
-       if (nflg)
+       if (nflg) {
                sgent->sg_name = xstrdup (group_newname);
+       }
 
-       if (pflg)
+       /* Always update the shadowed password if there is a shadow entry
+        * (even if shadowed passwords might not be enabled for this group
+        * (gr_passwd != "x")).
+        * It seems better to update the password in both places in case a
+        * shadow and a non shadow entry exist.
+        * This might occur only if there were already both entries.
+        */
+       if (pflg) {
                sgent->sg_passwd = group_passwd;
+       }
 }
 #endif                         /* SHADOWGRP */
 
 /*
  * grp_update - update group file entries
  *
- *     grp_update() writes the new records to the group files.
+ *     grp_update() updates the new records in the memory databases.
  */
 static void grp_update (void)
 {
@@ -188,24 +208,37 @@ static void grp_update (void)
         * Get the current settings for this group.
         */
        ogrp = gr_locate (group_name);
-       if (!ogrp) {
+       if (NULL == ogrp) {
                fprintf (stderr,
-                        _("%s: %s not found in /etc/group\n"),
-                        Prog, group_name);
-#ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "modifying group",
-                             group_name, -1, 0);
-#endif
-               fail_exit (E_GRP_UPDATE);
+                        _("%s: group '%s' does not exist in %s\n"),
+                        Prog, group_name, gr_dbname ());
+               exit (E_GRP_UPDATE);
        }
        grp = *ogrp;
        new_grent (&grp);
 #ifdef SHADOWGRP
-       if (is_shadow_grp && (osgrp = sgr_locate (group_name))) {
-               sgrp = *osgrp;
-               new_sgent (&sgrp);
-               if (pflg)
-                       grp.gr_passwd = SHADOW_PASSWD_STRING;
+       if (   is_shadow_grp
+           && (pflg || nflg)) {
+               osgrp = sgr_locate (group_name);
+               if (NULL != osgrp) {
+                       sgrp = *osgrp;
+                       new_sgent (&sgrp);
+               } else if (   pflg
+                          && (strcmp (grp.gr_passwd, SHADOW_PASSWD_STRING) == 0)) {
+                       static char *empty = NULL;
+                       /* If there is a gshadow file with no entries for
+                        * the group, but the group file indicates a
+                        * shadowed password, we force the creation of a
+                        * gshadow entry when a new password is requested.
+                        */
+                       memset (&sgrp, 0, sizeof sgrp);
+                       sgrp.sg_name   = xstrdup (grp.gr_name);
+                       sgrp.sg_passwd = xstrdup (grp.gr_passwd);
+                       sgrp.sg_adm    = &empty;
+                       sgrp.sg_mem    = dup_list (grp.gr_mem);
+                       new_sgent (&sgrp);
+                       osgrp = &sgrp; /* entry needs to be committed */
+               }
        }
 #endif                         /* SHADOWGRP */
 
@@ -216,66 +249,41 @@ static void grp_update (void)
        /*
         * Write out the new group file entry.
         */
-       if (!gr_update (&grp)) {
-               fprintf (stderr, _("%s: error adding new group entry\n"), Prog);
-#ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "adding group",
-                             group_name, -1, 0);
-#endif
-               fail_exit (E_GRP_UPDATE);
+       if (gr_update (&grp) == 0) {
+               fprintf (stderr,
+                        _("%s: failed to prepare the new %s entry '%s'\n"),
+                        Prog, gr_dbname (), grp.gr_name);
+               exit (E_GRP_UPDATE);
        }
-       if (nflg && !gr_remove (group_name)) {
-               fprintf (stderr, _("%s: error removing group entry\n"), Prog);
-#ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "deleting group",
-                             group_name, -1, 0);
-#endif
-               fail_exit (E_GRP_UPDATE);
+       if (nflg && (gr_remove (group_name) == 0)) {
+               fprintf (stderr,
+                        _("%s: cannot remove entry '%s' from %s\n"),
+                        Prog, grp.gr_name, gr_dbname ());
+               exit (E_GRP_UPDATE);
        }
-#ifdef SHADOWGRP
-
-       /*
-        * Make sure there was a shadow entry to begin with. Skip down to
-        * "out" if there wasn't. Can't just return because there might be
-        * some syslogging to do.
-        */
-       if (!osgrp)
-               goto out;
 
+#ifdef SHADOWGRP
        /*
-        * Write out the new shadow group entries as well.
+        * Make sure there was a shadow entry to begin with.
         */
-       if (is_shadow_grp && !sgr_update (&sgrp)) {
-               fprintf (stderr, _("%s: error adding new group entry\n"), Prog);
-#ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "adding group",
-                             group_name, -1, 0);
-#endif
-               fail_exit (E_GRP_UPDATE);
-       }
-       if (is_shadow_grp && nflg && !sgr_remove (group_name)) {
-               fprintf (stderr, _("%s: error removing group entry\n"), Prog);
-#ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "deleting group",
-                             group_name, -1, 0);
-#endif
-               fail_exit (E_GRP_UPDATE);
+       if (NULL != osgrp) {
+               /*
+                * Write out the new shadow group entries as well.
+                */
+               if (sgr_update (&sgrp) == 0) {
+                       fprintf (stderr,
+                                _("%s: failed to prepare the new %s entry '%s'\n"),
+                                Prog, sgr_dbname (), sgrp.sg_name);
+                       exit (E_GRP_UPDATE);
+               }
+               if (nflg && (sgr_remove (group_name) == 0)) {
+                       fprintf (stderr,
+                                _("%s: cannot remove entry '%s' from %s\n"),
+                                Prog, group_name, sgr_dbname ());
+                       exit (E_GRP_UPDATE);
+               }
        }
-      out:
 #endif                         /* SHADOWGRP */
-
-#ifdef WITH_AUDIT
-       audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "modifing group", group_name,
-                     group_id, 1);
-#endif
-       if (nflg)
-               SYSLOG ((LOG_INFO, "change group `%s' to `%s'",
-                        group_name, group_newname));
-
-       if (gflg) {
-               SYSLOG ((LOG_INFO, "change GID for `%s' to %u",
-                        nflg ? group_newname : group_name, group_newid));
-       }
 }
 
 /*
@@ -295,18 +303,19 @@ static void check_new_gid (void)
                return;
        }
 
-       if (oflg || !getgrgid (group_newid)) /* local, no need for xgetgrgid */
+       if (oflg ||
+           (getgrgid (group_newid) == NULL) /* local, no need for xgetgrgid */
+          ) {
                return;
+       }
 
        /*
         * Tell the user what they did wrong.
         */
-       fprintf (stderr, _("%s: %u is not a unique GID\n"), Prog, group_newid);
-#ifdef WITH_AUDIT
-       audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "modify gid", NULL,
-                     group_newid, 0);
-#endif
-       fail_exit (E_GID_IN_USE);
+       fprintf (stderr,
+                _("%s: GID '%lu' already exists\n"),
+                Prog, (unsigned long int) group_newid);
+       exit (E_GID_IN_USE);
 }
 
 /*
@@ -325,21 +334,17 @@ static void check_new_name (void)
                return;
        }
 
-       if (check_group_name (group_newname)) {
+       if (is_valid_group_name (group_newname)) {
 
                /*
                 * If the entry is found, too bad.
                 */
                /* local, no need for xgetgrnam */
-               if (getgrnam (group_newname)) {
+               if (getgrnam (group_newname) != NULL) {
                        fprintf (stderr,
-                                _("%s: %s is not a unique name\n"), Prog,
-                                group_newname);
-#ifdef WITH_AUDIT
-                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                     "modifying group", group_name, -1, 0);
-#endif
-                       fail_exit (E_NAME_IN_USE);
+                                _("%s: group '%s' already exists\n"),
+                                Prog, group_newname);
+                       exit (E_NAME_IN_USE);
                }
                return;
        }
@@ -348,30 +353,10 @@ static void check_new_name (void)
         * All invalid group names land here.
         */
 
-       fprintf (stderr, _("%s: %s is not a valid group name\n"),
-                Prog, group_newname);
-#ifdef WITH_AUDIT
-       audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "modifying group", group_name,
-                     -1, 0);
-#endif
-       fail_exit (E_BAD_ARG);
-}
-
-/*
- * get_id - validate and get group ID
- */
-static gid_t get_gid (const char *gidstr)
-{
-       long val;
-       char *errptr;
-
-       val = strtol (gidstr, &errptr, 10);
-       if (*errptr || errno == ERANGE || val < 0) {
-               fprintf (stderr, _("%s: invalid numeric argument '%s'\n"), Prog,
-                        gidstr);
-               fail_exit (E_BAD_ARG);
-       }
-       return val;
+       fprintf (stderr,
+                _("%s: invalid group name '%s'\n"),
+                Prog, group_newname);
+       exit (E_BAD_ARG);
 }
 
 /*
@@ -383,53 +368,57 @@ static gid_t get_gid (const char *gidstr)
  */
 static void process_flags (int argc, char **argv)
 {
-
-       {
-               int option_index = 0;
-               int c;
-               static struct option long_options[] = {
-                       {"gid", required_argument, NULL, 'g'},
-                       {"help", no_argument, NULL, 'h'},
-                       {"new-name", required_argument, NULL, 'n'},
-                       {"non-unique", no_argument, NULL, 'o'},
-                       {"password", required_argument, NULL, 'p'},
-                       {NULL, 0, NULL, '\0'}
-               };
-               while ((c =
-                       getopt_long (argc, argv, "g:hn:op:",
-                                    long_options, &option_index)) != -1) {
-                       switch (c) {
-                       case 'g':
-                               gflg++;
-                               group_newid = get_gid (optarg);
-#ifdef WITH_AUDIT
-                               audit_logger (AUDIT_USER_CHAUTHTOK,
-                                             Prog, "modifying group",
-                                             NULL, group_newid, 0);
-#endif
-                               break;
-                       case 'n':
-                               nflg++;
-                               group_newname = optarg;
-                               break;
-                       case 'o':
-                               oflg++;
-                               break;
-                       case 'p':
-                               group_passwd = optarg;
-                               pflg++;
-                               break;
-                       default:
-                               usage ();
+       int c;
+       static struct option long_options[] = {
+               {"gid", required_argument, NULL, 'g'},
+               {"help", no_argument, NULL, 'h'},
+               {"new-name", required_argument, NULL, 'n'},
+               {"non-unique", no_argument, NULL, 'o'},
+               {"password", required_argument, NULL, 'p'},
+               {"root", required_argument, NULL, 'R'},
+               {NULL, 0, NULL, '\0'}
+       };
+       while ((c = getopt_long (argc, argv, "g:hn:op:R:",
+                                long_options, NULL)) != -1) {
+               switch (c) {
+               case 'g':
+                       gflg = true;
+                       if (   (get_gid (optarg, &group_newid) == 0)
+                           || (group_newid == (gid_t)-1)) {
+                               fprintf (stderr,
+                                        _("%s: invalid group ID '%s'\n"),
+                                        Prog, optarg);
+                               exit (E_BAD_ARG);
                        }
+                       break;
+               case 'h':
+                       usage (E_SUCCESS);
+                       break;
+               case 'n':
+                       nflg = true;
+                       group_newname = optarg;
+                       break;
+               case 'o':
+                       oflg = true;
+                       break;
+               case 'p':
+                       group_passwd = optarg;
+                       pflg = true;
+                       break;
+               case 'R': /* no-op, handled in process_root_flag () */
+                       break;
+               default:
+                       usage (E_USAGE);
                }
        }
 
-       if (oflg && !gflg)
-               usage ();
+       if (oflg && !gflg) {
+               usage (E_USAGE);
+       }
 
-       if (optind != argc - 1)
-               usage ();
+       if (optind != (argc - 1)) {
+               usage (E_USAGE);
+       }
 
        group_name = argv[argc - 1];
 }
@@ -442,80 +431,264 @@ static void process_flags (int argc, char **argv)
  */
 static void close_files (void)
 {
-       if (!gr_close ()) {
-               fprintf (stderr, _("%s: cannot rewrite group file\n"), Prog);
-               fail_exit (E_GRP_UPDATE);
-       }
-       gr_unlock ();
-       group_locked--;
-#ifdef SHADOWGRP
-       if (is_shadow_grp && !sgr_close ()) {
+       if (gr_close () == 0) {
                fprintf (stderr,
-                        _("%s: cannot rewrite shadow group file\n"), Prog);
-               fail_exit (E_GRP_UPDATE);
+                        _("%s: failure while writing changes to %s\n"),
+                        Prog, gr_dbname ());
+               exit (E_GRP_UPDATE);
        }
-       if (is_shadow_grp) {
-               sgr_unlock ();
-               gshadow_locked--;
+#ifdef WITH_AUDIT
+       audit_logger (AUDIT_USER_ACCT, Prog,
+                     info_group.audit_msg,
+                     group_name, AUDIT_NO_ID,
+                     SHADOW_AUDIT_SUCCESS);
+#endif
+       SYSLOG ((LOG_INFO,
+                "group changed in %s (%s)",
+                gr_dbname (), info_group.action));
+       del_cleanup (cleanup_report_mod_group);
+
+       cleanup_unlock_group (NULL);
+       del_cleanup (cleanup_unlock_group);
+
+#ifdef SHADOWGRP
+       if (   is_shadow_grp
+           && (pflg || nflg)) {
+               if (sgr_close () == 0) {
+                       fprintf (stderr,
+                                _("%s: failure while writing changes to %s\n"),
+                                Prog, sgr_dbname ());
+                       exit (E_GRP_UPDATE);
+               }
+#ifdef WITH_AUDIT
+               audit_logger (AUDIT_USER_ACCT, Prog,
+                             info_gshadow.audit_msg,
+                             group_name, AUDIT_NO_ID,
+                             SHADOW_AUDIT_SUCCESS);
+#endif
+               SYSLOG ((LOG_INFO,
+                        "group changed in %s (%s)",
+                        sgr_dbname (), info_gshadow.action));
+               del_cleanup (cleanup_report_mod_gshadow);
+
+               cleanup_unlock_gshadow (NULL);
+               del_cleanup (cleanup_unlock_gshadow);
        }
 #endif                         /* SHADOWGRP */
+
        if (gflg) {
-               if (!pw_close ()) {
+               if (pw_close () == 0) {
                        fprintf (stderr,
-                                _("%s: cannot rewrite passwd file\n"), Prog);
-                       fail_exit (E_GRP_UPDATE);
+                                _("%s: failure while writing changes to %s\n"),
+                                Prog, pw_dbname ());
+                       exit (E_GRP_UPDATE);
                }
-               pw_unlock();
-               passwd_locked--;
+#ifdef WITH_AUDIT
+               audit_logger (AUDIT_USER_ACCT, Prog,
+                             info_passwd.audit_msg,
+                             group_name, AUDIT_NO_ID,
+                             SHADOW_AUDIT_SUCCESS);
+#endif
+               SYSLOG ((LOG_INFO,
+                        "group changed in %s (%s)",
+                        pw_dbname (), info_passwd.action));
+               del_cleanup (cleanup_report_mod_passwd);
+
+               cleanup_unlock_passwd (NULL);
+               del_cleanup (cleanup_unlock_passwd);
        }
+
+#ifdef WITH_AUDIT
+       audit_logger (AUDIT_USER_ACCT, Prog,
+                     "modifying group",
+                     group_name, AUDIT_NO_ID,
+                     SHADOW_AUDIT_SUCCESS);
+#endif
 }
 
 /*
- * open_files - lock and open the group files
- *
- *     open_files() opens the two group files.
+ * prepare_failure_reports - Prepare the cleanup_info structure for logging
+ * of success and failure to syslog or audit.
  */
-static void open_files (void)
+static void prepare_failure_reports (void)
 {
-       if (!gr_lock ()) {
-               fprintf (stderr, _("%s: unable to lock group file\n"), Prog);
-               fail_exit (E_GRP_UPDATE);
+       info_group.name   = group_name;
+#ifdef SHADOWGRP
+       info_gshadow.name = group_name;
+#endif
+       info_passwd.name  = group_name;
+
+       info_group.audit_msg   = xmalloc (512);
+#ifdef SHADOWGRP
+       info_gshadow.audit_msg = xmalloc (512);
+#endif
+       info_passwd.audit_msg  = xmalloc (512);
+
+       (void) snprintf (info_group.audit_msg, 511,
+                        "changing %s; ", gr_dbname ());
+#ifdef SHADOWGRP
+       (void) snprintf (info_gshadow.audit_msg, 511,
+                        "changing %s; ", sgr_dbname ());
+#endif
+       (void) snprintf (info_passwd.audit_msg, 511,
+                        "changing %s; ", pw_dbname ());
+
+       info_group.action   =   info_group.audit_msg
+                             + strlen (info_group.audit_msg);
+#ifdef SHADOWGRP
+       info_gshadow.action =   info_gshadow.audit_msg
+                             + strlen (info_gshadow.audit_msg);
+#endif
+       info_passwd.action  =   info_passwd.audit_msg
+                             + strlen (info_passwd.audit_msg);
+
+       (void) snprintf (info_group.action,
+                        511 - strlen (info_group.audit_msg),
+                        "group %s/%lu",
+                        group_name, (unsigned long int) group_id);
+#ifdef SHADOWGRP
+       (void) snprintf (info_gshadow.action,
+                        511 - strlen (info_group.audit_msg),
+                        "group %s", group_name);
+#endif
+       (void) snprintf (info_passwd.action,
+                        511 - strlen (info_group.audit_msg),
+                        "group %s/%lu",
+                        group_name, (unsigned long int) group_id);
+
+       if (nflg) {
+               strncat (info_group.action, ", new name: ",
+                        511 - strlen (info_group.audit_msg));
+               strncat (info_group.action, group_newname,
+                        511 - strlen (info_group.audit_msg));
+
+#ifdef SHADOWGRP
+               strncat (info_gshadow.action, ", new name: ",
+                        511 - strlen (info_gshadow.audit_msg));
+               strncat (info_gshadow.action, group_newname,
+                        511 - strlen (info_gshadow.audit_msg));
+#endif
+
+               strncat (info_passwd.action, ", new name: ",
+                        511 - strlen (info_passwd.audit_msg));
+               strncat (info_passwd.action, group_newname,
+                        511 - strlen (info_passwd.audit_msg));
+       }
+       if (pflg) {
+               strncat (info_group.action, ", new password",
+                        511 - strlen (info_group.audit_msg));
+
+#ifdef SHADOWGRP
+               strncat (info_gshadow.action, ", new password",
+                        511 - strlen (info_gshadow.audit_msg));
+#endif
+       }
+       if (gflg) {
+               strncat (info_group.action, ", new gid: ",
+                        511 - strlen (info_group.audit_msg));
+               (void) snprintf (info_group.action+strlen (info_group.action),
+                                511 - strlen (info_group.audit_msg),
+                                "%lu", (unsigned long int) group_newid);
+
+               strncat (info_passwd.action, ", new gid: ",
+                        511 - strlen (info_passwd.audit_msg));
+               (void) snprintf (info_passwd.action+strlen (info_passwd.action),
+                                511 - strlen (info_passwd.audit_msg),
+                                "%lu", (unsigned long int) group_newid);
        }
-       group_locked++;
-       if (!gr_open (O_RDWR)) {
-               fprintf (stderr, _("%s: unable to open group file\n"), Prog);
-               fail_exit (E_GRP_UPDATE);
+       info_group.audit_msg[511]   = '\0';
+#ifdef SHADOWGRP
+       info_gshadow.audit_msg[511] = '\0';
+#endif
+       info_passwd.audit_msg[511]  = '\0';
+
+// FIXME: add a system cleanup
+       add_cleanup (cleanup_report_mod_group, &info_group);
+#ifdef SHADOWGRP
+       if (   is_shadow_grp
+           && (pflg || nflg)) {
+               add_cleanup (cleanup_report_mod_gshadow, &info_gshadow);
+       }
+#endif
+       if (gflg) {
+               add_cleanup (cleanup_report_mod_passwd, &info_passwd);
+       }
+
+}
+
+/*
+ * lock_files - lock the accounts databases
+ *
+ *     lock_files() locks the group, gshadow, and passwd databases.
+ */
+static void lock_files (void)
+{
+       if (gr_lock () == 0) {
+               fprintf (stderr,
+                        _("%s: cannot lock %s; try again later.\n"),
+                        Prog, gr_dbname ());
+               exit (E_GRP_UPDATE);
        }
+       add_cleanup (cleanup_unlock_group, NULL);
+
 #ifdef SHADOWGRP
-       if (is_shadow_grp) {
-               if (!sgr_lock ()) {
+       if (   is_shadow_grp
+           && (pflg || nflg)) {
+               if (sgr_lock () == 0) {
                        fprintf (stderr,
-                                _("%s: unable to lock shadow group file\n"),
-                                Prog);
-                       fail_exit (E_GRP_UPDATE);
+                                _("%s: cannot lock %s; try again later.\n"),
+                                Prog, sgr_dbname ());
+                       exit (E_GRP_UPDATE);
                }
-               gshadow_locked++;
-               if (!sgr_open (O_RDWR)) {
+               add_cleanup (cleanup_unlock_gshadow, NULL);
+       }
+#endif
+
+       if (gflg) {
+               if (pw_lock () == 0) {
                        fprintf (stderr,
-                                _("%s: unable to open shadow group file\n"),
-                                Prog);
-                       fail_exit (E_GRP_UPDATE);
+                                _("%s: cannot lock %s; try again later.\n"),
+                                Prog, pw_dbname ());
+                       exit (E_GRP_UPDATE);
                }
+               add_cleanup (cleanup_unlock_passwd, NULL);
        }
-#endif                         /* SHADOWGRP */
-       if (gflg) {
-               if (!pw_lock ()) {
+}
+
+
+/*
+ * open_files - open the accounts databases
+ *
+ *     open_files() opens the group, gshadow, and passwd databases.
+ */
+static void open_files (void)
+{
+       if (gr_open (O_RDWR) == 0) {
+               fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ());
+               SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
+               exit (E_GRP_UPDATE);
+       }
+
+#ifdef SHADOWGRP
+       if (   is_shadow_grp
+           && (pflg || nflg)) {
+               if (sgr_open (O_RDWR) == 0) {
                        fprintf (stderr,
-                                _("%s: unable to lock password file\n"),
-                                Prog);
-                       fail_exit (E_GRP_UPDATE);
+                                _("%s: cannot open %s\n"),
+                                Prog, sgr_dbname ());
+                       SYSLOG ((LOG_WARN, "cannot open %s", sgr_dbname ()));
+                       exit (E_GRP_UPDATE);
                }
-               passwd_locked++;
-               if (!pw_open (O_RDWR)) {
+       }
+#endif                         /* SHADOWGRP */
+
+       if (gflg) {
+               if (pw_open (O_RDWR) == 0) {
                        fprintf (stderr,
-                                _("%s: unable to open password file\n"),
-                                Prog);
-                       fail_exit (E_GRP_UPDATE);
+                                _("%s: cannot open %s\n"),
+                                Prog, pw_dbname ());
+                       SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
+                       exit (E_GRP_UPDATE);
                }
        }
 }
@@ -532,94 +705,94 @@ void update_primary_groups (gid_t ogid, gid_t ngid)
                        lpwd = pw_locate (pwd->pw_name);
                        if (NULL == lpwd) {
                                fprintf (stderr,
-                                        _("%s: cannot change the primary group of user '%s' from %u to %u, since it is not in the passwd file.\n"),
-                                        Prog, pwd->pw_name, ogid, ngid);
-                               fail_exit (E_GRP_UPDATE);
+                                        _("%s: user '%s' does not exist in %s\n"),
+                                        Prog, pwd->pw_name, pw_dbname ());
+                               exit (E_GRP_UPDATE);
                        } else {
                                npwd = *lpwd;
                                npwd.pw_gid = ngid;
-                               if (!pw_update (&npwd)) {
+                               if (pw_update (&npwd) == 0) {
                                        fprintf (stderr,
-                                                _("%s: cannot change the primary group of user '%s' from %u to %u.\n"),
-                                                Prog, pwd->pw_name, ogid, ngid);
-                                       fail_exit (E_GRP_UPDATE);
+                                                _("%s: failed to prepare the new %s entry '%s'\n"),
+                                                Prog, pw_dbname (), npwd.pw_name);
+                                       exit (E_GRP_UPDATE);
                                }
                        }
                }
        }
+       endpwent ();
 }
 
 /*
  * main - groupmod command
  *
- *     The syntax of the groupmod command is
- *     
- *     groupmod [ -g gid [ -o ]] [ -n name ] group
- *
- *     The flags are
- *             -g - specify a new group ID value
- *             -o - permit the group ID value to be non-unique
- *             -n - specify a new group name
  */
 int main (int argc, char **argv)
 {
+#ifdef ACCT_TOOLS_SETUID
 #ifdef USE_PAM
        pam_handle_t *pamh = NULL;
        int retval;
-#endif
-
-#ifdef WITH_AUDIT
-       audit_help_open ();
-#endif
+#endif                         /* USE_PAM */
+#endif                         /* ACCT_TOOLS_SETUID */
 
        /*
         * Get my name so that I can use it to report errors.
         */
        Prog = Basename (argv[0]);
 
-       setlocale (LC_ALL, "");
-       bindtextdomain (PACKAGE, LOCALEDIR);
-       textdomain (PACKAGE);
+       (void) setlocale (LC_ALL, "");
+       (void) bindtextdomain (PACKAGE, LOCALEDIR);
+       (void) textdomain (PACKAGE);
 
-       process_flags (argc, argv);
+       process_root_flag ("-R", argc, argv);
 
        OPENLOG ("groupmod");
+#ifdef WITH_AUDIT
+       audit_help_open ();
+#endif
 
-#ifdef USE_PAM
-       retval = PAM_SUCCESS;
+       if (atexit (do_cleanups) != 0) {
+               fprintf (stderr,
+                        _("%s: Cannot setup cleanup service.\n"),
+                        Prog);
+               exit (1);
+       }
+
+       process_flags (argc, argv);
 
+#ifdef ACCT_TOOLS_SETUID
+#ifdef USE_PAM
        {
                struct passwd *pampw;
                pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
-               if (pampw == NULL) {
-                       retval = PAM_USER_UNKNOWN;
+               if (NULL == pampw) {
+                       fprintf (stderr,
+                                _("%s: Cannot determine your user name.\n"),
+                                Prog);
+                       exit (1);
                }
 
-               if (retval == PAM_SUCCESS) {
-                       retval = pam_start ("groupmod", pampw->pw_name,
-                                           &conv, &pamh);
-               }
+               retval = pam_start ("groupmod", pampw->pw_name, &conv, &pamh);
        }
 
-       if (retval == PAM_SUCCESS) {
+       if (PAM_SUCCESS == retval) {
                retval = pam_authenticate (pamh, 0);
-               if (retval != PAM_SUCCESS) {
-                       pam_end (pamh, retval);
-               }
        }
 
-       if (retval == PAM_SUCCESS) {
+       if (PAM_SUCCESS == retval) {
                retval = pam_acct_mgmt (pamh, 0);
-               if (retval != PAM_SUCCESS) {
-                       pam_end (pamh, retval);
-               }
        }
 
-       if (retval != PAM_SUCCESS) {
+       if (NULL != pamh) {
+               (void) pam_end (pamh, retval);
+       }
+       if (PAM_SUCCESS != retval) {
                fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
-               fail_exit (1);
+               exit (1);
        }
 #endif                         /* USE_PAM */
+#endif                         /* ACCT_TOOLS_SETUID */
 
 #ifdef SHADOWGRP
        is_shadow_grp = sgr_file_present ();
@@ -629,27 +802,17 @@ int main (int argc, char **argv)
                /*
                 * Start with a quick check to see if the group exists.
                 */
-               /* local, no need for xgetgrnam */
-               if (!(grp = getgrnam (group_name))) {
-                       fprintf (stderr, _("%s: group %s does not exist\n"),
-                                Prog, group_name);
-#ifdef WITH_AUDIT
-                       audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
-                                     "modifying group", group_name, -1, 0);
-#endif
-                       fail_exit (E_NOTFOUND);
-               } else
+               grp = getgrnam (group_name); /* local, no need for xgetgrnam */
+               if (NULL == grp) {
+                       fprintf (stderr,
+                                _("%s: group '%s' does not exist\n"),
+                                Prog, group_name);
+                       exit (E_NOTFOUND);
+               } else {
                        group_id = grp->gr_gid;
+               }
        }
 
-#ifdef WITH_AUDIT
-       /* Set new name/id to original if not specified on command line */
-       if (nflg == 0)
-               group_newname = group_name;
-       if (gflg == 0)
-               group_newid = group_id;
-#endif
-
 #ifdef USE_NIS
        /*
         * Now make sure it isn't an NIS group.
@@ -658,27 +821,35 @@ int main (int argc, char **argv)
                char *nis_domain;
                char *nis_master;
 
-               fprintf (stderr, _("%s: group %s is a NIS group\n"),
-                        Prog, group_name);
+               fprintf (stderr,
+                        _("%s: group %s is a NIS group\n"),
+                        Prog, group_name);
 
-#ifdef WITH_AUDIT
-               audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "modifying group",
-                             group_name, -1, 0);
-#endif
                if (!yp_get_default_domain (&nis_domain) &&
                    !yp_master (nis_domain, "group.byname", &nis_master)) {
-                       fprintf (stderr, _("%s: %s is the NIS master\n"),
-                                Prog, nis_master);
+                       fprintf (stderr,
+                                _("%s: %s is the NIS master\n"),
+                                Prog, nis_master);
                }
-               fail_exit (E_NOTFOUND);
+               exit (E_NOTFOUND);
        }
 #endif
 
-       if (gflg)
+       if (gflg) {
                check_new_gid ();
+       }
 
-       if (nflg)
+       if (nflg) {
                check_new_name ();
+       }
+
+       lock_files ();
+
+       /*
+        * Now if the group is not changed, it's our fault.
+        * Make sure failures will be reported.
+        */
+       prepare_failure_reports ();
 
        /*
         * Do the hard stuff - open the files, create the group entries,
@@ -692,11 +863,6 @@ int main (int argc, char **argv)
 
        nscd_flush_cache ("group");
 
-#ifdef USE_PAM
-       if (retval == PAM_SUCCESS)
-               pam_end (pamh, PAM_SUCCESS);
-#endif                         /* USE_PAM */
-       exit (E_SUCCESS);
-       /* NOT REACHED */
+       return E_SUCCESS;
 }