]> granicus.if.org Git - shadow/blob - src/grpck.c
6b3193c2cdb0d03ea7d543edb57b31fa12800bbe
[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
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 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                         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                         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 (_
588                                         ("no matching group file entry in %s\n"),
589                                         sgr_file);
590                                 printf (_("add group '%s' in %s?"),
591                                         grp->gr_name, sgr_file);
592                                 *errors += 1;
593                                 if (yes_or_no (read_only)) {
594                                         struct sgrp sg;
595                                         struct group gr;
596                                         static char *empty = NULL;
597
598                                         sg.sg_name = grp->gr_name;
599                                         sg.sg_passwd = grp->gr_passwd;
600                                         sg.sg_adm = &empty;
601                                         sg.sg_mem = grp->gr_mem;
602                                         SYSLOG ((LOG_INFO,
603                                                  "add group '%s' to '%s'",
604                                                  grp->gr_name, sgr_file));
605                                         *changed = true;
606
607                                         if (sgr_update (&sg) == 0) {
608                                                 fprintf (stderr,
609                                                          _("%s: failed to prepare the new %s entry '%s'\n"),
610                                                          Prog, sgr_dbname (), sg.sg_name);
611                                                 fail_exit (E_CANT_UPDATE);
612                                         }
613                                         /* remove password from /etc/group */
614                                         gr = *grp;
615                                         gr.gr_passwd = SHADOW_PASSWD_STRING;    /* XXX warning: const */
616                                         if (gr_update (&gr) == 0) {
617                                                 fprintf (stderr,
618                                                          _("%s: failed to prepare the new %s entry '%s'\n"),
619                                                          Prog, gr_dbname (), gr.gr_name);
620                                                 fail_exit (E_CANT_UPDATE);
621                                         }
622                                 }
623                         } else {
624                                 /**
625                                  * Verify that all the members defined in /etc/group are also
626                                  * present in /etc/gshadow.
627                                  */
628                                 compare_members_lists (grp->gr_name,
629                                                        grp->gr_mem, sgr->sg_mem,
630                                                        grp_file, sgr_file);
631                         }
632                 }
633 #endif
634
635         }
636 }
637
638 #ifdef SHADOWGRP
639 /*
640  * check_sgr_file - check the content of the shadowed group file (gshadow)
641  */
642 static void check_sgr_file (int *errors, bool *changed)
643 {
644         struct group *grp;
645         struct commonio_entry *sge, *tsge;
646         struct sgrp *sgr;
647
648         /*
649          * Loop through the entire shadow group file.
650          */
651         for (sge = __sgr_get_head (); NULL != sge; sge = sge->next) {
652
653                 /*
654                  * Start with the entries that are completely corrupt. They
655                  * have no (struct sgrp) entry because they couldn't be
656                  * parsed properly.
657                  */
658                 if (NULL == sge->eptr) {
659
660                         /*
661                          * Tell the user this entire line is bogus and ask
662                          * them to delete it.
663                          */
664                         puts (_("invalid shadow group file entry"));
665                         printf (_("delete line '%s'? "), sge->line);
666                         *errors += 1;
667
668                         /*
669                          * prompt the user to delete the entry or not
670                          */
671                         if (!yes_or_no (read_only)) {
672                                 continue;
673                         }
674
675                         /*
676                          * All shadow group file deletions wind up here. 
677                          * This code removes the current entry from the
678                          * linked list. When done, it skips back to the top
679                          * of the loop to try out the next list element.
680                          */
681                       delete_sg:
682                         SYSLOG ((LOG_INFO, "delete shadow line '%s'",
683                                  sge->line));
684                         *changed = true;
685
686                         __sgr_del_entry (sge);
687                         continue;
688                 }
689
690                 /*
691                  * Shadow group structure is good, start using it.
692                  */
693                 sgr = sge->eptr;
694
695                 /*
696                  * Make sure this entry has a unique name.
697                  */
698                 for (tsge = __sgr_get_head (); NULL != tsge; tsge = tsge->next) {
699
700                         const struct sgrp *ent = tsge->eptr;
701
702                         /*
703                          * Don't check this entry
704                          */
705                         if (tsge == sge) {
706                                 continue;
707                         }
708
709                         /*
710                          * Don't check invalid entries.
711                          */
712                         if (NULL == ent) {
713                                 continue;
714                         }
715
716                         if (strcmp (sgr->sg_name, ent->sg_name) != 0) {
717                                 continue;
718                         }
719
720                         /*
721                          * Tell the user this entry is a duplicate of
722                          * another and ask them to delete it.
723                          */
724                         puts (_("duplicate shadow group entry"));
725                         printf (_("delete line '%s'? "), sge->line);
726                         *errors += 1;
727
728                         /*
729                          * prompt the user to delete the entry or not
730                          */
731                         if (yes_or_no (read_only)) {
732                                 goto delete_sg;
733                         }
734                 }
735
736                 /*
737                  * Make sure this entry exists in the /etc/group file.
738                  */
739                 grp = (struct group *) gr_locate (sgr->sg_name);
740                 if (grp == NULL) {
741                         printf (_("no matching group file entry in %s\n"),
742                                 grp_file);
743                         printf (_("delete line '%s'? "), sge->line);
744                         *errors += 1;
745                         if (yes_or_no (read_only)) {
746                                 goto delete_sg;
747                         }
748                 } else {
749                         /**
750                          * Verify that the all members defined in /etc/gshadow are also
751                          * present in /etc/group.
752                          */
753                         compare_members_lists (sgr->sg_name,
754                                                sgr->sg_mem, grp->gr_mem,
755                                                sgr_file, grp_file);
756                 }
757
758                 /*
759                  * Make sure each administrator exists
760                  */
761                 if (check_members (sgr->sg_name, sgr->sg_adm,
762                                    _("shadow group %s: no administrative user %s\n"),
763                                    _("delete administrative member '%s'? "),
764                                    "delete admin '%s' from shadow group '%s'",
765                                    errors) == 1) {
766                         *changed = true;
767                         sge->changed = true;
768                         __sgr_set_changed ();
769                 }
770
771                 /*
772                  * Make sure each member exists
773                  */
774                 if (check_members (sgr->sg_name, sgr->sg_mem,
775                                    _("shadow group %s: no user %s\n"),
776                                    _("delete member '%s'? "),
777                                    "delete member '%s' from shadow group '%s'",
778                                    errors) == 1) {
779                         *changed = true;
780                         sge->changed = true;
781                         __sgr_set_changed ();
782                 }
783         }
784 }
785 #endif                          /* SHADOWGRP */
786
787 /*
788  * grpck - verify group file integrity
789  */
790 int main (int argc, char **argv)
791 {
792         int errors = 0;
793         bool changed = false;
794
795         /*
796          * Get my name so that I can use it to report errors.
797          */
798         Prog = Basename (argv[0]);
799
800         (void) setlocale (LC_ALL, "");
801         (void) bindtextdomain (PACKAGE, LOCALEDIR);
802         (void) textdomain (PACKAGE);
803
804         OPENLOG ("grpck");
805
806         /* Parse the command line arguments */
807         process_flags (argc, argv);
808
809         open_files ();
810
811         if (sort_mode) {
812                 gr_sort ();
813 #ifdef  SHADOWGRP
814                 if (is_shadow) {
815                         sgr_sort ();
816                 }
817                 changed = true;
818 #endif
819         } else {
820                 check_grp_file (&errors, &changed);
821 #ifdef  SHADOWGRP
822                 if (is_shadow) {
823                         check_sgr_file (&errors, &changed);
824                 }
825 #endif
826         }
827
828         /* Commit the change in the database if needed */
829         close_files (changed);
830
831         nscd_flush_cache ("group");
832
833         /*
834          * Tell the user what we did and exit.
835          */
836         if (0 != errors) {
837                 printf (changed ?
838                         _("%s: the files have been updated\n") :
839                         _("%s: no changes\n"), Prog);
840         }
841
842         exit ((0 != errors) ? E_BAD_ENTRY : E_OKAY);
843 }
844