]> granicus.if.org Git - shadow/blob - src/grpck.c
* src/grpck.c: Use a bool when possible instead of int integers.
[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 - 2008, 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 static char *Prog;
68 static const char *grp_file = GROUP_FILE;
69 static bool use_system_grp_file = true;
70
71 #ifdef  SHADOWGRP
72 static const char *sgr_file = SGROUP_FILE;
73 static bool use_system_sgr_file = true;
74 static bool is_shadow = false;
75 #endif
76 /* Options */
77 static bool read_only = false;
78 static bool sort_mode = false;
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 (bool 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 check_grp_file (int *errors, bool *changed);
93 #ifdef SHADOWGRP
94 static void compare_members_lists (const char *groupname,
95                                    char **members,
96                                    char **other_members,
97                                    const char *file,
98                                    const char *other_file);
99 static void check_sgr_file (int *errors, bool *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 = true;
154                         break;
155                 case 's':
156                         sort_mode = true;
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 = false;
188         }
189 #ifdef  SHADOWGRP
190         if ((optind + 2) == argc) {
191                 sgr_file = argv[optind + 1];
192                 sgr_name (sgr_file);
193                 is_shadow = true;
194                 use_system_sgr_file = false;
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 (bool 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; NULL != members[i]; i++) {
333                 /* local, no need for xgetpwnam */
334                 if (getpwnam (members[i]) != NULL) {
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 #ifdef SHADOWGRP
358 /*
359  * compare_members_lists - make sure the list of members is contained in
360  *                         another list.
361  *
362  *      compare_members_lists() checks that all the members of members are
363  *      also in other_members.
364  *      file and other_file are used for logging.
365  *
366  *      TODO: No changes are performed on the lists.
367  */
368 static void compare_members_lists (const char *groupname,
369                                    char **members,
370                                    char **other_members,
371                                    const char *file,
372                                    const char *other_file)
373 {
374         char **pmem, **other_pmem;
375
376         for (pmem = members; NULL != *pmem; pmem++) {
377                 for (other_pmem = other_members; NULL != *other_pmem; other_pmem++) {
378                         if (strcmp (*pmem, *other_pmem) == 0) {
379                                 break;
380                         }
381                 }
382                 if (*other_pmem == NULL) {
383                         printf
384                             ("'%s' is a member of the '%s' group in %s but not in %s\n",
385                              *pmem, groupname, file, other_file);
386                 }
387         }
388 }
389 #endif                          /* SHADOWGRP */
390
391 /*
392  * check_grp_file - check the content of the group file
393  */
394 static void check_grp_file (int *errors, bool *changed)
395 {
396         struct commonio_entry *gre, *tgre;
397         struct group *grp;
398 #ifdef SHADOWGRP
399         struct sgrp *sgr;
400 #endif
401
402         /*
403          * Loop through the entire group file.
404          */
405         for (gre = __gr_get_head (); NULL != gre; gre = gre->next) {
406                 /*
407                  * Skip all NIS entries.
408                  */
409
410                 if ((gre->line[0] == '+') || (gre->line[0] == '-')) {
411                         continue;
412                 }
413
414                 /*
415                  * Start with the entries that are completely corrupt. They
416                  * have no (struct group) entry because they couldn't be
417                  * parsed properly.
418                  */
419                 if (NULL == gre->eptr) {
420
421                         /*
422                          * Tell the user this entire line is bogus and ask
423                          * them to delete it.
424                          */
425                         puts (_("invalid group file entry"));
426                         printf (_("delete line '%s'? "), gre->line);
427                         *errors += 1;
428
429                         /*
430                          * prompt the user to delete the entry or not
431                          */
432                         if (!yes_or_no (read_only)) {
433                                 continue;
434                         }
435
436                         /*
437                          * All group file deletions wind up here. This code
438                          * removes the current entry from the linked list.
439                          * When done, it skips back to the top of the loop
440                          * to try out the next list element.
441                          */
442                       delete_gr:
443                         SYSLOG ((LOG_INFO, "delete group line `%s'",
444                                  gre->line));
445                         *changed = true;
446
447                         __gr_del_entry (gre);
448                         continue;
449                 }
450
451                 /*
452                  * Group structure is good, start using it.
453                  */
454                 grp = gre->eptr;
455
456                 /*
457                  * Make sure this entry has a unique name.
458                  */
459                 for (tgre = __gr_get_head (); NULL != tgre; tgre = tgre->next) {
460
461                         const struct group *ent = tgre->eptr;
462
463                         /*
464                          * Don't check this entry
465                          */
466                         if (tgre == gre) {
467                                 continue;
468                         }
469
470                         /*
471                          * Don't check invalid entries.
472                          */
473                         if (NULL == ent) {
474                                 continue;
475                         }
476
477                         if (strcmp (grp->gr_name, ent->gr_name) != 0) {
478                                 continue;
479                         }
480
481                         /*
482                          * Tell the user this entry is a duplicate of
483                          * another and ask them to delete it.
484                          */
485                         puts (_("duplicate group entry"));
486                         printf (_("delete line '%s'? "), gre->line);
487                         *errors += 1;
488
489                         /*
490                          * prompt the user to delete the entry or not
491                          */
492                         if (yes_or_no (read_only)) {
493                                 goto delete_gr;
494                         }
495                 }
496
497                 /*
498                  * Check for invalid group names.  --marekm
499                  */
500                 if (!is_valid_group_name (grp->gr_name)) {
501                         *errors += 1;
502                         printf (_("invalid group name '%s'\n"), grp->gr_name);
503                 }
504
505                 /*
506                  * Workaround for a NYS libc 5.3.12 bug on RedHat 4.2 -
507                  * groups with no members are returned as groups with one
508                  * member "", causing grpck to fail.  --marekm
509                  */
510                 if (   (NULL != grp->gr_mem[0])
511                     && (NULL == grp->gr_mem[1])
512                     && ('\0' == grp->gr_mem[0][0])) {
513                         grp->gr_mem[0] = NULL;
514                 }
515
516                 if (check_members (grp->gr_name, grp->gr_mem,
517                                    _("group %s: no user %s\n"),
518                                    _("delete member '%s'? "),
519                                    "delete member `%s' from group `%s'",
520                                    errors) == 1) {
521                         *changed = true;
522                         gre->changed = true;
523                         __gr_set_changed ();
524                 }
525
526 #ifdef  SHADOWGRP
527                 /*
528                  * Make sure this entry exists in the /etc/gshadow file.
529                  */
530
531                 if (is_shadow) {
532                         sgr = (struct sgrp *) sgr_locate (grp->gr_name);
533                         if (sgr == NULL) {
534                                 printf (_
535                                         ("no matching group file entry in %s\n"),
536                                         sgr_file);
537                                 printf (_("add group '%s' in %s ?"),
538                                         grp->gr_name, sgr_file);
539                                 *errors += 1;
540                                 if (yes_or_no (read_only)) {
541                                         struct sgrp sg;
542                                         struct group gr;
543                                         static char *empty = NULL;
544
545                                         sg.sg_name = grp->gr_name;
546                                         sg.sg_passwd = grp->gr_passwd;
547                                         sg.sg_adm = &empty;
548                                         sg.sg_mem = grp->gr_mem;
549                                         SYSLOG ((LOG_INFO,
550                                                  "add group `%s' to `%s'",
551                                                  grp->gr_name, sgr_file));
552                                         *changed = true;
553
554                                         if (sgr_update (&sg) == 0) {
555                                                 fprintf (stderr,
556                                                          _
557                                                          ("%s: can't update shadow entry for %s\n"),
558                                                          Prog, sg.sg_name);
559                                                 exit (E_CANT_UPDATE);
560                                         }
561                                         /* remove password from /etc/group */
562                                         gr = *grp;
563                                         gr.gr_passwd = SHADOW_PASSWD_STRING;    /* XXX warning: const */
564                                         if (gr_update (&gr) == 0) {
565                                                 fprintf (stderr,
566                                                          _
567                                                          ("%s: can't update entry for group %s\n"),
568                                                          Prog, gr.gr_name);
569                                                 exit (E_CANT_UPDATE);
570                                         }
571                                 }
572                         } else {
573                                 /**
574                                  * Verify that all the members defined in /etc/group are also
575                                  * present in /etc/gshadow.
576                                  */
577                                 compare_members_lists (grp->gr_name,
578                                                        grp->gr_mem, sgr->sg_mem,
579                                                        grp_file, sgr_file);
580                         }
581                 }
582 #endif
583
584         }
585 }
586
587 #ifdef SHADOWGRP
588 /*
589  * check_sgr_file - check the content of the shadowed group file (gshadow)
590  */
591 static void check_sgr_file (int *errors, bool *changed)
592 {
593         struct group *grp;
594         struct commonio_entry *sge, *tsge;
595         struct sgrp *sgr;
596
597         /*
598          * Loop through the entire shadow group file.
599          */
600         for (sge = __sgr_get_head (); NULL != sge; sge = sge->next) {
601
602                 /*
603                  * Start with the entries that are completely corrupt. They
604                  * have no (struct sgrp) entry because they couldn't be
605                  * parsed properly.
606                  */
607                 if (NULL == sge->eptr) {
608
609                         /*
610                          * Tell the user this entire line is bogus and ask
611                          * them to delete it.
612                          */
613                         puts (_("invalid shadow group file entry"));
614                         printf (_("delete line '%s'? "), sge->line);
615                         *errors += 1;
616
617                         /*
618                          * prompt the user to delete the entry or not
619                          */
620                         if (!yes_or_no (read_only)) {
621                                 continue;
622                         }
623
624                         /*
625                          * All shadow group file deletions wind up here. 
626                          * This code removes the current entry from the
627                          * linked list. When done, it skips back to the top
628                          * of the loop to try out the next list element.
629                          */
630                       delete_sg:
631                         SYSLOG ((LOG_INFO, "delete shadow line `%s'",
632                                  sge->line));
633                         *changed = true;
634
635                         __sgr_del_entry (sge);
636                         continue;
637                 }
638
639                 /*
640                  * Shadow group structure is good, start using it.
641                  */
642                 sgr = sge->eptr;
643
644                 /*
645                  * Make sure this entry has a unique name.
646                  */
647                 for (tsge = __sgr_get_head (); NULL != tsge; tsge = tsge->next) {
648
649                         const struct sgrp *ent = tsge->eptr;
650
651                         /*
652                          * Don't check this entry
653                          */
654                         if (tsge == sge) {
655                                 continue;
656                         }
657
658                         /*
659                          * Don't check invalid entries.
660                          */
661                         if (NULL == ent) {
662                                 continue;
663                         }
664
665                         if (strcmp (sgr->sg_name, ent->sg_name) != 0) {
666                                 continue;
667                         }
668
669                         /*
670                          * Tell the user this entry is a duplicate of
671                          * another and ask them to delete it.
672                          */
673                         puts (_("duplicate shadow group entry"));
674                         printf (_("delete line '%s'? "), sge->line);
675                         *errors += 1;
676
677                         /*
678                          * prompt the user to delete the entry or not
679                          */
680                         if (yes_or_no (read_only)) {
681                                 goto delete_sg;
682                         }
683                 }
684
685                 /*
686                  * Make sure this entry exists in the /etc/group file.
687                  */
688                 grp = (struct group *) gr_locate (sgr->sg_name);
689                 if (grp == NULL) {
690                         printf (_("no matching group file entry in %s\n"),
691                                 grp_file);
692                         printf (_("delete line '%s'? "), sge->line);
693                         *errors += 1;
694                         if (yes_or_no (read_only)) {
695                                 goto delete_sg;
696                         }
697                 } else {
698                         /**
699                          * Verify that the all members defined in /etc/gshadow are also
700                          * present in /etc/group.
701                          */
702                         compare_members_lists (sgr->sg_name,
703                                                sgr->sg_mem, grp->gr_mem,
704                                                sgr_file, grp_file);
705                 }
706
707                 /*
708                  * Make sure each administrator exists
709                  */
710                 if (check_members (sgr->sg_name, sgr->sg_adm,
711                                    _("shadow group %s: no administrative user %s\n"),
712                                    _("delete administrative member '%s'? "),
713                                    "delete admin `%s' from shadow group `%s'",
714                                    errors) == 1) {
715                         *changed = true;
716                         sge->changed = true;
717                         __sgr_set_changed ();
718                 }
719
720                 /*
721                  * Make sure each member exists
722                  */
723                 if (check_members (sgr->sg_name, sgr->sg_mem,
724                                    _("shadow group %s: no user %s\n"),
725                                    _("delete member '%s'? "),
726                                    "delete member `%s' from shadow group `%s'",
727                                    errors) == 1) {
728                         *changed = true;
729                         sge->changed = true;
730                         __sgr_set_changed ();
731                 }
732         }
733 }
734 #endif                          /* SHADOWGRP */
735
736 /*
737  * grpck - verify group file integrity
738  */
739 int main (int argc, char **argv)
740 {
741         int errors = 0;
742         bool changed = false;
743
744         /*
745          * Get my name so that I can use it to report errors.
746          */
747         Prog = Basename (argv[0]);
748
749         (void) setlocale (LC_ALL, "");
750         (void) bindtextdomain (PACKAGE, LOCALEDIR);
751         (void) textdomain (PACKAGE);
752
753         OPENLOG ("grpck");
754
755         /* Parse the command line arguments */
756         process_flags (argc, argv);
757
758         open_files ();
759
760         if (sort_mode) {
761                 gr_sort ();
762 #ifdef  SHADOWGRP
763                 if (is_shadow) {
764                         sgr_sort ();
765                 }
766                 changed = true;
767 #endif
768         } else {
769                 check_grp_file (&errors, &changed);
770 #ifdef  SHADOWGRP
771                 if (is_shadow) {
772                         check_sgr_file (&errors, &changed);
773                 }
774 #endif
775         }
776
777         /* Commit the change in the database if needed */
778         close_files (changed);
779
780         nscd_flush_cache ("group");
781
782         /*
783          * Tell the user what we did and exit.
784          */
785         if (0 != errors) {
786                 printf (changed ?
787                         _("%s: the files have been updated\n") :
788                         _("%s: no changes\n"), Prog);
789         }
790
791         exit ((0 != errors) ? E_BAD_ENTRY : E_OKAY);
792 }
793