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 - 2009, 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;
151 grent->gr_passwd = group_passwd;
157 * new_sgent - updates the values in a shadow group file entry
159 * new_sgent() takes all of the values that have been entered and fills
160 * in a (struct sgrp) with them.
162 static void new_sgent (struct sgrp *sgent)
165 sgent->sg_name = xstrdup (group_newname);
169 sgent->sg_passwd = group_passwd;
172 #endif /* SHADOWGRP */
175 * grp_update - update group file entries
177 * grp_update() updates the new records in the memory databases.
179 static void grp_update (void)
182 const struct group *ogrp;
186 const struct sgrp *osgrp = NULL;
187 #endif /* SHADOWGRP */
190 * Get the current settings for this group.
192 ogrp = gr_locate (group_name);
195 _("%s: group '%s' does not exist in %s\n"),
196 Prog, group_name, gr_dbname ());
204 osgrp = sgr_locate (group_name);
209 grp.gr_passwd = SHADOW_PASSWD_STRING;
213 #endif /* SHADOWGRP */
216 update_primary_groups (ogrp->gr_gid, group_newid);
220 * Write out the new group file entry.
222 if (gr_update (&grp) == 0) {
224 _("%s: failed to prepare the new %s entry '%s'\n"),
225 Prog, gr_dbname (), grp.gr_name);
228 if (nflg && (gr_remove (group_name) == 0)) {
230 _("%s: cannot remove entry '%s' from %s\n"),
231 Prog, grp.gr_name, gr_dbname ());
237 * Make sure there was a shadow entry to begin with.
241 * Write out the new shadow group entries as well.
243 if (sgr_update (&sgrp) == 0) {
245 _("%s: failed to prepare the new %s entry '%s'\n"),
246 Prog, sgr_dbname (), sgrp.sg_name);
249 if (nflg && (sgr_remove (group_name) == 0)) {
251 _("%s: cannot remove entry '%s' from %s\n"),
252 Prog, group_name, sgr_dbname ());
256 #endif /* SHADOWGRP */
260 * check_new_gid - check the new GID value for uniqueness
262 * check_new_gid() insures that the new GID value is unique.
264 static void check_new_gid (void)
267 * First, the easy stuff. If the ID can be duplicated, or if the ID
268 * didn't really change, just return. If the ID didn't change, turn
269 * off those flags. No sense doing needless work.
271 if (group_id == group_newid) {
277 (getgrgid (group_newid) == NULL) /* local, no need for xgetgrgid */
283 * Tell the user what they did wrong.
286 _("%s: GID '%lu' already exists\n"),
287 Prog, (unsigned long int) group_newid);
292 * check_new_name - check the new name for uniqueness
294 * check_new_name() insures that the new name does not exist already.
295 * You can't have the same name twice, period.
297 static void check_new_name (void)
300 * Make sure they are actually changing the name.
302 if (strcmp (group_name, group_newname) == 0) {
307 if (is_valid_group_name (group_newname)) {
310 * If the entry is found, too bad.
312 /* local, no need for xgetgrnam */
313 if (getgrnam (group_newname) != NULL) {
315 _("%s: group '%s' already exists\n"),
316 Prog, group_newname);
317 exit (E_NAME_IN_USE);
323 * All invalid group names land here.
327 _("%s: invalid group name '%s'\n"),
328 Prog, group_newname);
333 * process_flags - perform command line argument setting
335 * process_flags() interprets the command line arguments and sets the
336 * values that the user will be created with accordingly. The values
337 * are checked for sanity.
339 static void process_flags (int argc, char **argv)
341 int option_index = 0;
343 static struct option long_options[] = {
344 {"gid", required_argument, NULL, 'g'},
345 {"help", no_argument, NULL, 'h'},
346 {"new-name", required_argument, NULL, 'n'},
347 {"non-unique", no_argument, NULL, 'o'},
348 {"password", required_argument, NULL, 'p'},
349 {NULL, 0, NULL, '\0'}
352 getopt_long (argc, argv, "g:hn:op:",
353 long_options, &option_index)) != -1) {
357 if ( (get_gid (optarg, &group_newid) == 0)
358 || (group_newid == (gid_t)-1)) {
360 _("%s: invalid group ID '%s'\n"),
370 group_newname = optarg;
376 group_passwd = optarg;
388 if (optind != (argc - 1)) {
392 group_name = argv[argc - 1];
396 * close_files - close all of the files that were opened
398 * close_files() closes all of the files that were opened for this new
399 * group. This causes any modified entries to be written out.
401 static void close_files (void)
403 if (gr_close () == 0) {
405 _("%s: failure while writing changes to %s\n"),
410 audit_logger (AUDIT_USER_ACCT, Prog,
411 info_group.audit_msg,
412 group_name, AUDIT_NO_ID,
413 SHADOW_AUDIT_SUCCESS);
416 "group changed in %s (%s)",
417 gr_dbname (), info_group.action));
418 del_cleanup (cleanup_report_mod_group);
420 cleanup_unlock_group (NULL);
421 del_cleanup (cleanup_unlock_group);
426 if (sgr_close () == 0) {
428 _("%s: failure while writing changes to %s\n"),
429 Prog, sgr_dbname ());
433 audit_logger (AUDIT_USER_ACCT, Prog,
434 info_gshadow.audit_msg,
435 group_name, AUDIT_NO_ID,
436 SHADOW_AUDIT_SUCCESS);
439 "group changed in %s (%s)",
440 sgr_dbname (), info_gshadow.action));
441 del_cleanup (cleanup_report_mod_gshadow);
443 cleanup_unlock_gshadow (NULL);
444 del_cleanup (cleanup_unlock_gshadow);
446 #endif /* SHADOWGRP */
449 if (pw_close () == 0) {
451 _("%s: failure while writing changes to %s\n"),
456 audit_logger (AUDIT_USER_ACCT, Prog,
457 info_passwd.audit_msg,
458 group_name, AUDIT_NO_ID,
459 SHADOW_AUDIT_SUCCESS);
462 "group changed in %s (%s)",
463 pw_dbname (), info_passwd.action));
464 del_cleanup (cleanup_report_mod_passwd);
466 cleanup_unlock_passwd (NULL);
467 del_cleanup (cleanup_unlock_passwd);
471 audit_logger (AUDIT_USER_ACCT, Prog,
473 group_name, AUDIT_NO_ID,
474 SHADOW_AUDIT_SUCCESS);
479 * prepare_failure_reports - Prepare the cleanup_info structure for logging
480 * of success and failure to syslog or audit.
482 static void prepare_failure_reports (void)
484 info_group.name = group_name;
486 info_gshadow.name = group_name;
488 info_passwd.name = group_name;
490 info_group.audit_msg = xmalloc (512);
492 info_gshadow.audit_msg = xmalloc (512);
494 info_passwd.audit_msg = xmalloc (512);
496 snprintf (info_group.audit_msg, 511,
497 "changing %s; ", gr_dbname ());
499 snprintf (info_gshadow.audit_msg, 511,
500 "changing %s; ", sgr_dbname ());
502 snprintf (info_passwd.audit_msg, 511,
503 "changing %s; ", pw_dbname ());
505 info_group.action = info_group.audit_msg
506 + strlen (info_group.audit_msg);
508 info_gshadow.action = info_gshadow.audit_msg
509 + strlen (info_gshadow.audit_msg);
511 info_passwd.action = info_passwd.audit_msg
512 + strlen (info_passwd.audit_msg);
514 snprintf (info_group.action, 511 - strlen (info_group.audit_msg),
515 "group %s/%lu", group_name, (unsigned long int) group_id);
517 snprintf (info_gshadow.action, 511 - strlen (info_group.audit_msg),
518 "group %s", group_name);
520 snprintf (info_passwd.action, 511 - strlen (info_group.audit_msg),
521 "group %s/%lu", group_name, (unsigned long int) group_id);
524 strncat (info_group.action, ", new name: ",
525 511 - strlen (info_group.audit_msg));
526 strncat (info_group.action, group_newname,
527 511 - strlen (info_group.audit_msg));
530 strncat (info_gshadow.action, ", new name: ",
531 511 - strlen (info_gshadow.audit_msg));
532 strncat (info_gshadow.action, group_newname,
533 511 - strlen (info_gshadow.audit_msg));
536 strncat (info_passwd.action, ", new name: ",
537 511 - strlen (info_passwd.audit_msg));
538 strncat (info_passwd.action, group_newname,
539 511 - strlen (info_passwd.audit_msg));
542 strncat (info_group.action, ", new password",
543 511 - strlen (info_group.audit_msg));
546 strncat (info_gshadow.action, ", new password",
547 511 - strlen (info_gshadow.audit_msg));
551 strncat (info_group.action, ", new gid: ",
552 511 - strlen (info_group.audit_msg));
553 snprintf (info_group.action+strlen (info_group.action),
554 511 - strlen (info_group.audit_msg),
555 "%lu", (unsigned long int) group_newid);
557 strncat (info_passwd.action, ", new gid: ",
558 511 - strlen (info_passwd.audit_msg));
559 snprintf (info_passwd.action+strlen (info_passwd.action),
560 511 - strlen (info_passwd.audit_msg),
561 "%lu", (unsigned long int) group_newid);
563 info_group.audit_msg[511] = '\0';
565 info_gshadow.audit_msg[511] = '\0';
567 info_passwd.audit_msg[511] = '\0';
569 // FIXME: add a system cleanup
570 add_cleanup (cleanup_report_mod_group, &info_group);
574 add_cleanup (cleanup_report_mod_gshadow, &info_gshadow);
578 add_cleanup (cleanup_report_mod_passwd, &info_passwd);
584 * lock_files - lock the accounts databases
586 * lock_files() locks the group, gshadow, and passwd databases.
588 static void lock_files (void)
590 if (gr_lock () == 0) {
592 _("%s: cannot lock %s; try again later.\n"),
596 add_cleanup (cleanup_unlock_group, NULL);
601 if (sgr_lock () == 0) {
603 _("%s: cannot lock %s; try again later.\n"),
604 Prog, sgr_dbname ());
607 add_cleanup (cleanup_unlock_gshadow, NULL);
612 if (pw_lock () == 0) {
614 _("%s: cannot lock %s; try again later.\n"),
618 add_cleanup (cleanup_unlock_passwd, NULL);
624 * open_files - open the accounts databases
626 * open_files() opens the group, gshadow, and passwd databases.
628 static void open_files (void)
630 if (gr_open (O_RDWR) == 0) {
631 fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ());
632 SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
639 if (sgr_open (O_RDWR) == 0) {
641 _("%s: cannot open %s\n"),
642 Prog, sgr_dbname ());
643 SYSLOG ((LOG_WARN, "cannot open %s", sgr_dbname ()));
647 #endif /* SHADOWGRP */
650 if (pw_open (O_RDWR) == 0) {
652 _("%s: cannot open %s\n"),
654 SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
660 void update_primary_groups (gid_t ogid, gid_t ngid)
665 while ((pwd = getpwent ()) != NULL) {
666 if (pwd->pw_gid == ogid) {
667 const struct passwd *lpwd;
669 lpwd = pw_locate (pwd->pw_name);
672 _("%s: user '%s' does not exist in %s\n"),
673 Prog, pwd->pw_name, pw_dbname ());
678 if (pw_update (&npwd) == 0) {
680 _("%s: failed to prepare the new %s entry '%s'\n"),
681 Prog, pw_dbname (), npwd.pw_name);
691 * main - groupmod command
694 int main (int argc, char **argv)
696 #ifdef ACCT_TOOLS_SETUID
698 pam_handle_t *pamh = NULL;
701 #endif /* ACCT_TOOLS_SETUID */
706 atexit (do_cleanups);
709 * Get my name so that I can use it to report errors.
711 Prog = Basename (argv[0]);
713 (void) setlocale (LC_ALL, "");
714 (void) bindtextdomain (PACKAGE, LOCALEDIR);
715 (void) textdomain (PACKAGE);
717 process_flags (argc, argv);
719 OPENLOG ("groupmod");
721 #ifdef ACCT_TOOLS_SETUID
724 struct passwd *pampw;
725 pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
728 _("%s: Cannot determine your user name.\n"),
733 retval = pam_start ("groupmod", pampw->pw_name, &conv, &pamh);
736 if (PAM_SUCCESS == retval) {
737 retval = pam_authenticate (pamh, 0);
740 if (PAM_SUCCESS == retval) {
741 retval = pam_acct_mgmt (pamh, 0);
745 (void) pam_end (pamh, retval);
747 if (PAM_SUCCESS != retval) {
748 fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
752 #endif /* ACCT_TOOLS_SETUID */
755 is_shadow_grp = sgr_file_present ();
760 * Start with a quick check to see if the group exists.
762 grp = getgrnam (group_name); /* local, no need for xgetgrnam */
765 _("%s: group '%s' does not exist\n"),
769 group_id = grp->gr_gid;
775 * Now make sure it isn't an NIS group.
782 _("%s: group %s is a NIS group\n"),
785 if (!yp_get_default_domain (&nis_domain) &&
786 !yp_master (nis_domain, "group.byname", &nis_master)) {
788 _("%s: %s is the NIS master\n"),
806 * Now if the group is not changed, it's our fault.
807 * Make sure failures will be reported.
809 prepare_failure_reports ();
812 * Do the hard stuff - open the files, create the group entries,
813 * then close and update the files.
821 nscd_flush_cache ("group");