]> granicus.if.org Git - shadow/blobdiff - src/grpck.c
* src/newgrp.c, src/chfn.c, src/groupmems.c, src/usermod.c,
[shadow] / src / grpck.c
index 58465fc7793983908ef400c5dd64ae2d6fa2244b..ca4d72e18d9c7d56a1b34106888f3bd562b7967f 100644 (file)
@@ -1,5 +1,9 @@
 /*
- * Copyright 1992 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1992 - 1994, Julianne Frances Haugh
+ * Copyright (c) 1996 - 2000, Marek Michałkiewicz
+ * Copyright (c) 2001       , Michał Moskal
+ * Copyright (c) 2001 - 2006, Tomasz Kłoczko
+ * Copyright (c) 2007 - 2009, 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 "rcsid.h"
-RCSID (PKG_VER "$Id: grpck.c,v 1.23 2005/04/06 04:26:06 kloczek Exp $")
-#include <stdio.h>
+#ident "$Id$"
+
 #include <fcntl.h>
 #include <grp.h>
-#include "prototypes.h"
-#include "defines.h"
-#include "chkname.h"
 #include <pwd.h>
+#include <stdio.h>
+#include "chkname.h"
 #include "commonio.h"
+#include "defines.h"
 #include "groupio.h"
-extern void __gr_del_entry (const struct commonio_entry *);
-extern struct commonio_entry *__gr_get_head (void);
+#include "nscd.h"
+#include "prototypes.h"
 
 #ifdef SHADOWGRP
 #include "sgroupio.h"
-extern void __sgr_del_entry (const struct commonio_entry *);
-extern struct commonio_entry *__sgr_get_head (void);
 #endif
 
 /*
  * Exit codes
  */
-
+/*@-exitarg@*/
 #define        E_OKAY          0
 #define        E_USAGE         1
 #define        E_BAD_ENTRY     2
@@ -61,26 +62,78 @@ extern struct commonio_entry *__sgr_get_head (void);
 #define        E_CANT_UPDATE   5
 
 /*
- * Local variables
+ * Global variables
  */
+char *Prog;
 
-static char *Prog;
 static const char *grp_file = GROUP_FILE;
+static bool use_system_grp_file = true;
 
 #ifdef SHADOWGRP
 static const char *sgr_file = SGROUP_FILE;
+static bool use_system_sgr_file = true;
+static bool is_shadow = false;
+static bool sgr_locked = false;
 #endif
-static int read_only = 0;
+static bool gr_locked = false;
+/* Options */
+static bool read_only = false;
+static bool sort_mode = false;
 
 /* local function prototypes */
+static void fail_exit (int status);
 static void usage (void);
-static int yes_or_no (void);
 static void delete_member (char **, const char *);
+static void process_flags (int argc, char **argv);
+static void open_files (void);
+static void close_files (bool changed);
+static int check_members (const char *groupname,
+                          char **members,
+                          const char *fmt_info,
+                          const char *fmt_prompt,
+                          const char *fmt_syslog,
+                          int *errors);
+static void check_grp_file (int *errors, bool *changed);
+#ifdef SHADOWGRP
+static void compare_members_lists (const char *groupname,
+                                   char **members,
+                                   char **other_members,
+                                   const char *file,
+                                   const char *other_file);
+static void check_sgr_file (int *errors, bool *changed);
+#endif
 
 /*
- * usage - print syntax message and exit
+ * fail_exit - exit with an error code after unlocking files
  */
+static void fail_exit (int status)
+{
+       if (gr_locked) {
+               if (gr_unlock () == 0) {
+                       fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+                       SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+                       /* continue */
+               }
+       }
 
+#ifdef SHADOWGRP
+       if (sgr_locked) {
+               if (sgr_unlock () == 0) {
+                       fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+                       SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+                       /* continue */
+               }
+       }
+#endif
+
+       closelog ();
+
+       exit (status);
+}
+
+/*
+ * usage - print syntax message and exit
+ */
 static void usage (void)
 {
 #ifdef SHADOWGRP
@@ -91,96 +144,50 @@ static void usage (void)
        exit (E_USAGE);
 }
 
