2 * Copyright (c) 1991 - 1994, Julianne Frances Haugh
3 * Copyright (c) 1996 - 2000, Marek Michałkiewicz
4 * Copyright (c) 2000 - 2006, Tomasz Kłoczko
5 * Copyright (c) 2007 - 2011, Nicolas François
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. The name of the copyright holders or contributors may not be used to
17 * endorse or promote products derived from this software without
18 * specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 #include <sys/types.h>
43 #ifdef ACCT_TOOLS_SETUID
48 #endif /* ACCT_TOOLS_SETUID */
54 #include "prototypes.h"
62 #define E_SUCCESS 0 /* success */
63 #define E_USAGE 2 /* invalid command syntax */
64 #define E_BAD_ARG 3 /* invalid argument to option */
65 #define E_GID_IN_USE 4 /* gid already in use (and no -o) */
66 #define E_NOTFOUND 6 /* specified group doesn't exist */
67 #define E_NAME_IN_USE 9 /* group name already in use */
68 #define E_GRP_UPDATE 10 /* can't update group file */
75 static bool is_shadow_grp;
76 #endif /* SHADOWGRP */
77 static char *group_name;
78 static char *group_newname;
79 static char *group_passwd;
80 static gid_t group_id;
81 static gid_t group_newid;
83 static struct cleanup_info_mod info_passwd;
84 static struct cleanup_info_mod info_group;
86 static struct cleanup_info_mod info_gshadow;
90 oflg = false, /* permit non-unique group ID to be specified with -g */
91 gflg = false, /* new ID value for the group */
92 nflg = false, /* a new name has been specified for the group */
93 pflg = false; /* new encrypted password */
95 /* local function prototypes */
96 static void usage (int status);
97 static void new_grent (struct group *);
100 static void new_sgent (struct sgrp *);
102 static void grp_update (void);
103 static void check_new_gid (void);
104 static void check_new_name (void);
105 static void process_flags (int, char **);
106 static void lock_files (void);
107 static void prepare_failure_reports (void);
108 static void open_files (void);
109 static void close_files (void);
110 static void update_primary_groups (gid_t ogid, gid_t ngid);
113 * usage - display usage message and exit
116 static void usage (int status)
118 FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
119 (void) fprintf (usageout,
120 _("Usage: %s [options] GROUP\n"
124 (void) fputs (_(" -g, --gid GID change the group ID to GID\n"), usageout);
125 (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
126 (void) fputs (_(" -n, --new-name NEW_GROUP change the name to NEW_GROUP\n"), usageout);
127 (void) fputs (_(" -o, --non-unique allow to use a duplicate (non-unique) GID\n"), usageout);
128 (void) fputs (_(" -p, --password PASSWORD change the password to this (encrypted)\n"
129 " PASSWORD\n"), usageout);
130 (void) fputs ("\n", usageout);
135 * new_grent - updates the values in a group file entry
137 * new_grent() takes all of the values that have been entered and fills
138 * in a (struct group) with them.
140 static void new_grent (struct group *grent)
143 grent->gr_name = xstrdup (group_newname);
147 grent->gr_gid = group_newid;
152 && ( (!is_shadow_grp)
153 || (strcmp (grent->gr_passwd, SHADOW_PASSWD_STRING) != 0))
156 /* Update the password in group if there is no gshadow
157 * file or if the password is currently in group
158 * (gr_passwd != "x"). We do not force the usage of
159 * shadow passwords if it was not the case before.
161 grent->gr_passwd = group_passwd;
167 * new_sgent - updates the values in a shadow group file entry
169 * new_sgent() takes all of the values that have been entered and fills
170 * in a (struct sgrp) with them.
172 static void new_sgent (struct sgrp *sgent)
175 sgent->sg_name = xstrdup (group_newname);
178 /* Always update the shadowed password if there is a shadow entry
179 * (even if shadowed passwords might not be enabled for this group
180 * (gr_passwd != "x")).
181 * It seems better to update the password in both places in case a
182 * shadow and a non shadow entry exist.
183 * This might occur only if there were already both entries.
186 sgent->sg_passwd = group_passwd;
189 #endif /* SHADOWGRP */
192 * grp_update - update group file entries
194 * grp_update() updates the new records in the memory databases.
196 static void grp_update (void)
199 const struct group *ogrp;
203 const struct sgrp *osgrp = NULL;
204 #endif /* SHADOWGRP */
207 * Get the current settings for this group.
209 ogrp = gr_locate (group_name);
212 _("%s: group '%s' does not exist in %s\n"),
213 Prog, group_name, gr_dbname ());
221 osgrp = sgr_locate (group_name);
226 && (strcmp (grp.gr_passwd, SHADOW_PASSWD_STRING) == 0)) {
227 static char *empty = NULL;
228 /* If there is a gshadow file with no entries for
229 * the group, but the group file indicates a
230 * shadowed password, we force the creation of a
231 * gshadow entry when a new password is requested.
233 memset (&sgrp, 0, sizeof sgrp);
234 sgrp.sg_name = xstrdup (grp.gr_name);
235 sgrp.sg_passwd = xstrdup (grp.gr_passwd);
236 sgrp.sg_adm = ∅
237 sgrp.sg_mem = dup_list (grp.gr_mem);
239 osgrp = &sgrp; /* entry needs to be committed */
242 #endif /* SHADOWGRP */
245 update_primary_groups (ogrp->gr_gid, group_newid);
249 * Write out the new group file entry.
251 if (gr_update (&grp) == 0) {
253 _("%s: failed to prepare the new %s entry '%s'\n"),
254 Prog, gr_dbname (), grp.gr_name);
257 if (nflg && (gr_remove (group_name) == 0)) {
259 _("%s: cannot remove entry '%s' from %s\n"),
260 Prog, grp.gr_name, gr_dbname ());
266 * Make sure there was a shadow entry to begin with.
270 * Write out the new shadow group entries as well.
272 if (sgr_update (&sgrp) == 0) {
274 _("%s: failed to prepare the new %s entry '%s'\n"),
275 Prog, sgr_dbname (), sgrp.sg_name);
278 if (nflg && (sgr_remove (group_name) == 0)) {
280 _("%s: cannot remove entry '%s' from %s\n"),
281 Prog, group_name, sgr_dbname ());
285 #endif /* SHADOWGRP */
289 * check_new_gid - check the new GID value for uniqueness
291 * check_new_gid() insures that the new GID value is unique.
293 static void check_new_gid (void)
296 * First, the easy stuff. If the ID can be duplicated, or if the ID
297 * didn't really change, just return. If the ID didn't change, turn
298 * off those flags. No sense doing needless work.
300 if (group_id == group_newid) {
306 (getgrgid (group_newid) == NULL) /* local, no need for xgetgrgid */
312 * Tell the user what they did wrong.
315 _("%s: GID '%lu' already exists\n"),
316 Prog, (unsigned long int) group_newid);
321 * check_new_name - check the new name for uniqueness
323 * check_new_name() insures that the new name does not exist already.
324 * You can't have the same name twice, period.
326 static void check_new_name (void)
329 * Make sure they are actually changing the name.
331 if (strcmp (group_name, group_newname) == 0) {
336 if (is_valid_group_name (group_newname)) {
339 * If the entry is found, too bad.
341 /* local, no need for xgetgrnam */
342 if (getgrnam (group_newname) != NULL) {
344 _("%s: group '%s' already exists\n"),
345 Prog, group_newname);
346 exit (E_NAME_IN_USE);
352 * All invalid group names land here.
356 _("%s: invalid group name '%s'\n"),
357 Prog, group_newname);
362 * process_flags - perform command line argument setting
364 * process_flags() interprets the command line arguments and sets the
365 * values that the user will be created with accordingly. The values
366 * are checked for sanity.
368 static void process_flags (int argc, char **argv)
370 int option_index = 0;
372 static struct option long_options[] = {
373 {"gid", required_argument, NULL, 'g'},
374 {"help", no_argument, NULL, 'h'},
375 {"new-name", required_argument, NULL, 'n'},
376 {"non-unique", no_argument, NULL, 'o'},
377 {"password", required_argument, NULL, 'p'},
378 {NULL, 0, NULL, '\0'}
381 getopt_long (argc, argv, "g:hn:op:",
382 long_options, &option_index)) != -1) {
386 if ( (get_gid (optarg, &group_newid) == 0)
387 || (group_newid == (gid_t)-1)) {
389 _("%s: invalid group ID '%s'\n"),
399 group_newname = optarg;
405 group_passwd = optarg;
417 if (optind != (argc - 1)) {
421 group_name = argv[argc - 1];
425 * close_files - close all of the files that were opened
427 * close_files() closes all of the files that were opened for this new
428 * group. This causes any modified entries to be written out.
430 static void close_files (void)
432 if (gr_close () == 0) {
434 _("%s: failure while writing changes to %s\n"),
439 audit_logger (AUDIT_USER_ACCT, Prog,
440 info_group.audit_msg,
441 group_name, AUDIT_NO_ID,
442 SHADOW_AUDIT_SUCCESS);
445 "group changed in %s (%s)",
446 gr_dbname (), info_group.action));
447 del_cleanup (cleanup_report_mod_group);
449 cleanup_unlock_group (NULL);
450 del_cleanup (cleanup_unlock_group);
455 if (sgr_close () == 0) {
457 _("%s: failure while writing changes to %s\n"),
458 Prog, sgr_dbname ());
462 audit_logger (AUDIT_USER_ACCT, Prog,
463 info_gshadow.audit_msg,
464 group_name, AUDIT_NO_ID,
465 SHADOW_AUDIT_SUCCESS);
468 "group changed in %s (%s)",
469 sgr_dbname (), info_gshadow.action));
470 del_cleanup (cleanup_report_mod_gshadow);
472 cleanup_unlock_gshadow (NULL);
473 del_cleanup (cleanup_unlock_gshadow);
475 #endif /* SHADOWGRP */
478 if (pw_close () == 0) {
480 _("%s: failure while writing changes to %s\n"),
485 audit_logger (AUDIT_USER_ACCT, Prog,
486 info_passwd.audit_msg,
487 group_name, AUDIT_NO_ID,
488 SHADOW_AUDIT_SUCCESS);
491 "group changed in %s (%s)",
492 pw_dbname (), info_passwd.action));
493 del_cleanup (cleanup_report_mod_passwd);
495 cleanup_unlock_passwd (NULL);
496 del_cleanup (cleanup_unlock_passwd);
500 audit_logger (AUDIT_USER_ACCT, Prog,
502 group_name, AUDIT_NO_ID,
503 SHADOW_AUDIT_SUCCESS);
508 * prepare_failure_reports - Prepare the cleanup_info structure for logging
509 * of success and failure to syslog or audit.
511 static void prepare_failure_reports (void)
513 info_group.name = group_name;
515 info_gshadow.name = group_name;
517 info_passwd.name = group_name;
519 info_group.audit_msg = xmalloc (512);
521 info_gshadow.audit_msg = xmalloc (512);
523 info_passwd.audit_msg = xmalloc (512);
525 (void) snprintf (info_group.audit_msg, 511,
526 "changing %s; ", gr_dbname ());
528 (void) snprintf (info_gshadow.audit_msg, 511,
529 "changing %s; ", sgr_dbname ());
531 (void) snprintf (info_passwd.audit_msg, 511,
532 "changing %s; ", pw_dbname ());
534 info_group.action = info_group.audit_msg
535 + strlen (info_group.audit_msg);
537 info_gshadow.action = info_gshadow.audit_msg
538 + strlen (info_gshadow.audit_msg);
540 info_passwd.action = info_passwd.audit_msg
541 + strlen (info_passwd.audit_msg);
543 (void) snprintf (info_group.action,
544 511 - strlen (info_group.audit_msg),
546 group_name, (unsigned long int) group_id);
548 (void) snprintf (info_gshadow.action,
549 511 - strlen (info_group.audit_msg),
550 "group %s", group_name);
552 (void) snprintf (info_passwd.action,
553 511 - strlen (info_group.audit_msg),
555 group_name, (unsigned long int) group_id);
558 strncat (info_group.action, ", new name: ",
559 511 - strlen (info_group.audit_msg));
560 strncat (info_group.action, group_newname,
561 511 - strlen (info_group.audit_msg));
564 strncat (info_gshadow.action, ", new name: ",
565 511 - strlen (info_gshadow.audit_msg));
566 strncat (info_gshadow.action, group_newname,
567 511 - strlen (info_gshadow.audit_msg));
570 strncat (info_passwd.action, ", new name: ",
571 511 - strlen (info_passwd.audit_msg));
572 strncat (info_passwd.action, group_newname,
573 511 - strlen (info_passwd.audit_msg));
576 strncat (info_group.action, ", new password",
577 511 - strlen (info_group.audit_msg));
580 strncat (info_gshadow.action, ", new password",
581 511 - strlen (info_gshadow.audit_msg));
585 strncat (info_group.action, ", new gid: ",
586 511 - strlen (info_group.audit_msg));
587 (void) snprintf (info_group.action+strlen (info_group.action),
588 511 - strlen (info_group.audit_msg),
589 "%lu", (unsigned long int) group_newid);
591 strncat (info_passwd.action, ", new gid: ",
592 511 - strlen (info_passwd.audit_msg));
593 (void) snprintf (info_passwd.action+strlen (info_passwd.action),
594 511 - strlen (info_passwd.audit_msg),
595 "%lu", (unsigned long int) group_newid);
597 info_group.audit_msg[511] = '\0';
599 info_gshadow.audit_msg[511] = '\0';
601 info_passwd.audit_msg[511] = '\0';
603 // FIXME: add a system cleanup
604 add_cleanup (cleanup_report_mod_group, &info_group);
608 add_cleanup (cleanup_report_mod_gshadow, &info_gshadow);
612 add_cleanup (cleanup_report_mod_passwd, &info_passwd);
618 * lock_files - lock the accounts databases
620 * lock_files() locks the group, gshadow, and passwd databases.
622 static void lock_files (void)
624 if (gr_lock () == 0) {
626 _("%s: cannot lock %s; try again later.\n"),
630 add_cleanup (cleanup_unlock_group, NULL);
635 if (sgr_lock () == 0) {
637 _("%s: cannot lock %s; try again later.\n"),
638 Prog, sgr_dbname ());
641 add_cleanup (cleanup_unlock_gshadow, NULL);
646 if (pw_lock () == 0) {
648 _("%s: cannot lock %s; try again later.\n"),
652 add_cleanup (cleanup_unlock_passwd, NULL);
658 * open_files - open the accounts databases
660 * open_files() opens the group, gshadow, and passwd databases.
662 static void open_files (void)
664 if (gr_open (O_RDWR) == 0) {
665 fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ());
666 SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
673 if (sgr_open (O_RDWR) == 0) {
675 _("%s: cannot open %s\n"),
676 Prog, sgr_dbname ());
677 SYSLOG ((LOG_WARN, "cannot open %s", sgr_dbname ()));
681 #endif /* SHADOWGRP */
684 if (pw_open (O_RDWR) == 0) {
686 _("%s: cannot open %s\n"),
688 SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
694 void update_primary_groups (gid_t ogid, gid_t ngid)
699 while ((pwd = getpwent ()) != NULL) {
700 if (pwd->pw_gid == ogid) {
701 const struct passwd *lpwd;
703 lpwd = pw_locate (pwd->pw_name);
706 _("%s: user '%s' does not exist in %s\n"),
707 Prog, pwd->pw_name, pw_dbname ());
712 if (pw_update (&npwd) == 0) {
714 _("%s: failed to prepare the new %s entry '%s'\n"),
715 Prog, pw_dbname (), npwd.pw_name);
725 * main - groupmod command
728 int main (int argc, char **argv)
730 #ifdef ACCT_TOOLS_SETUID
732 pam_handle_t *pamh = NULL;
735 #endif /* ACCT_TOOLS_SETUID */
742 * Get my name so that I can use it to report errors.
744 Prog = Basename (argv[0]);
746 (void) setlocale (LC_ALL, "");
747 (void) bindtextdomain (PACKAGE, LOCALEDIR);
748 (void) textdomain (PACKAGE);
750 if (atexit (do_cleanups) != 0) {
752 _("%s: Cannot setup cleanup service.\n"),
757 process_flags (argc, argv);
759 OPENLOG ("groupmod");
761 #ifdef ACCT_TOOLS_SETUID
764 struct passwd *pampw;
765 pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
768 _("%s: Cannot determine your user name.\n"),
773 retval = pam_start ("groupmod", pampw->pw_name, &conv, &pamh);
776 if (PAM_SUCCESS == retval) {
777 retval = pam_authenticate (pamh, 0);
780 if (PAM_SUCCESS == retval) {
781 retval = pam_acct_mgmt (pamh, 0);
785 (void) pam_end (pamh, retval);
787 if (PAM_SUCCESS != retval) {
788 fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
792 #endif /* ACCT_TOOLS_SETUID */
795 is_shadow_grp = sgr_file_present ();
800 * Start with a quick check to see if the group exists.
802 grp = getgrnam (group_name); /* local, no need for xgetgrnam */
805 _("%s: group '%s' does not exist\n"),
809 group_id = grp->gr_gid;
815 * Now make sure it isn't an NIS group.
822 _("%s: group %s is a NIS group\n"),
825 if (!yp_get_default_domain (&nis_domain) &&
826 !yp_master (nis_domain, "group.byname", &nis_master)) {
828 _("%s: %s is the NIS master\n"),
846 * Now if the group is not changed, it's our fault.
847 * Make sure failures will be reported.
849 prepare_failure_reports ();
852 * Do the hard stuff - open the files, create the group entries,
853 * then close and update the files.
861 nscd_flush_cache ("group");