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