]> granicus.if.org Git - shadow/blob - src/grpck.c
* src/usermod.c (process_flags): Report usage if no options are
[shadow] / src / grpck.c
1 /*
2  * Copyright (c) 1992 - 1994, Julianne Frances Haugh
3  * Copyright (c) 1996 - 2000, Marek Michałkiewicz
4  * Copyright (c) 2001       , Michał Moskal
5  * Copyright (c) 2001 - 2006, Tomasz Kłoczko
6  * Copyright (c) 2007 - 2009, Nicolas François
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. The name of the copyright holders or contributors may not be used to
18  *    endorse or promote products derived from this software without
19  *    specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24  * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
25  * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33
34 #include <config.h>
35
36 #ident "$Id$"
37
38 #include <fcntl.h>
39 #include <grp.h>
40 #include <pwd.h>
41 #include <stdio.h>
42 #include "chkname.h"
43 #include "commonio.h"
44 #include "defines.h"
45 #include "groupio.h"
46 #include "nscd.h"
47 #include "prototypes.h"
48
49 #ifdef SHADOWGRP
50 #include "sgroupio.h"
51 #endif
52
53 /*
54  * Exit codes
55  */
56 /*@-exitarg@*/
57 #define E_OKAY          0
58 #define E_USAGE         1
59 #define E_BAD_ENTRY     2
60 #define E_CANT_OPEN     3
61 #define E_CANT_LOCK     4
62 #define E_CANT_UPDATE   5
63
64 /*
65  * Global variables
66  */
67 const char *Prog;
68
69 static const char *grp_file = GROUP_FILE;
70 static bool use_system_grp_file = true;
71
72 #ifdef  SHADOWGRP
73 static const char *sgr_file = SGROUP_FILE;
74 static bool use_system_sgr_file = true;
75 static bool is_shadow = false;
76 static bool sgr_locked = false;
77 #endif
78 static bool gr_locked = false;
79 /* Options */
80 static bool read_only = false;
81 static bool sort_mode = false;
82
83 /* local function prototypes */
84 static void fail_exit (int status);
85 static void usage (void);
86 static void delete_member (char **, const char *);
87 static void process_flags (int argc, char **argv);
88 static void open_files (void);
89 static void close_files (bool changed);
90 static int check_members (const char *groupname,
91                           char **members,
92                           const char *fmt_info,
93                           const char *fmt_prompt,
94                           const char *fmt_syslog,
95                           int *errors);
96 static void check_grp_file (int *errors, bool *changed);
97 #ifdef SHADOWGRP
98 static void compare_members_lists (const char *groupname,
99                                    char **members,
100                                    char **other_members,
101                                    const char *file,
102                                    const char *other_file);
103 static void check_sgr_file (int *errors, bool *changed);
104 #endif
105
106 /*
107  * fail_exit - exit with an error code after unlocking files
108  */
109 static void fail_exit (int status)
110 {
111         if (gr_locked) {
112                 if (gr_unlock () == 0) {
113                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
114                         SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
115                         /* continue */
116                 }
117         }
118
119 #ifdef  SHADOWGRP
120         if (sgr_locked) {
121                 if (sgr_unlock () == 0) {
122                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
123                         SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
124                         /* continue */
125                 }
126         }
127 #endif
128
129         closelog ();
130
131         exit (status);
132 }
133
134 /*
135  * usage - print syntax message and exit
136  */
137 static void usage (void)
138 {
139 #ifdef  SHADOWGRP
140         fprintf (stderr, _("Usage: %s [-r] [-s] [group [gshadow]]\n"), Prog);
141 #else
142         fprintf (stderr, _("Usage: %s [-r] [-s] [group]\n"), Prog);
143 #endif
144         exit (E_USAGE);
145 }
146
147 /*
148  * delete_member - delete an entry in a list of members
149  *
150  * It only deletes the first entry with the given name.
151  */
152 static void delete_member (char **list, const char *member)
153 {
154         int i;
155
156         for (i = 0; list[i]; i++) {
157                 if (list[i] == member) {
158                         break;
159                 }
160         }
161
162         if (list[i]) {
163                 for (; list[i]; i++) {
164                         list[i] = list[i + 1];
165                 }
166         }
167 }
168
169 /*
170  * process_flags - parse the command line options
171  *
172  *      It will not return if an error is encountered.
173  */
174 static void process_flags (int argc, char **argv)
175 {
176         int arg;
177
178         /*
179          * Parse the command line arguments
180          */
181         while ((arg = getopt (argc, argv, "qrs")) != EOF) {
182                 switch (arg) {
183                 case 'q':
184                         /* quiet - ignored for now */
185                         break;
186                 case 'r':
187                         read_only = true;
188                         break;
189                 case 's':
190                         sort_mode = true;
191                         break;
192                 default:
193                         usage ();
194                 }
195         }
196
197         if (sort_mode && read_only) {
198                 fprintf (stderr, _("%s: -s and -r are incompatibile\n"), Prog);
199                 exit (E_USAGE);
200         }
201
202         /*
203          * Make certain we have the right number of arguments
204          */
205 #ifdef  SHADOWGRP
206         if ((argc < optind) || (argc > (optind + 2)))
207 #else
208         if ((argc < optind) || (argc > (optind + 1)))
209 #endif
210         {
211                 usage ();
212         }
213
214         /*
215          * If there are two left over filenames, use those as the group and
216          * group password filenames.
217          */
218         if (optind != argc) {
219                 grp_file = argv[optind];
220                 gr_setdbname (grp_file);
221                 use_system_grp_file = false;
222         }
223 #ifdef  SHADOWGRP
224         if ((optind + 2) == argc) {
225                 sgr_file = argv[optind + 1];
226                 sgr_setdbname (sgr_file);
227                 is_shadow = true;
228                 use_system_sgr_file = false;
229         } else if (optind == argc) {
230                 is_shadow = sgr_file_present ();
231         }
232 #endif
233 }
234
235 /*
236  * open_files - open the shadow database
237  *
238  *      In read-only mode, the databases are not locked and are opened
239  *      only for reading.
240  */
241 static void open_files (void)
242 {
243         /*
244          * Lock the files if we aren't in "read-only" mode
245          */
246         if (!read_only) {
247                 if (gr_lock () == 0) {
248                         fprintf (stderr,
249                                  _("%s: cannot lock %s; try again later.\n"),
250                                  Prog, grp_file);
251                         fail_exit (E_CANT_LOCK);
252                 }
253                 gr_locked = true;
254 #ifdef  SHADOWGRP
255                 if (is_shadow) {
256                         if (sgr_lock () == 0) {
257                                 fprintf (stderr,
258                                          _("%s: cannot lock %s; try again later.\n"),
259                                          Prog, sgr_file);
260                                 fail_exit (E_CANT_LOCK);
261                         }
262                         sgr_locked = true;
263                 }
264 #endif
265         }
266
267         /*
268          * Open the files. Use O_RDONLY if we are in read_only mode,
269          * O_RDWR otherwise.
270          */
271         if (gr_open (read_only ? O_RDONLY : O_RDWR) == 0) {
272                 fprintf (stderr, _("%s: cannot open %s\n"), Prog,
273                          grp_file);
274                 if (use_system_grp_file) {
275                         SYSLOG ((LOG_WARN, "cannot open %s", grp_file));
276                 }
277                 fail_exit (E_CANT_OPEN);
278         }
279 #ifdef  SHADOWGRP
280         if (is_shadow && (sgr_open (read_only ? O_RDONLY : O_RDWR) == 0)) {
281                 fprintf (stderr, _("%s: cannot open %s\n"), Prog,
282                          sgr_file);
283                 if (use_system_sgr_file) {
284                         SYSLOG ((LOG_WARN, "cannot open %s", sgr_file));
285                 }
286                 fail_exit (E_CANT_OPEN);
287         }
288 #endif
289 }
290
291 /*
292  * close_files - close and unlock the group/gshadow databases
293  *
294  *      If changed is not set, the databases are not closed, and no
295  *      changes are committed in the databases. The databases are
296  *      unlocked anyway.
297  */
298 static void close_files (bool changed)
299 {
300         /*
301          * All done. If there were no change we can just abandon any
302          * changes to the files.
303          */
304         if (changed) {
305                 if (gr_close () == 0) {
306                         fprintf (stderr, _("%s: failure while writing changes to %s\n"),
307                                  Prog, grp_file);
308                         fail_exit (E_CANT_UPDATE);
309                 }
310 #ifdef  SHADOWGRP
311                 if (is_shadow && (sgr_close () == 0)) {
312                         fprintf (stderr, _("%s: failure while writing changes to %s\n"),
313                                  Prog, sgr_file);
314                         fail_exit (E_CANT_UPDATE);
315                 }
316 #endif
317         }
318
319         /*
320          * Don't be anti-social - unlock the files when you're done.
321          */
322 #ifdef  SHADOWGRP
323         if (sgr_locked) {
324                 if (sgr_unlock () == 0) {
325                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
326                         SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
327                         /* continue */
328                 }
329                 sgr_locked = false;
330         }
331 #endif
332         if (gr_locked) {
333                 if (gr_unlock () == 0) {
334                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
335                         SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
336                         /* continue */
337                 }
338                 gr_locked = false;
339         }
340 }
341
342 /*
343  * check_members - check that every members of a group exist
344  *
345  *      If an error is detected, *errors is incremented.
346  *
347  *      The user will be prompted for the removal of the non-existent
348  *      user.
349  *
350  *      If any changes are performed, the return value will be 1,
351  *      otherwise check_members() returns 0.
352  *
353  *      fmt_info, fmt_prompt, and fmt_syslog are used for logging.
354  *        * fmt_info must contain two string flags (%s): for the group's
355  *          name and the missing member.
356  *        * fmt_prompt must contain one string flags (%s): the missing
357  *          member.
358  *        * fmt_syslog must contain two string flags (%s): for the
359  *          group's name and the missing member.
360  */
361 static int check_members (const char *groupname,
362                           char **members,
363                           const char *fmt_info,
364                           const char *fmt_prompt,
365                           const char *fmt_syslog,
366                           int *errors)
367 {
368         int i;
369         int members_changed = 0;
370
371         /*
372          * Make sure each member exists
373          */
374         for (i = 0; NULL != members[i]; i++) {
375                 /* local, no need for xgetpwnam */
376                 if (getpwnam (members[i]) != NULL) {
377                         continue;
378                 }
379                 /*
380                  * Can't find this user. Remove them
381                  * from the list.
382                  */
383                 *errors += 1;
384                 printf (fmt_info, groupname, members[i]);
385                 printf (fmt_prompt, members[i]);
386
387                 if (!yes_or_no (read_only)) {
388                         continue;
389                 }
390
391                 SYSLOG ((LOG_INFO, fmt_syslog, members[i], groupname));
392                 members_changed = 1;
393                 delete_member (members, members[i]);
394
395                 /* Rewind in case of removal */
396                 i--;
397         }
398
399         return members_changed;
400 }
401
402 #ifdef SHADOWGRP
403 /*
404  * compare_members_lists - make sure the list of members is contained in
405  *                         another list.
406  *
407  *      compare_members_lists() checks that all the members of members are
408  *      also in other_members.
409  *      file and other_file are used for logging.
410  *
411  *      TODO: No changes are performed on the lists.
412  */
413 static void compare_members_lists (const char *groupname,
414                                    char **members,
415                                    char **other_members,
416                                    const char *file,
417                                    const char *other_file)
418 {
419         char **pmem, **other_pmem;
420
421         for (pmem = members; NULL != *pmem; pmem++) {
422                 for (other_pmem = other_members; NULL != *other_pmem; other_pmem++) {
423                         if (strcmp (*pmem, *other_pmem) == 0) {
424                                 break;
425                         }
426                 }
427                 if (*other_pmem == NULL) {
428                         printf
429                             ("'%s' is a member of the '%s' group in %s but not in %s\n",
430                              *pmem, groupname, file, other_file);
431                 }
432         }
433 }
434 #endif                          /* SHADOWGRP */
435
436 /*
437  * check_grp_file - check the content of the group file
438  */
439 static void check_grp_file (int *errors, bool *changed)
440 {
441         struct commonio_entry *gre, *tgre;
442         struct group *grp;
443 #ifdef SHADOWGRP
444         struct sgrp *sgr;
445 #endif
446
447         /*
448          * Loop through the entire group file.
449          */
450         for (gre = __gr_get_head (); NULL != gre; gre = gre->next) {
451                 /*
452                  * Skip all NIS entries.
453                  */
454
455                 if ((gre->line[0] == '+') || (gre->line[0] == '-')) {
456                         continue;
457                 }
458
459                 /*
460                  * Start with the entries that are completely corrupt. They
461                  * have no (struct group) entry because they couldn't be
462                  * parsed properly.
463                  */
464                 if (NULL == gre->eptr) {
465
466                         /*
467                          * Tell the user this entire line is bogus and ask
468                          * them to delete it.
469                          */
470                         (void) puts (_("invalid group file entry"));
471                         printf (_("delete line '%s'? "), gre->line);
472                         *errors += 1;
473
474                         /*
475                          * prompt the user to delete the entry or not
476                          */
477                         if (!yes_or_no (read_only)) {
478                                 continue;
479                         }
480
481                         /*
482                          * All group file deletions wind up here. This code
483                          * removes the current entry from the linked list.
484                          * When done, it skips back to the top of the loop
485                          * to try out the next list element.
486                          */
487                       delete_gr:
488                         SYSLOG ((LOG_INFO, "delete group line '%s'",
489                                  gre->line));
490                         *changed = true;
491
492                         __gr_del_entry (gre);
493                         continue;
494                 }
495
496                 /*
497                  * Group structure is good, start using it.
498                  */
499                 grp = gre->eptr;
500
501                 /*
502                  * Make sure this entry has a unique name.
503                  */
504                 for (tgre = __gr_get_head (); NULL != tgre; tgre = tgre->next) {
505
506                         const struct group *ent = tgre->eptr;
507
508                         /*
509                          * Don't check this entry
510                          */
511                         if (tgre == gre) {
512                                 continue;
513                         }
514
515                         /*
516                          * Don't check invalid entries.
517                          */
518                         if (NULL == ent) {
519                                 continue;
520                         }
521
522                         if (strcmp (grp->gr_name, ent->gr_name) != 0) {
523                                 continue;
524                         }
525
526                         /*
527                          * Tell the user this entry is a duplicate of
528                          * another and ask them to delete it.
529                          */
530                         (void) puts (_("duplicate group entry"));
531                         printf (_("delete line '%s'? "), gre->line);
532                         *errors += 1;
533
534                         /*
535                          * prompt the user to delete the entry or not
536                          */
537                         if (yes_or_no (read_only)) {
538                                 goto delete_gr;
539                         }
540                 }
541
542                 /*
543                  * Check for invalid group names.  --marekm
544                  */
545                 if (!is_valid_group_name (grp->gr_name)) {
546                         *errors += 1;
547                         printf (_("invalid group name '%s'\n"), grp->gr_name);
548                 }
549
550                 /*
551                  * Check for invalid group ID.
552                  */
553                 if (grp->gr_gid == (gid_t)-1) {
554                         printf (_("invalid group ID '%lu'\n"), (long unsigned int)grp->gr_gid);
555                         *errors += 1;
556                 }
557
558                 /*
559                  * Workaround for a NYS libc 5.3.12 bug on RedHat 4.2 -
560                  * groups with no members are returned as groups with one
561                  * member "", causing grpck to fail.  --marekm
562                  */
563                 if (   (NULL != grp->gr_mem[0])
564                     && (NULL == grp->gr_mem[1])
565                     && ('\0' == grp->gr_mem[0][0])) {
566                         grp->gr_mem[0] = NULL;
567                 }
568
569                 if (check_members (grp->gr_name, grp->gr_mem,
570                                    _("group %s: no user %s\n"),
571                                    _("delete member '%s'? "),
572                                    "delete member '%s' from group '%s'",
573                                    errors) == 1) {
574                         *changed = true;
575                         gre->changed = true;
576                         __gr_set_changed ();
577                 }
578
579 #ifdef  SHADOWGRP
580                 /*
581                  * Make sure this entry exists in the /etc/gshadow file.
582                  */
583
584                 if (is_shadow) {
585                         sgr = (struct sgrp *) sgr_locate (grp->gr_name);
586                         if (sgr == NULL) {
587                                 printf (_("no matching group file entry in %s\n"),
588                                         sgr_file);
589                                 printf (_("add group '%s' in %s? "),
590                                         grp->gr_name, sgr_file);
591                                 *errors += 1;
592                                 if (yes_or_no (read_only)) {
593                                         struct sgrp sg;
594                                         struct group gr;
595                                         static char *empty = NULL;
596
597                                         sg.sg_name = grp->gr_name;
598                                         sg.sg_passwd = grp->gr_passwd;
599                                         sg.sg_adm = &empty;
600                                         sg.sg_mem = grp->gr_mem;
601                                         SYSLOG ((LOG_INFO,
602                                                  "add group '%s' to '%s'",
603                                                  grp->gr_name, sgr_file));
604                                         *changed = true;
605
606                                         if (sgr_update (&sg) == 0) {
607                                                 fprintf (stderr,
608                                                          _("%s: failed to prepare the new %s entry '%s'\n"),
609                                                          Prog, sgr_dbname (), sg.sg_name);
610                                                 fail_exit (E_CANT_UPDATE);
611                                         }
612                                         /* remove password from /etc/group */
613                                         gr = *grp;
614                                         gr.gr_passwd = SHADOW_PASSWD_STRING;    /* XXX warning: const */
615                                         if (gr_update (&gr) == 0) {
616                                                 fprintf (stderr,
617                                                          _("%s: failed to prepare the new %s entry '%s'\n"),
618                                                          Prog, gr_dbname (), gr.gr_name);
619                                                 fail_exit (E_CANT_UPDATE);
620                                         }
621                                 }
622                         } else {
623                                 /**
624                                  * Verify that all the members defined in /etc/group are also
625                                  * present in /etc/gshadow.
626                                  */
627                                 compare_members_lists (grp->gr_name,
628                                                        grp->gr_mem, sgr->sg_mem,
629                                                        grp_file, sgr_file);
630
631                                 /* The group entry has a gshadow counterpart.
632                                  * Make sure no passwords are in group.
633                                  */
634                                 if (strcmp (grp->gr_passwd, SHADOW_PASSWD_STRING) != 0) {
635                                         printf (_("group %s has an entry in %s, but its password field in %s is not set to 'x'\n"),
636                                                 grp->gr_name, sgr_file, grp_file);
637                                         *errors += 1;
638                                 }
639                         }
640                 }
641 #endif
642
643         }
644 }
645
646 #ifdef SHADOWGRP
647 /*
648  * check_sgr_file - check the content of the shadowed group file (gshadow)
649  */
650 static void check_sgr_file (int *errors, bool *changed)
651 {
652         struct group *grp;
653         struct commonio_entry *sge, *tsge;
654         struct sgrp *sgr;
655
656         /*
657          * Loop through the entire shadow group file.
658          */
659         for (sge = __sgr_get_head (); NULL != sge; sge = sge->next) {
660
661                 /*
662                  * Start with the entries that are completely corrupt. They
663                  * have no (struct sgrp) entry because they couldn't be
664                  * parsed properly.
665                  */
666                 if (NULL == sge->eptr) {
667
668                         /*
669                          * Tell the user this entire line is bogus and ask
670                          * them to delete it.
671                          */
672                         (void) puts (_("invalid shadow group file entry"));
673                         printf (_("delete line '%s'? "), sge->line);
674                         *errors += 1;
675
676                         /*
677                          * prompt the user to delete the entry or not
678                          */
679                         if (!yes_or_no (read_only)) {
680                                 continue;
681                         }
682
683                         /*
684                          * All shadow group file deletions wind up here. 
685                          * This code removes the current entry from the
686                          * linked list. When done, it skips back to the top
687                          * of the loop to try out the next list element.
688                          */
689                       delete_sg:
690                         SYSLOG ((LOG_INFO, "delete shadow line '%s'",
691                                  sge->line));
692                         *changed = true;
693
694                         __sgr_del_entry (sge);
695                         continue;
696                 }
697
698                 /*
699                  * Shadow group structure is good, start using it.
700                  */
701                 sgr = sge->eptr;
702
703                 /*
704                  * Make sure this entry has a unique name.
705                  */
706                 for (tsge = __sgr_get_head (); NULL != tsge; tsge = tsge->next) {
707
708                         const struct sgrp *ent = tsge->eptr;
709
710                         /*
711                          * Don't check this entry
712                          */
713                         if (tsge == sge) {
714                                 continue;
715                         }
716
717                         /*
718                          * Don't check invalid entries.
719                          */
720                         if (NULL == ent) {
721                                 continue;
722                         }
723
724                         if (strcmp (sgr->sg_name, ent->sg_name) != 0) {
725                                 continue;
726                         }
727
728                         /*
729                          * Tell the user this entry is a duplicate of
730                          * another and ask them to delete it.
731                          */
732                         (void) puts (_("duplicate shadow group entry"));
733                         printf (_("delete line '%s'? "), sge->line);
734                         *errors += 1;
735
736                         /*
737                          * prompt the user to delete the entry or not
738                          */
739                         if (yes_or_no (read_only)) {
740                                 goto delete_sg;
741                         }
742                 }
743
744                 /*
745                  * Make sure this entry exists in the /etc/group file.
746                  */
747                 grp = (struct group *) gr_locate (sgr->sg_name);
748                 if (grp == NULL) {
749                         printf (_("no matching group file entry in %s\n"),
750                                 grp_file);
751                         printf (_("delete line '%s'? "), sge->line);
752                         *errors += 1;
753                         if (yes_or_no (read_only)) {
754                                 goto delete_sg;
755                         }
756                 } else {
757                         /**
758                          * Verify that the all members defined in /etc/gshadow are also
759                          * present in /etc/group.
760                          */
761                         compare_members_lists (sgr->sg_name,
762                                                sgr->sg_mem, grp->gr_mem,
763                                                sgr_file, grp_file);
764                 }
765
766                 /*
767                  * Make sure each administrator exists
768                  */
769                 if (check_members (sgr->sg_name, sgr->sg_adm,
770                                    _("shadow group %s: no administrative user %s\n"),
771                                    _("delete administrative member '%s'? "),
772                                    "delete admin '%s' from shadow group '%s'",
773                                    errors) == 1) {
774                         *changed = true;
775                         sge->changed = true;
776                         __sgr_set_changed ();
777                 }
778
779                 /*
780                  * Make sure each member exists
781                  */
782                 if (check_members (sgr->sg_name, sgr->sg_mem,
783                                    _("shadow group %s: no user %s\n"),
784                                    _("delete member '%s'? "),
785                                    "delete member '%s' from shadow group '%s'",
786                                    errors) == 1) {
787                         *changed = true;
788                         sge->changed = true;
789                         __sgr_set_changed ();
790                 }
791         }
792 }
793 #endif                          /* SHADOWGRP */
794
795 /*
796  * grpck - verify group file integrity
797  */
798 int main (int argc, char **argv)
799 {
800         int errors = 0;
801         bool changed = false;
802
803         /*
804          * Get my name so that I can use it to report errors.
805          */
806         Prog = Basename (argv[0]);
807
808         (void) setlocale (LC_ALL, "");
809         (void) bindtextdomain (PACKAGE, LOCALEDIR);
810         (void) textdomain (PACKAGE);
811
812         OPENLOG ("grpck");
813
814         /* Parse the command line arguments */
815         process_flags (argc, argv);
816
817         open_files ();
818
819         if (sort_mode) {
820                 gr_sort ();
821 #ifdef  SHADOWGRP
822                 if (is_shadow) {
823                         sgr_sort ();
824                 }
825                 changed = true;
826 #endif
827         } else {
828                 check_grp_file (&errors, &changed);
829 #ifdef  SHADOWGRP
830                 if (is_shadow) {
831                         check_sgr_file (&errors, &changed);
832                 }
833 #endif
834         }
835
836         /* Commit the change in the database if needed */
837         close_files (changed);
838
839         nscd_flush_cache ("group");
840
841         /*
842          * Tell the user what we did and exit.
843          */
844         if (0 != errors) {
845                 if (changed) {
846                         printf (_("%s: the files have been updated\n"), Prog);
847                 } else {
848                         printf (_("%s: no changes\n"), Prog);
849                 }
850         }
851
852         return ((0 != errors) ? E_BAD_ENTRY : E_OKAY);
853 }
854