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 struct cleanup_info_mod info_passwd;
84 struct cleanup_info_mod info_group;
86 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 snprintf (info_group.audit_msg, 511,
526 "changing %s; ", gr_dbname ());
528 snprintf (info_gshadow.audit_msg, 511,
529 "changing %s; ", sgr_dbname ());
531 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 snprintf (info_group.action, 511 - strlen (info_group.audit_msg),
544 "group %s/%lu", group_name, (unsigned long int) group_id);
546 snprintf (info_gshadow.action, 511 - strlen (info_group.audit_msg),
547 "group %s", group_name);
549 snprintf (info_passwd.action, 511 - strlen (info_group.audit_msg),
550 "group %s/%lu", group_name, (unsigned long int) group_id);
553 strncat (info_group.action, ", new name: ",
554 511 - strlen (info_group.audit_msg));
555 strncat (info_group.action, group_newname,
556 511 - strlen (info_group.audit_msg));
559 strncat (info_gshadow.action, ", new name: ",
560 511 - strlen (info_gshadow.audit_msg));
561 strncat (info_gshadow.action, group_newname,
562 511 - strlen (info_gshadow.audit_msg));
565 strncat (info_passwd.action, ", new name: ",
566 511 - strlen (info_passwd.audit_msg));
567 strncat (info_passwd.action, group_newname,
568 511 - strlen (info_passwd.audit_msg));
571 strncat (info_group.action, ", new password",
572 511 - strlen (info_group.audit_msg));
575 strncat (info_gshadow.action, ", new password",
576 511 - strlen (info_gshadow.audit_msg));
580 strncat (info_group.action, ", new gid: ",
581 511 - strlen (info_group.audit_msg));
582 snprintf (info_group.action+strlen (info_group.action),
583 511 - strlen (info_group.audit_msg),
584 "%lu", (unsigned long int) group_newid);
586 strncat (info_passwd.action, ", new gid: ",
587 511 - strlen (info_passwd.audit_msg));
588 snprintf (info_passwd.action+strlen (info_passwd.action),
589 511 - strlen (info_passwd.audit_msg),
590 "%lu", (unsigned long int) group_newid);
592 info_group.audit_msg[511] = '\0';
594 info_gshadow.audit_msg[511] = '\0';
596 info_passwd.audit_msg[511] = '\0';
598 // FIXME: add a system cleanup
599 add_cleanup (cleanup_report_mod_group, &info_group);
603 add_cleanup (cleanup_report_mod_gshadow, &info_gshadow);
607 add_cleanup (cleanup_report_mod_passwd, &info_passwd);
613 * lock_files - lock the accounts databases
615 * lock_files() locks the group, gshadow, and passwd databases.
617 static void lock_files (void)
619 if (gr_lock () == 0) {
621 _("%s: cannot lock %s; try again later.\n"),
625 add_cleanup (cleanup_unlock_group, NULL);
630 if (sgr_lock () == 0) {
632 _("%s: cannot lock %s; try again later.\n"),
633 Prog, sgr_dbname ());
636 add_cleanup (cleanup_unlock_gshadow, NULL);
641 if (pw_lock () == 0) {
643 _("%s: cannot lock %s; try again later.\n"),
647 add_cleanup (cleanup_unlock_passwd, NULL);
653 * open_files - open the accounts databases
655 * open_files() opens the group, gshadow, and passwd databases.
657 static void open_files (void)
659 if (gr_open (O_RDWR) == 0) {
660 fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ());
661 SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
668 if (sgr_open (O_RDWR) == 0) {
670 _("%s: cannot open %s\n"),
671 Prog, sgr_dbname ());
672 SYSLOG ((LOG_WARN, "cannot open %s", sgr_dbname ()));
676 #endif /* SHADOWGRP */
679 if (pw_open (O_RDWR) == 0) {
681 _("%s: cannot open %s\n"),
683 SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
689 void update_primary_groups (gid_t ogid, gid_t ngid)
694 while ((pwd = getpwent ()) != NULL) {
695 if (pwd->pw_gid == ogid) {
696 const struct passwd *lpwd;
698 lpwd = pw_locate (pwd->pw_name);
701 _("%s: user '%s' does not exist in %s\n"),
702 Prog, pwd->pw_name, pw_dbname ());
707 if (pw_update (&npwd) == 0) {
709 _("%s: failed to prepare the new %s entry '%s'\n"),
710 Prog, pw_dbname (), npwd.pw_name);
720 * main - groupmod command
723 int main (int argc, char **argv)
725 #ifdef ACCT_TOOLS_SETUID
727 pam_handle_t *pamh = NULL;
730 #endif /* ACCT_TOOLS_SETUID */
735 atexit (do_cleanups);
738 * Get my name so that I can use it to report errors.
740 Prog = Basename (argv[0]);
742 (void) setlocale (LC_ALL, "");
743 (void) bindtextdomain (PACKAGE, LOCALEDIR);
744 (void) textdomain (PACKAGE);
746 process_flags (argc, argv);
748 OPENLOG ("groupmod");
750 #ifdef ACCT_TOOLS_SETUID
753 struct passwd *pampw;
754 pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
757 _("%s: Cannot determine your user name.\n"),
762 retval = pam_start ("groupmod", pampw->pw_name, &conv, &pamh);
765 if (PAM_SUCCESS == retval) {
766 retval = pam_authenticate (pamh, 0);
769 if (PAM_SUCCESS == retval) {
770 retval = pam_acct_mgmt (pamh, 0);
774 (void) pam_end (pamh, retval);
776 if (PAM_SUCCESS != retval) {
777 fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
781 #endif /* ACCT_TOOLS_SETUID */
784 is_shadow_grp = sgr_file_present ();
789 * Start with a quick check to see if the group exists.
791 grp = getgrnam (group_name); /* local, no need for xgetgrnam */
794 _("%s: group '%s' does not exist\n"),
798 group_id = grp->gr_gid;
804 * Now make sure it isn't an NIS group.
811 _("%s: group %s is a NIS group\n"),
814 if (!yp_get_default_domain (&nis_domain) &&
815 !yp_master (nis_domain, "group.byname", &nis_master)) {
817 _("%s: %s is the NIS master\n"),
835 * Now if the group is not changed, it's our fault.
836 * Make sure failures will be reported.
838 prepare_failure_reports ();
841 * Do the hard stuff - open the files, create the group entries,
842 * then close and update the files.
850 nscd_flush_cache ("group");