-/*
- * yes_or_no - get answer to question from the user
- */
-
-static int yes_or_no (void)
-{
-       char buf[80];
-
-       /*
-        * In read-only mode all questions are answered "no".
-        */
-
-       if (read_only) {
-               puts (_("No"));
-               return 0;
-       }
-
-       /*
-        * Get a line and see what the first character is.
-        */
-
-       if (fgets (buf, sizeof buf, stdin))
-               return buf[0] == 'y' || buf[0] == 'Y';
-
-       return 0;
-}
-
 /*
  * delete_member - delete an entry in a list of members
+ *
+ * It only deletes the first entry with the given name.
  */
-
 static void delete_member (char **list, const char *member)
 {
        int i;
 
-       for (i = 0; list[i]; i++)
-               if (list[i] == member)
+       for (i = 0; list[i]; i++) {
+               if (list[i] == member) {
                        break;
+               }
+       }
 
-       if (list[i])
-               for (; list[i]; i++)
+       if (list[i]) {
+               for (; list[i]; i++) {
                        list[i] = list[i + 1];
+               }
+       }
 }
 
 /*
- * grpck - verify group file integrity
+ * process_flags - parse the command line options
+ *
+ *     It will not return if an error is encountered.
  */
-
-int main (int argc, char **argv)
+static void process_flags (int argc, char **argv)
 {
        int arg;
-       int errors = 0;
-       int deleted = 0;
-       int i;
-       struct commonio_entry *gre, *tgre;
-       struct group *grp;
-       int sort_mode = 0;
-
-#ifdef SHADOWGRP
-       struct commonio_entry *sge, *tsge;
-       struct sgrp *sgr;
-       int is_shadow = 0;
-#endif
-
-       /*
-        * Get my name so that I can use it to report errors.
-        */
-
-       Prog = Basename (argv[0]);
-
-       setlocale (LC_ALL, "");
-       bindtextdomain (PACKAGE, LOCALEDIR);
-       textdomain (PACKAGE);
-
-       OPENLOG ("grpck");
 
        /*
         * Parse the command line arguments
         */
-
        while ((arg = getopt (argc, argv, "qrs")) != EOF) {
                switch (arg) {
                case 'q':
                        /* quiet - ignored for now */
                        break;
                case 'r':
-                       read_only = 1;
+                       read_only = true;
                        break;
                case 's':
-                       sort_mode = 1;
+                       sort_mode = true;
                        break;
                default:
                        usage ();
@@ -195,53 +202,64 @@ int main (int argc, char **argv)
        /*
         * Make certain we have the right number of arguments
         */
-
 #ifdef SHADOWGRP
-       if (optind != argc && optind + 1 != argc && optind + 2 != argc)
+       if ((argc < optind) || (argc > (optind + 2)))
 #else
-       if (optind != argc && optind + 1 != argc)
+       if ((argc < optind) || (argc > (optind + 1)))
 #endif
+       {
                usage ();
+       }
 
        /*
         * If there are two left over filenames, use those as the group and
         * group password filenames.
         */
-
        if (optind != argc) {
                grp_file = argv[optind];
-               gr_name (grp_file);
+               gr_setdbname (grp_file);
+               use_system_grp_file = false;
        }
 #ifdef SHADOWGRP
-       if (optind + 2 == argc) {
+       if ((optind + 2) == argc) {
                sgr_file = argv[optind + 1];
-               sgr_name (sgr_file);
-               is_shadow = 1;
-       } else if (optind == argc)
+               sgr_setdbname (sgr_file);
+               is_shadow = true;
+               use_system_sgr_file = false;
+       } else if (optind == argc) {
                is_shadow = sgr_file_present ();
+       }
 #endif
+}
 
+/*
+ * open_files - open the shadow database
+ *
+ *     In read-only mode, the databases are not locked and are opened
+ *     only for reading.
+ */
+static void open_files (void)
+{
        /*
         * Lock the files if we aren't in "read-only" mode
         */
-
        if (!read_only) {
-               if (!gr_lock ()) {
-                       fprintf (stderr, _("%s: cannot lock file %s\n"),
-                                Prog, grp_file);
-                       if (optind == argc)
-                               SYSLOG ((LOG_WARN, "cannot lock %s", grp_file));
-                       closelog ();
-                       exit (E_CANT_LOCK);
+               if (gr_lock () == 0) {
+                       fprintf (stderr,
+                                _("%s: cannot lock %s; try again later.\n"),
+                                Prog, grp_file);
+                       fail_exit (E_CANT_LOCK);
                }
+               gr_locked = true;
 #ifdef SHADOWGRP
-               if (is_shadow && !sgr_lock ()) {
-                       fprintf (stderr, _("%s: cannot lock file %s\n"),
-                                Prog, sgr_file);
-                       if (optind == argc)
-                               SYSLOG ((LOG_WARN, "cannot lock %s", sgr_file));
-                       closelog ();
-                       exit (E_CANT_LOCK);
+               if (is_shadow) {
+                       if (sgr_lock () == 0) {
+                               fprintf (stderr,
+                                        _("%s: cannot lock %s; try again later.\n"),
+                                        Prog, sgr_file);
+                               fail_exit (E_CANT_LOCK);
+                       }
+                       sgr_locked = true;
                }
 #endif
        }
@@ -250,70 +268,215 @@ int main (int argc, char **argv)
         * Open the files. Use O_RDONLY if we are in read_only mode,
         * O_RDWR otherwise.
         */
-
-       if (!gr_open (read_only ? O_RDONLY : O_RDWR)) {
-               fprintf (stderr, _("%s: cannot open file %s\n"), Prog,
-                        grp_file);
-               if (optind == argc)
+       if (gr_open (read_only ? O_RDONLY : O_RDWR) == 0) {
+               fprintf (stderr, _("%s: cannot open %s\n"), Prog,
+                        grp_file);
+               if (use_system_grp_file) {
                        SYSLOG ((LOG_WARN, "cannot open %s", grp_file));
-               closelog ();
-               exit (E_CANT_OPEN);
+               }
+               fail_exit (E_CANT_OPEN);
        }
 #ifdef SHADOWGRP
-       if (is_shadow && !sgr_open (read_only ? O_RDONLY : O_RDWR)) {
-               fprintf (stderr, _("%s: cannot open file %s\n"), Prog,
-                        sgr_file);
-               if (optind == argc)
+       if (is_shadow && (sgr_open (read_only ? O_RDONLY : O_RDWR) == 0)) {
+               fprintf (stderr, _("%s: cannot open %s\n"), Prog,
+                        sgr_file);
+               if (use_system_sgr_file) {
                        SYSLOG ((LOG_WARN, "cannot open %s", sgr_file));
-               closelog ();
-               exit (E_CANT_OPEN);
+               }
+               fail_exit (E_CANT_OPEN);
        }
 #endif
+}
 
-       if (sort_mode) {
-               gr_sort ();
+/*
+ * close_files - close and unlock the group/gshadow databases
+ *
+ *     If changed is not set, the databases are not closed, and no
+ *     changes are committed in the databases. The databases are
+ *     unlocked anyway.
+ */
+static void close_files (bool changed)
+{
+       /*
+        * All done. If there were no change we can just abandon any
+        * changes to the files.
+        */
+       if (changed) {
+               if (gr_close () == 0) {
+                       fprintf (stderr, _("%s: failure while writing changes to %s\n"),
+                                Prog, grp_file);
+                       fail_exit (E_CANT_UPDATE);
+               }
 #ifdef SHADOWGRP
-               if (is_shadow)
-                       sgr_sort ();
+               if (is_shadow && (sgr_close () == 0)) {
+                       fprintf (stderr, _("%s: failure while writing changes to %s\n"),
+                                Prog, sgr_file);
+                       fail_exit (E_CANT_UPDATE);
+               }
 #endif
-               goto write_and_bye;
        }
 
        /*
-        * Loop through the entire group file.
+        * Don't be anti-social - unlock the files when you're done.
+        */
+#ifdef SHADOWGRP
+       if (sgr_locked) {
+               if (sgr_unlock () == 0) {
+                       fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
+                       SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
+                       /* continue */
+               }
+               sgr_locked = false;
+       }
+#endif
+       if (gr_locked) {
+               if (gr_unlock () == 0) {
+                       fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
+                       SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
+                       /* continue */
+               }
+               gr_locked = false;
+       }
+}
+
+/*
+ * check_members - check that every members of a group exist
+ *
+ *     If an error is detected, *errors is incremented.
+ *
+ *     The user will be prompted for the removal of the non-existent
+ *     user.
+ *
+ *     If any changes are performed, the return value will be 1,
+ *     otherwise check_members() returns 0.
+ *
+ *     fmt_info, fmt_prompt, and fmt_syslog are used for logging.
+ *       * fmt_info must contain two string flags (%s): for the group's
+ *         name and the missing member.
+ *       * fmt_prompt must contain one string flags (%s): the missing
+ *         member.
+ *       * fmt_syslog must contain two string flags (%s): for the
+ *         group's name and the missing member.
+ */
+static int check_members (const char *groupname,
+                          char **members,
+                          const char *fmt_info,
+                          const char *fmt_prompt,
+                          const char *fmt_syslog,
+                          int *errors)
+{
+       int i;
+       int members_changed = 0;
+
+       /*
+        * Make sure each member exists
         */
+       for (i = 0; NULL != members[i]; i++) {
+               /* local, no need for xgetpwnam */
+               if (getpwnam (members[i]) != NULL) {
+                       continue;
+               }
+               /*
+                * Can't find this user. Remove them
+                * from the list.
+                */
+               *errors += 1;
+               printf (fmt_info, groupname, members[i]);
+               printf (fmt_prompt, members[i]);
+
+               if (!yes_or_no (read_only)) {
+                       continue;
+               }
+
+               SYSLOG ((LOG_INFO, fmt_syslog, members[i], groupname));
+               members_changed = 1;
+               delete_member (members, members[i]);
+
+               /* Rewind in case of removal */
+               i--;
+       }
+
+       return members_changed;
+}
+
+#ifdef SHADOWGRP
+/*
+ * compare_members_lists - make sure the list of members is contained in
+ *                         another list.
+ *
+ *     compare_members_lists() checks that all the members of members are
+ *     also in other_members.
+ *     file and other_file are used for logging.
+ *
+ *     TODO: No changes are performed on the lists.
+ */
+static void compare_members_lists (const char *groupname,
+                                   char **members,
+                                   char **other_members,
+                                   const char *file,
+                                   const char *other_file)
+{
+       char **pmem, **other_pmem;
+
+       for (pmem = members; NULL != *pmem; pmem++) {
+               for (other_pmem = other_members; NULL != *other_pmem; other_pmem++) {
+                       if (strcmp (*pmem, *other_pmem) == 0) {
+                               break;
+                       }
+               }
+               if (*other_pmem == NULL) {
+                       printf
+                           ("'%s' is a member of the '%s' group in %s but not in %s\n",
+                            *pmem, groupname, file, other_file);
+               }
+       }
+}
+#endif                         /* SHADOWGRP */
 
-       for (gre = __gr_get_head (); gre; gre = gre->next) {
+/*
+ * check_grp_file - check the content of the group file
+ */
+static void check_grp_file (int *errors, bool *changed)
+{
+       struct commonio_entry *gre, *tgre;
+       struct group *grp;
+#ifdef SHADOWGRP
+       struct sgrp *sgr;
+#endif
+
+       /*
+        * Loop through the entire group file.
+        */
+       for (gre = __gr_get_head (); NULL != gre; gre = gre->next) {
                /*
                 * Skip all NIS entries.
                 */
 
-               if (gre->line[0] == '+' || gre->line[0] == '-')
+               if ((gre->line[0] == '+') || (gre->line[0] == '-')) {
                        continue;
+               }
 
                /*
                 * Start with the entries that are completely corrupt. They
                 * have no (struct group) entry because they couldn't be
                 * parsed properly.
                 */
-
-               if (!gre->eptr) {
+               if (NULL == gre->eptr) {
 
                        /*
                         * Tell the user this entire line is bogus and ask
                         * them to delete it.
                         */
-
-                       printf (_("invalid group file entry\n"));
-                       printf (_("delete line `%s'? "), gre->line);
-                       errors++;
+                       (void) puts (_("invalid group file entry"));
+                       printf (_("delete line '%s'? "), gre->line);
+                       *errors += 1;
 
                        /*
                         * prompt the user to delete the entry or not
                         */
-
-                       if (!yes_or_no ())
+                       if (!yes_or_no (read_only)) {
                                continue;
+                       }
 
                        /*
                         * All group file deletions wind up here. This code
@@ -321,11 +484,10 @@ int main (int argc, char **argv)
                         * When done, it skips back to the top of the loop
                         * to try out the next list element.
                         */
-
                      delete_gr:
-                       SYSLOG ((LOG_INFO, "delete group line `%s'",
-                                gre->line));
-                       deleted++;
+                       SYSLOG ((LOG_INFO, "delete group line '%s'",
+                                gre->line));
+                       *changed = true;
 
                        __gr_del_entry (gre);
                        continue;
@@ -334,57 +496,63 @@ int main (int argc, char **argv)
                /*
                 * Group structure is good, start using it.
                 */
-
                grp = gre->eptr;
 
                /*
                 * Make sure this entry has a unique name.
                 */
-
-               for (tgre = __gr_get_head (); tgre; tgre = tgre->next) {
+               for (tgre = __gr_get_head (); NULL != tgre; tgre = tgre->next) {
 
                        const struct group *ent = tgre->eptr;
 
                        /*
                         * Don't check this entry
                         */
-
-                       if (tgre == gre)
+                       if (tgre == gre) {
                                continue;
+                       }
 
                        /*
                         * Don't check invalid entries.
                         */
-
-                       if (!ent)
+                       if (NULL == ent) {
                                continue;
+                       }
 
-                       if (strcmp (grp->gr_name, ent->gr_name) != 0)
+                       if (strcmp (grp->gr_name, ent->gr_name) != 0) {
                                continue;
+                       }
 
                        /*
                         * Tell the user this entry is a duplicate of
                         * another and ask them to delete it.
                         */
-
-                       puts (_("duplicate group entry\n"));
-                       printf (_("delete line `%s'? "), gre->line);
-                       errors++;
+                       (void) puts (_("duplicate group entry"));
+                       printf (_("delete line '%s'? "), gre->line);
+                       *errors += 1;
 
                        /*
                         * prompt the user to delete the entry or not
                         */
-
-                       if (yes_or_no ())
+                       if (yes_or_no (read_only)) {
                                goto delete_gr;
+                       }
                }
 
                /*
                 * Check for invalid group names.  --marekm
                 */
-               if (!check_group_name (grp->gr_name)) {
-                       errors++;
-                       printf (_("invalid group name `%s'\n"), grp->gr_name);
+               if (!is_valid_group_name (grp->gr_name)) {
+                       *errors += 1;
+                       printf (_("invalid group name '%s'\n"), grp->gr_name);
+               }
+
+               /*
+                * Check for invalid group ID.
+                */
+               if (grp->gr_gid == (gid_t)-1) {
+                       printf (_("invalid group ID '%lu'\n"), (long unsigned int)grp->gr_gid);
+                       *errors += 1;
                }
 
                /*
@@ -392,73 +560,116 @@ int main (int argc, char **argv)
                 * groups with no members are returned as groups with one
                 * member "", causing grpck to fail.  --marekm
                 */
+               if (   (NULL != grp->gr_mem[0])
+                   && (NULL == grp->gr_mem[1])
+                   && ('\0' == grp->gr_mem[0][0])) {
+                       grp->gr_mem[0] = NULL;
+               }
 
-               if (grp->gr_mem[0] && !grp->gr_mem[1]
-                   && *(grp->gr_mem[0]) == '\0')
-                       grp->gr_mem[0] = (char *) 0;
+               if (check_members (grp->gr_name, grp->gr_mem,
+                                  _("group %s: no user %s\n"),
+                                  _("delete member '%s'? "),
+                                  "delete member '%s' from group '%s'",
+                                  errors) == 1) {
+                       *changed = true;
+                       gre->changed = true;
+                       __gr_set_changed ();
+               }
 
+#ifdef SHADOWGRP
                /*
-                * Make sure each member exists
+                * Make sure this entry exists in the /etc/gshadow file.
                 */
 
-               for (i = 0; grp->gr_mem[i]; i++) {
-                       if (getpwnam (grp->gr_mem[i]))
-                               continue;
-                       /*
-                        * Can't find this user. Remove them
-                        * from the list.
-                        */
-
-                       errors++;
-                       printf (_("group %s: no user %s\n"),
-                               grp->gr_name, grp->gr_mem[i]);
-                       printf (_("delete member `%s'? "), grp->gr_mem[i]);
-
-                       if (!yes_or_no ())
-                               continue;
-
-                       SYSLOG ((LOG_INFO, "delete member `%s' group `%s'",
-                                grp->gr_mem[i], grp->gr_name));
-                       deleted++;
-                       delete_member (grp->gr_mem, grp->gr_mem[i]);
-                       gre->changed = 1;
-                       __gr_set_changed ();
+               if (is_shadow) {
+                       sgr = (struct sgrp *) sgr_locate (grp->gr_name);
+                       if (sgr == NULL) {
+                               printf (_("no matching group file entry in %s\n"),
+                                       sgr_file);
+                               printf (_("add group '%s' in %s? "),
+                                       grp->gr_name, sgr_file);
+                               *errors += 1;
+                               if (yes_or_no (read_only)) {
+                                       struct sgrp sg;
+                                       struct group gr;
+                                       static char *empty = NULL;
+
+                                       sg.sg_name = grp->gr_name;
+                                       sg.sg_passwd = grp->gr_passwd;
+                                       sg.sg_adm = &empty;
+                                       sg.sg_mem = grp->gr_mem;
+                                       SYSLOG ((LOG_INFO,
+                                                "add group '%s' to '%s'",
+                                                grp->gr_name, sgr_file));
+                                       *changed = true;
+
+                                       if (sgr_update (&sg) == 0) {
+                                               fprintf (stderr,
+                                                        _("%s: failed to prepare the new %s entry '%s'\n"),
+                                                        Prog, sgr_dbname (), sg.sg_name);
+                                               fail_exit (E_CANT_UPDATE);
+                                       }
+                                       /* remove password from /etc/group */
+                                       gr = *grp;
+                                       gr.gr_passwd = SHADOW_PASSWD_STRING;    /* XXX warning: const */
+                                       if (gr_update (&gr) == 0) {
+                                               fprintf (stderr,
+                                                        _("%s: failed to prepare the new %s entry '%s'\n"),
+                                                        Prog, gr_dbname (), gr.gr_name);
+                                               fail_exit (E_CANT_UPDATE);
+                                       }
+                               }
+                       } else {
+                               /**
+                                * Verify that all the members defined in /etc/group are also
+                                * present in /etc/gshadow.
+                                */
+                               compare_members_lists (grp->gr_name,
+                                                      grp->gr_mem, sgr->sg_mem,
+                                                      grp_file, sgr_file);
+                       }
                }
+#endif
+
        }
+}
 
-#ifdef SHADOWGRP
-       if (!is_shadow)
-               goto shadow_done;
+#ifdef SHADOWGRP
+/*
+ * check_sgr_file - check the content of the shadowed group file (gshadow)
+ */
+static void check_sgr_file (int *errors, bool *changed)
+{
+       struct group *grp;
+       struct commonio_entry *sge, *tsge;
+       struct sgrp *sgr;
 
        /*
         * Loop through the entire shadow group file.
         */
-
-       for (sge = __sgr_get_head (); sge; sge = sge->next) {
+       for (sge = __sgr_get_head (); NULL != sge; sge = sge->next) {
 
                /*
                 * Start with the entries that are completely corrupt. They
                 * have no (struct sgrp) entry because they couldn't be
                 * parsed properly.
                 */
-
-               if (!sge->eptr) {
+               if (NULL == sge->eptr) {
 
                        /*
                         * Tell the user this entire line is bogus and ask
                         * them to delete it.
                         */
-
-                       printf (_("invalid shadow group file entry\n"));
-                       printf (_("delete line `%s'? "), sge->line);
-                       errors++;
+                       (void) puts (_("invalid shadow group file entry"));
+                       printf (_("delete line '%s'? "), sge->line);
+                       *errors += 1;
 
                        /*
                         * prompt the user to delete the entry or not
                         */
-
-                       if (!yes_or_no ())
+                       if (!yes_or_no (read_only)) {
                                continue;
+                       }
 
                        /*
                         * All shadow group file deletions wind up here. 
@@ -466,11 +677,10 @@ int main (int argc, char **argv)
                         * linked list. When done, it skips back to the top
                         * of the loop to try out the next list element.
                         */
-
                      delete_sg:
-                       SYSLOG ((LOG_INFO, "delete shadow line `%s'",
-                                sge->line));
-                       deleted++;
+                       SYSLOG ((LOG_INFO, "delete shadow line '%s'",
+                                sge->line));
+                       *changed = true;
 
                        __sgr_del_entry (sge);
                        continue;
@@ -479,166 +689,157 @@ int main (int argc, char **argv)
                /*
                 * Shadow group structure is good, start using it.
                 */
-
                sgr = sge->eptr;
 
                /*
                 * Make sure this entry has a unique name.
                 */
-
-               for (tsge = __sgr_get_head (); tsge; tsge = tsge->next) {
+               for (tsge = __sgr_get_head (); NULL != tsge; tsge = tsge->next) {
 
                        const struct sgrp *ent = tsge->eptr;
 
                        /*
                         * Don't check this entry
                         */
-
-                       if (tsge == sge)
+                       if (tsge == sge) {
                                continue;
+                       }
 
                        /*
                         * Don't check invalid entries.
                         */
-
-                       if (!ent)
+                       if (NULL == ent) {
                                continue;
+                       }
 
-                       if (strcmp (sgr->sg_name, ent->sg_name) != 0)
+                       if (strcmp (sgr->sg_name, ent->sg_name) != 0) {
                                continue;
+                       }
 
                        /*
                         * Tell the user this entry is a duplicate of
                         * another and ask them to delete it.
                         */
-
-                       puts (_("duplicate shadow group entry\n"));
-                       printf (_("delete line `%s'? "), sge->line);
-                       errors++;
+                       (void) puts (_("duplicate shadow group entry"));
+                       printf (_("delete line '%s'? "), sge->line);
+                       *errors += 1;
 
                        /*
                         * prompt the user to delete the entry or not
                         */
-
-                       if (yes_or_no ())
+                       if (yes_or_no (read_only)) {
                                goto delete_sg;
+                       }
                }
 
                /*
                 * Make sure this entry exists in the /etc/group file.
                 */
-
-               if (!gr_locate (sgr->sg_name)) {
-                       puts (_("no matching group file entry\n"));
-                       printf (_("delete line `%s'? "), sge->line);
-                       errors++;
-                       if (yes_or_no ())
+               grp = (struct group *) gr_locate (sgr->sg_name);
+               if (grp == NULL) {
+                       printf (_("no matching group file entry in %s\n"),
+                               grp_file);
+                       printf (_("delete line '%s'? "), sge->line);
+                       *errors += 1;
+                       if (yes_or_no (read_only)) {
                                goto delete_sg;
+                       }
+               } else {
+                       /**
+                        * Verify that the all members defined in /etc/gshadow are also
+                        * present in /etc/group.
+                        */
+                       compare_members_lists (sgr->sg_name,
+                                              sgr->sg_mem, grp->gr_mem,
+                                              sgr_file, grp_file);
                }
 
                /*
                 * Make sure each administrator exists
                 */
-
-               for (i = 0; sgr->sg_adm[i]; i++) {
-                       if (getpwnam (sgr->sg_adm[i]))
-                               continue;
-                       /*
-                        * Can't find this user. Remove them
-                        * from the list.
-                        */
-
-                       errors++;
-                       printf (_
-                               ("shadow group %s: no administrative user %s\n"),
-                               sgr->sg_name, sgr->sg_adm[i]);
-                       printf (_("delete administrative member `%s'? "),
-                               sgr->sg_adm[i]);
-
-                       if (!yes_or_no ())
-                               continue;
-
-                       SYSLOG ((LOG_INFO,
-                                "delete admin `%s' from shadow group `%s'",
-                                sgr->sg_adm[i], sgr->sg_name));
-                       deleted++;
-                       delete_member (sgr->sg_adm, sgr->sg_adm[i]);
-                       sge->changed = 1;
+               if (check_members (sgr->sg_name, sgr->sg_adm,
+                                  _("shadow group %s: no administrative user %s\n"),
+                                  _("delete administrative member '%s'? "),
+                                  "delete admin '%s' from shadow group '%s'",
+                                  errors) == 1) {
+                       *changed = true;
+                       sge->changed = true;
                        __sgr_set_changed ();
                }
 
                /*
                 * Make sure each member exists
                 */
-
-               for (i = 0; sgr->sg_mem[i]; i++) {
-                       if (getpwnam (sgr->sg_mem[i]))
-                               continue;
-
-                       /*
-                        * Can't find this user. Remove them from the list.
-                        */
-
-                       errors++;
-                       printf (_("shadow group %s: no user %s\n"),
-                               sgr->sg_name, sgr->sg_mem[i]);
-                       printf (_("delete member `%s'? "), sgr->sg_mem[i]);
-
-                       if (!yes_or_no ())
-                               continue;
-
-                       SYSLOG ((LOG_INFO,
-                                "delete member `%s' from shadow group `%s'",
-                                sgr->sg_mem[i], sgr->sg_name));
-                       deleted++;
-                       delete_member (sgr->sg_mem, sgr->sg_mem[i]);
-                       sge->changed = 1;
+               if (check_members (sgr->sg_name, sgr->sg_mem,
+                                  _("shadow group %s: no user %s\n"),
+                                  _("delete member '%s'? "),
+                                  "delete member '%s' from shadow group '%s'",
+                                  errors) == 1) {
+                       *changed = true;
+                       sge->changed = true;
                        __sgr_set_changed ();
                }
        }
-
-      shadow_done:
+}
 #endif                         /* SHADOWGRP */
 
+/*
+ * grpck - verify group file integrity
+ */
+int main (int argc, char **argv)
+{
+       int errors = 0;
+       bool changed = false;
+
        /*
-        * All done. If there were no deletions we can just abandon any
-        * changes to the files.
+        * Get my name so that I can use it to report errors.
         */
+       Prog = Basename (argv[0]);
+
+       (void) setlocale (LC_ALL, "");
+       (void) bindtextdomain (PACKAGE, LOCALEDIR);
+       (void) textdomain (PACKAGE);
+
+       OPENLOG ("grpck");
 
-       if (deleted) {
-             write_and_bye:
-               if (!gr_close ()) {
-                       fprintf (stderr, _("%s: cannot update file %s\n"),
-                                Prog, grp_file);
-                       exit (E_CANT_UPDATE);
+       /* Parse the command line arguments */
+       process_flags (argc, argv);
+
+       open_files ();
+
+       if (sort_mode) {
+               gr_sort ();
+#ifdef SHADOWGRP
+               if (is_shadow) {
+                       sgr_sort ();
                }
+               changed = true;
+#endif
+       } else {
+               check_grp_file (&errors, &changed);
 #ifdef SHADOWGRP
-               if (is_shadow && !sgr_close ()) {
-                       fprintf (stderr, _("%s: cannot update file %s\n"),
-                                Prog, sgr_file);
-                       exit (E_CANT_UPDATE);
+               if (is_shadow) {
+                       check_sgr_file (&errors, &changed);
                }
 #endif
        }
 
-       /*
-        * Don't be anti-social - unlock the files when you're done.
-        */
+       /* Commit the change in the database if needed */
+       close_files (changed);
 
-#ifdef SHADOWGRP
-       if (is_shadow)
-               sgr_unlock ();
-#endif
-       (void) gr_unlock ();
+       nscd_flush_cache ("group");
 
        /*
         * Tell the user what we did and exit.
         */
+       if (0 != errors) {
+               if (changed) {
+                       printf (_("%s: the files have been updated\n"), Prog);
+               } else {
+                       printf (_("%s: no changes\n"), Prog);
+               }
+       }
 
-       if (errors)
-               printf (deleted ?
-                       _("%s: the files have been updated\n") :
-                       _("%s: no changes\n"), Prog);
-
-       exit (errors ? E_BAD_ENTRY : E_OKAY);
+       return ((0 != errors) ? E_BAD_ENTRY : E_OKAY);
 }
+