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 - 2011, Nicolas François
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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.
49 #include "prototypes.h"
65 #define E_CANT_UPDATE 5
72 static const char *grp_file = GROUP_FILE;
73 static bool use_system_grp_file = true;
76 static const char *sgr_file = SGROUP_FILE;
77 static bool use_system_sgr_file = true;
78 static bool is_shadow = false;
79 static bool sgr_locked = false;
81 static bool gr_locked = false;
83 static bool read_only = false;
84 static bool sort_mode = false;
86 /* local function prototypes */
87 static void fail_exit (int status);
88 static /*@noreturn@*/void usage (int status);
89 static void delete_member (char **, const char *);
90 static void process_flags (int argc, char **argv);
91 static void open_files (void);
92 static void close_files (bool changed);
93 static int check_members (const char *groupname,
96 const char *fmt_prompt,
97 const char *fmt_syslog,
99 static void check_grp_file (int *errors, bool *changed);
101 static void compare_members_lists (const char *groupname,
103 char **other_members,
105 const char *other_file);
106 static void check_sgr_file (int *errors, bool *changed);
110 * fail_exit - exit with an error code after unlocking files
112 static void fail_exit (int status)
115 if (gr_unlock () == 0) {
116 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
117 SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
124 if (sgr_unlock () == 0) {
125 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
126 SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
138 * usage - print syntax message and exit
140 static /*@noreturn@*/void usage (int status)
142 FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
144 (void) fprintf (usageout,
145 _("Usage: %s [options] [group [gshadow]]\n"
149 #else /* !SHADOWGRP */
150 (void) fprintf (usageout,
151 _("Usage: %s [options] [group]\n"
155 #endif /* !SHADOWGRP */
156 (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
157 (void) fputs (_(" -r, --read-only display errors and warnings\n"
158 " but do not change files\n"), usageout);
159 (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
160 (void) fputs (_(" -s, --sort sort entries by UID\n"), usageout);
161 (void) fputs ("\n", usageout);
166 * delete_member - delete an entry in a list of members
168 * It only deletes the first entry with the given name.
169 * The member is defined by its address, no string comparison are
172 static void delete_member (char **list, const char *member)
176 for (i = 0; NULL != list[i]; i++) {
177 if (list[i] == member) {
182 for (; NULL != list[i]; i++) {
183 list[i] = list[i + 1];
188 * process_flags - parse the command line options
190 * It will not return if an error is encountered.
192 static void process_flags (int argc, char **argv)
195 static struct option long_options[] = {
196 {"help", no_argument, NULL, 'h'},
197 {"quiet", no_argument, NULL, 'q'},
198 {"read-only", no_argument, NULL, 'r'},
199 {"root", required_argument, NULL, 'R'},
200 {"sort", no_argument, NULL, 's'},
201 {NULL, 0, NULL, '\0'}
205 * Parse the command line arguments
207 while ((c = getopt_long (argc, argv, "hqrR:s",
208 long_options, NULL)) != -1) {
212 /*@notreached@*/break;
214 /* quiet - ignored for now */
219 case 'R': /* no-op, handled in process_root_flag () */
229 if (sort_mode && read_only) {
230 fprintf (stderr, _("%s: -s and -r are incompatible\n"), Prog);
235 * Make certain we have the right number of arguments
238 if (argc > (optind + 2))
240 if (argc > (optind + 1))
247 * If there are two left over filenames, use those as the group and
248 * group password filenames.
250 if (optind != argc) {
251 grp_file = argv[optind];
252 gr_setdbname (grp_file);
253 use_system_grp_file = false;
256 if ((optind + 2) == argc) {
257 sgr_file = argv[optind + 1];
258 sgr_setdbname (sgr_file);
260 use_system_sgr_file = false;
261 } else if (optind == argc) {
262 is_shadow = sgr_file_present ();
268 * open_files - open the shadow database
270 * In read-only mode, the databases are not locked and are opened
273 static void open_files (void)
276 * Lock the files if we aren't in "read-only" mode
279 if (gr_lock () == 0) {
281 _("%s: cannot lock %s; try again later.\n"),
283 fail_exit (E_CANT_LOCK);
288 if (sgr_lock () == 0) {
290 _("%s: cannot lock %s; try again later.\n"),
292 fail_exit (E_CANT_LOCK);
300 * Open the files. Use O_RDONLY if we are in read_only mode,
303 if (gr_open (read_only ? O_RDONLY : O_CREAT | O_RDWR) == 0) {
304 fprintf (stderr, _("%s: cannot open %s\n"), Prog,
306 if (use_system_grp_file) {
307 SYSLOG ((LOG_WARN, "cannot open %s", grp_file));
309 fail_exit (E_CANT_OPEN);
312 if (is_shadow && (sgr_open (read_only ? O_RDONLY : O_CREAT | O_RDWR) == 0)) {
313 fprintf (stderr, _("%s: cannot open %s\n"), Prog,
315 if (use_system_sgr_file) {
316 SYSLOG ((LOG_WARN, "cannot open %s", sgr_file));
318 fail_exit (E_CANT_OPEN);
324 * close_files - close and unlock the group/gshadow databases
326 * If changed is not set, the databases are not closed, and no
327 * changes are committed in the databases. The databases are
330 static void close_files (bool changed)
333 * All done. If there were no change we can just abandon any
334 * changes to the files.
337 if (gr_close () == 0) {
338 fprintf (stderr, _("%s: failure while writing changes to %s\n"),
340 fail_exit (E_CANT_UPDATE);
343 if (is_shadow && (sgr_close () == 0)) {
344 fprintf (stderr, _("%s: failure while writing changes to %s\n"),
346 fail_exit (E_CANT_UPDATE);
352 * Don't be anti-social - unlock the files when you're done.
356 if (sgr_unlock () == 0) {
357 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
358 SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
365 if (gr_unlock () == 0) {
366 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
367 SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
375 * check_members - check that every members of a group exist
377 * If an error is detected, *errors is incremented.
379 * The user will be prompted for the removal of the non-existent
382 * If any changes are performed, the return value will be 1,
383 * otherwise check_members() returns 0.
385 * fmt_info, fmt_prompt, and fmt_syslog are used for logging.
386 * * fmt_info must contain two string flags (%s): for the group's
387 * name and the missing member.
388 * * fmt_prompt must contain one string flags (%s): the missing
390 * * fmt_syslog must contain two string flags (%s): for the
391 * group's name and the missing member.
393 static int check_members (const char *groupname,
395 const char *fmt_info,
396 const char *fmt_prompt,
397 const char *fmt_syslog,
401 int members_changed = 0;
404 * Make sure each member exists
406 for (i = 0; NULL != members[i]; i++) {
407 /* local, no need for xgetpwnam */
408 if (getpwnam (members[i]) != NULL) {
412 * Can't find this user. Remove them
416 printf (fmt_info, groupname, members[i]);
417 printf (fmt_prompt, members[i]);
419 if (!yes_or_no (read_only)) {
423 SYSLOG ((LOG_INFO, fmt_syslog, members[i], groupname));
425 delete_member (members, members[i]);
427 /* Rewind in case of removal */
431 return members_changed;
436 * compare_members_lists - make sure the list of members is contained in
439 * compare_members_lists() checks that all the members of members are
440 * also in other_members.
441 * file and other_file are used for logging.
443 * TODO: No changes are performed on the lists.
445 static void compare_members_lists (const char *groupname,
447 char **other_members,
449 const char *other_file)
451 char **pmem, **other_pmem;
453 for (pmem = members; NULL != *pmem; pmem++) {
454 for (other_pmem = other_members; NULL != *other_pmem; other_pmem++) {
455 if (strcmp (*pmem, *other_pmem) == 0) {
459 if (*other_pmem == NULL) {
461 ("'%s' is a member of the '%s' group in %s but not in %s\n",
462 *pmem, groupname, file, other_file);
466 #endif /* SHADOWGRP */
469 * check_grp_file - check the content of the group file
471 static void check_grp_file (int *errors, bool *changed)
473 struct commonio_entry *gre, *tgre;
480 * Loop through the entire group file.
482 for (gre = __gr_get_head (); NULL != gre; gre = gre->next) {
484 * Skip all NIS entries.
487 if ((gre->line[0] == '+') || (gre->line[0] == '-')) {
492 * Start with the entries that are completely corrupt. They
493 * have no (struct group) entry because they couldn't be
496 if (NULL == gre->eptr) {
499 * Tell the user this entire line is bogus and ask
502 (void) puts (_("invalid group file entry"));
503 printf (_("delete line '%s'? "), gre->line);
507 * prompt the user to delete the entry or not
509 if (!yes_or_no (read_only)) {
514 * All group file deletions wind up here. This code
515 * removes the current entry from the linked list.
516 * When done, it skips back to the top of the loop
517 * to try out the next list element.
520 SYSLOG ((LOG_INFO, "delete group line '%s'",
524 __gr_del_entry (gre);
529 * Group structure is good, start using it.
534 * Make sure this entry has a unique name.
536 for (tgre = __gr_get_head (); NULL != tgre; tgre = tgre->next) {
538 const struct group *ent = tgre->eptr;
541 * Don't check this entry
548 * Don't check invalid entries.
554 if (strcmp (grp->gr_name, ent->gr_name) != 0) {
559 * Tell the user this entry is a duplicate of
560 * another and ask them to delete it.
562 (void) puts (_("duplicate group entry"));
563 printf (_("delete line '%s'? "), gre->line);
567 * prompt the user to delete the entry or not
569 if (yes_or_no (read_only)) {
575 * Check for invalid group names. --marekm
577 if (!is_valid_group_name (grp->gr_name)) {
579 printf (_("invalid group name '%s'\n"), grp->gr_name);
583 * Check for invalid group ID.
585 if (grp->gr_gid == (gid_t)-1) {
586 printf (_("invalid group ID '%lu'\n"), (long unsigned int)grp->gr_gid);
591 * Workaround for a NYS libc 5.3.12 bug on RedHat 4.2 -
592 * groups with no members are returned as groups with one
593 * member "", causing grpck to fail. --marekm
595 if ( (NULL != grp->gr_mem[0])
596 && (NULL == grp->gr_mem[1])
597 && ('\0' == grp->gr_mem[0][0])) {
598 grp->gr_mem[0] = NULL;
601 if (check_members (grp->gr_name, grp->gr_mem,
602 _("group %s: no user %s\n"),
603 _("delete member '%s'? "),
604 "delete member '%s' from group '%s'",
613 * Make sure this entry exists in the /etc/gshadow file.
617 sgr = (struct sgrp *) sgr_locate (grp->gr_name);
619 printf (_("no matching group file entry in %s\n"),
621 printf (_("add group '%s' in %s? "),
622 grp->gr_name, sgr_file);
624 if (yes_or_no (read_only)) {
627 static char *empty = NULL;
629 sg.sg_name = grp->gr_name;
630 sg.sg_passwd = grp->gr_passwd;
632 sg.sg_mem = grp->gr_mem;
634 "add group '%s' to '%s'",
635 grp->gr_name, sgr_file));
638 if (sgr_update (&sg) == 0) {
640 _("%s: failed to prepare the new %s entry '%s'\n"),
641 Prog, sgr_dbname (), sg.sg_name);
642 fail_exit (E_CANT_UPDATE);
644 /* remove password from /etc/group */
646 gr.gr_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
647 if (gr_update (&gr) == 0) {
649 _("%s: failed to prepare the new %s entry '%s'\n"),
650 Prog, gr_dbname (), gr.gr_name);
651 fail_exit (E_CANT_UPDATE);
656 * Verify that all the members defined in /etc/group are also
657 * present in /etc/gshadow.
659 compare_members_lists (grp->gr_name,
660 grp->gr_mem, sgr->sg_mem,
663 /* The group entry has a gshadow counterpart.
664 * Make sure no passwords are in group.
666 if (strcmp (grp->gr_passwd, SHADOW_PASSWD_STRING) != 0) {
667 printf (_("group %s has an entry in %s, but its password field in %s is not set to 'x'\n"),
668 grp->gr_name, sgr_file, grp_file);
680 * check_sgr_file - check the content of the shadowed group file (gshadow)
682 static void check_sgr_file (int *errors, bool *changed)
685 struct commonio_entry *sge, *tsge;
689 * Loop through the entire shadow group file.
691 for (sge = __sgr_get_head (); NULL != sge; sge = sge->next) {
694 * Start with the entries that are completely corrupt. They
695 * have no (struct sgrp) entry because they couldn't be
698 if (NULL == sge->eptr) {
701 * Tell the user this entire line is bogus and ask
704 (void) puts (_("invalid shadow group file entry"));
705 printf (_("delete line '%s'? "), sge->line);
709 * prompt the user to delete the entry or not
711 if (!yes_or_no (read_only)) {
716 * All shadow group file deletions wind up here.
717 * This code removes the current entry from the
718 * linked list. When done, it skips back to the top
719 * of the loop to try out the next list element.
722 SYSLOG ((LOG_INFO, "delete shadow line '%s'",
726 __sgr_del_entry (sge);
731 * Shadow group structure is good, start using it.
736 * Make sure this entry has a unique name.
738 for (tsge = __sgr_get_head (); NULL != tsge; tsge = tsge->next) {
740 const struct sgrp *ent = tsge->eptr;
743 * Don't check this entry
750 * Don't check invalid entries.
756 if (strcmp (sgr->sg_name, ent->sg_name) != 0) {
761 * Tell the user this entry is a duplicate of
762 * another and ask them to delete it.
764 (void) puts (_("duplicate shadow group entry"));
765 printf (_("delete line '%s'? "), sge->line);
769 * prompt the user to delete the entry or not
771 if (yes_or_no (read_only)) {
777 * Make sure this entry exists in the /etc/group file.
779 grp = (struct group *) gr_locate (sgr->sg_name);
781 printf (_("no matching group file entry in %s\n"),
783 printf (_("delete line '%s'? "), sge->line);
785 if (yes_or_no (read_only)) {
790 * Verify that the all members defined in /etc/gshadow are also
791 * present in /etc/group.
793 compare_members_lists (sgr->sg_name,
794 sgr->sg_mem, grp->gr_mem,
799 * Make sure each administrator exists
801 if (check_members (sgr->sg_name, sgr->sg_adm,
802 _("shadow group %s: no administrative user %s\n"),
803 _("delete administrative member '%s'? "),
804 "delete admin '%s' from shadow group '%s'",
808 __sgr_set_changed ();
812 * Make sure each member exists
814 if (check_members (sgr->sg_name, sgr->sg_mem,
815 _("shadow group %s: no user %s\n"),
816 _("delete member '%s'? "),
817 "delete member '%s' from shadow group '%s'",
821 __sgr_set_changed ();
825 #endif /* SHADOWGRP */
828 * grpck - verify group file integrity
830 int main (int argc, char **argv)
833 bool changed = false;
836 * Get my name so that I can use it to report errors.
838 Prog = Basename (argv[0]);
840 (void) setlocale (LC_ALL, "");
841 (void) bindtextdomain (PACKAGE, LOCALEDIR);
842 (void) textdomain (PACKAGE);
844 process_root_flag ("-R", argc, argv);
848 /* Parse the command line arguments */
849 process_flags (argc, argv);
862 check_grp_file (&errors, &changed);
865 check_sgr_file (&errors, &changed);
870 /* Commit the change in the database if needed */
871 close_files (changed);
874 nscd_flush_cache ("group");
875 sssd_flush_cache (SSSD_DB_GROUP);
879 * Tell the user what we did and exit.
883 printf (_("%s: the files have been updated\n"), Prog);
885 printf (_("%s: no changes\n"), Prog);
889 return ((0 != errors) ? E_BAD_ENTRY : E_OKAY);