2 * Copyright 1990 - 1994, Julianne Frances Haugh
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Julianne F. Haugh nor the names of its contributors
14 * may be used to endorse or promote products derived from this software
15 * without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 #include <sys/types.h>
42 #include "exitcodes.h"
45 #include "prototypes.h"
55 static int is_shadowgrp;
59 aflg = 0, Aflg = 0, dflg = 0, Mflg = 0, rflg = 0, Rflg = 0;
61 unsigned int bywho = -1;
67 /* local function prototypes */
68 static void usage (void);
69 static RETSIGTYPE catch_signals (int);
70 static int check_list (const char *);
73 * usage - display usage message
75 static void usage (void)
77 fprintf (stderr, _("Usage: %s [-r|-R] group\n"), Prog);
78 fprintf (stderr, _(" %s [-a user] group\n"), Prog);
79 fprintf (stderr, _(" %s [-d user] group\n"), Prog);
82 _(" %s [-A user,...] [-M user,...] group\n"), Prog);
84 fprintf (stderr, _(" %s [-M user,...] group\n"), Prog);
90 * catch_signals - set or reset termio modes.
92 * catch_signals() is called before processing begins. signal() is then
93 * called with catch_signals() as the signal handler. If signal later
94 * calls catch_signals() with a signal number, the terminal modes are
97 static RETSIGTYPE catch_signals (int killed)
114 * check_list - check a comma-separated list of user names for validity
116 * check_list scans a comma-separated list of user names and checks
117 * that each listed name exists.
119 static int check_list (const char *users)
121 const char *start, *end;
126 for (start = users; start && *start; start = end) {
127 if ((end = strchr (start, ','))) {
131 len = strlen (start);
134 if (len > sizeof (username) - 1)
135 len = sizeof (username) - 1;
136 strncpy (username, start, len);
137 username[len] = '\0';
140 * This user must exist.
143 if (!getpwnam (username)) {
144 fprintf (stderr, _("%s: unknown user %s\n"),
152 static void failure (void)
154 fprintf (stderr, _("%s: Permission denied.\n"), Prog);
159 * gpasswd - administer the /etc/group file
161 * -a user add user to the named group
162 * -d user remove user from the named group
163 * -r remove password from the named group
164 * -R restrict access to the named group
165 * -A user,... make list of users the administrative users
166 * -M user,... make list of users the group members
168 int main (int argc, char **argv)
174 struct group *gr = NULL;
176 static char pass[BUFSIZ];
179 struct sgrp *sg = NULL;
183 struct passwd *pw = NULL;
187 char *members = NULL;
194 setlocale (LC_ALL, "");
195 bindtextdomain (PACKAGE, LOCALEDIR);
196 textdomain (PACKAGE);
199 * Make a note of whether or not this command was invoked by root.
200 * This will be used to bypass certain checks later on. Also, set
201 * the real user ID to match the effective user ID. This will
202 * prevent the invoker from issuing signals which would interfer
205 amroot = getuid () == 0;
207 Prog = Basename (argv[0]);
210 setbuf (stdout, NULL);
211 setbuf (stderr, NULL);
214 is_shadowgrp = sgr_file_present ();
216 while ((flag = getopt (argc, argv, "a:A:d:gM:rR")) != EOF) {
218 case 'a': /* add a user */
220 if (!getpwnam (user)) {
222 _("%s: unknown user %s\n"), Prog,
225 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
226 "adding to group", user, -1, 0);
236 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
237 "Listing administrators", NULL,
245 ("%s: shadow group passwords required for -A\n"),
250 if (check_list (admins))
255 case 'd': /* delete a user */
259 case 'g': /* no-op from normal password */
264 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
265 "listing members", NULL, bywho,
271 if (check_list (members))
275 case 'r': /* remove group password */
278 case 'R': /* restrict group password */
287 * Make sure exclusive flags are exclusive
290 if (aflg + dflg + rflg + Rflg + (Aflg || Mflg) > 1)
294 * Determine the name of the user that invoked this command. This
295 * is really hit or miss because there are so many ways that command
296 * can be executed and so many ways to trip up the routines that
297 * report the user name.
300 pw = get_my_pwent ();
302 fprintf (stderr, _("Who are you?\n"));
304 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "user lookup", NULL,
309 myname = xstrdup (pw->pw_name);
312 * Get the name of the group that is being affected. The group entry
313 * will be completely replicated so it may be modified later on.
317 * XXX - should get the entry using gr_locate() and modify that,
318 * getgrnam() could give us a NIS group. --marekm
320 if (!(group = argv[optind]))
323 if (!(gr = getgrnam (group))) {
324 fprintf (stderr, _("unknown group: %s\n"), group);
326 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "group lookup", group,
332 grent.gr_name = xstrdup (gr->gr_name);
333 grent.gr_passwd = xstrdup (gr->gr_passwd);
335 grent.gr_mem = dup_list (gr->gr_mem);
337 if ((sg = getsgnam (group))) {
339 sgent.sg_name = xstrdup (sg->sg_name);
340 sgent.sg_passwd = xstrdup (sg->sg_passwd);
342 sgent.sg_mem = dup_list (sg->sg_mem);
343 sgent.sg_adm = dup_list (sg->sg_adm);
345 sgent.sg_name = xstrdup (group);
346 sgent.sg_passwd = grent.gr_passwd;
347 grent.gr_passwd = "!"; /* XXX warning: const */
349 sgent.sg_mem = dup_list (grent.gr_mem);
351 sgent.sg_adm = (char **) xmalloc (sizeof (char *) * 2);
352 #ifdef FIRST_MEMBER_IS_ADMIN
353 if (sgent.sg_mem[0]) {
354 sgent.sg_adm[0] = xstrdup (sgent.sg_mem[0]);
364 * The policy here for changing a group is that 1) you must be root
365 * or 2). you must be listed as an administrative member.
366 * Administrative members can do anything to a group that the root
369 if (!amroot && !is_on_list (sgent.sg_adm, myname)) {
371 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "modify group", group,
376 #else /* ! SHADOWGRP */
378 #ifdef FIRST_MEMBER_IS_ADMIN
380 * The policy here for changing a group is that 1) you must bes root
381 * or 2) you must be the first listed member of the group. The
382 * first listed member of a group can do anything to that group that
383 * the root user can. The rationale for this hack is that the FIRST
384 * user is probably the most important user in this entire group.
387 if (grent.gr_mem[0] == (char *) 0) {
389 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
390 "modifying group", group, -1, 0);
395 if (strcmp (grent.gr_mem[0], myname) != 0) {
397 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
398 "modifying group", myname, -1, 0);
405 * This feature enabled by default could be a security problem when
406 * installed on existing systems where the first group member might
407 * be just a normal user. --marekm
411 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "modifying group",
418 #endif /* SHADOWGRP */
421 * Removing a password is straight forward. Just set the password
425 grent.gr_passwd = ""; /* XXX warning: const */
427 sgent.sg_passwd = ""; /* XXX warning: const */
430 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
431 "deleting group password", group, -1, 1);
433 SYSLOG ((LOG_INFO, "remove password from group %s by %s",
438 * Same thing for restricting the group. Set the password
441 grent.gr_passwd = "!"; /* XXX warning: const */
443 sgent.sg_passwd = "!"; /* XXX warning: const */
446 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
447 "restrict access to group", group, -1, 1);
449 SYSLOG ((LOG_INFO, "restrict access to group %s by %s",
455 * Adding a member to a member list is pretty straightforward as
456 * well. Call the appropriate routine and split.
459 printf (_("Adding user %s to group %s\n"), user, group);
460 grent.gr_mem = add_list (grent.gr_mem, user);
462 sgent.sg_mem = add_list (sgent.sg_mem, user);
465 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "adding group member",
468 SYSLOG ((LOG_INFO, "add member %s to group %s by %s", user,
474 * Removing a member from the member list is the same deal as adding
475 * one, except the routine is different.
480 printf (_("Removing user %s from group %s\n"), user, group);
482 if (is_on_list (grent.gr_mem, user)) {
484 grent.gr_mem = del_list (grent.gr_mem, user);
487 if (is_on_list (sgent.sg_mem, user)) {
489 sgent.sg_mem = del_list (sgent.sg_mem, user);
493 fprintf (stderr, _("%s: unknown member %s\n"),
496 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
497 "deleting member", user, -1, 0);
502 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "deleting member",
505 SYSLOG ((LOG_INFO, "remove member %s from group %s by %s",
506 user, group, myname));
511 * Replacing the entire list of administators is simple. Check the
512 * list to make sure everyone is a real user. Then slap the new list
517 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "setting group admin",
520 SYSLOG ((LOG_INFO, "set administrators of %s to %s",
522 sgent.sg_adm = comma_to_list (admins);
529 * Replacing the entire list of members is simple. Check the list to
530 * make sure everyone is a real user. Then slap the new list in
535 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
536 "setting group members", group, -1, 1);
538 SYSLOG ((LOG_INFO, "set members of %s to %s", group, members));
540 sgent.sg_mem = comma_to_list (members);
542 grent.gr_mem = comma_to_list (members);
547 * If the password is being changed, the input and output must both
548 * be a tty. The typical keyboard signals are caught so the termio
549 * modes can be restored.
551 if (!isatty (0) || !isatty (1)) {
552 fprintf (stderr, _("%s: Not a tty\n"), Prog);
554 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "changing password",
560 catch_signals (0); /* save tty modes */
562 signal (SIGHUP, catch_signals);
563 signal (SIGINT, catch_signals);
564 signal (SIGQUIT, catch_signals);
565 signal (SIGTERM, catch_signals);
567 signal (SIGTSTP, catch_signals);
571 * A new password is to be entered and it must be encrypted, etc.
572 * The password will be prompted for twice, and both entries must be
573 * identical. There is no need to validate the old password since
574 * the invoker is either the group owner, or root.
576 printf (_("Changing the password for group %s\n"), group);
578 for (retries = 0; retries < RETRIES; retries++) {
579 if (!(cp = getpass (_("New Password: "))))
584 if (!(cp = getpass (_("Re-enter new password: "))))
587 if (strcmp (pass, cp) == 0) {
593 memzero (pass, sizeof pass);
595 if (retries + 1 < RETRIES) {
596 puts (_("They don't match; try again"));
598 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
599 "changing password", group, -1, 0);
604 if (retries == RETRIES) {
605 fprintf (stderr, _("%s: Try again later\n"), Prog);
609 cp = pw_encrypt (pass, crypt_make_salt ());
610 memzero (pass, sizeof pass);
613 sgent.sg_passwd = cp;
616 grent.gr_passwd = cp;
618 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "changing password", group,
621 SYSLOG ((LOG_INFO, "change the password for group %s by %s", group,
625 * This is the common arrival point to output the new group file.
626 * The freshly crafted entry is in allocated space. The group file
627 * will be locked and opened for writing. The new entry will be
632 fprintf (stderr, _("Cannot change ID to root.\n"));
633 SYSLOG ((LOG_ERR, "can't setuid(0)"));
635 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "changing id to root",
644 fprintf (stderr, _("%s: can't get lock\n"), Prog);
645 SYSLOG ((LOG_WARN, "failed to get lock for /etc/group"));
647 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "locking /etc/group",
653 if (is_shadowgrp && !sgr_lock ()) {
654 fprintf (stderr, _("%s: can't get shadow lock\n"), Prog);
655 SYSLOG ((LOG_WARN, "failed to get lock for /etc/gshadow"));
657 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
658 "locking /etc/gshadow", group, -1, 0);
663 if (!gr_open (O_RDWR)) {
664 fprintf (stderr, _("%s: can't open file\n"), Prog);
665 SYSLOG ((LOG_WARN, "cannot open /etc/group"));
667 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "opening /etc/group",
673 if (is_shadowgrp && !sgr_open (O_RDWR)) {
674 fprintf (stderr, _("%s: can't open shadow file\n"), Prog);
675 SYSLOG ((LOG_WARN, "cannot open /etc/gshadow"));
677 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
678 "opening /etc/gshadow", group, -1, 0);
683 if (!gr_update (&grent)) {
684 fprintf (stderr, _("%s: can't update entry\n"), Prog);
685 SYSLOG ((LOG_WARN, "cannot update /etc/group"));
687 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "updating /etc/group",
693 if (is_shadowgrp && !sgr_update (&sgent)) {
694 fprintf (stderr, _("%s: can't update shadow entry\n"), Prog);
695 SYSLOG ((LOG_WARN, "cannot update /etc/gshadow"));
697 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
698 "updating /etc/gshadow", group, -1, 0);
704 fprintf (stderr, _("%s: can't re-write file\n"), Prog);
705 SYSLOG ((LOG_WARN, "cannot re-write /etc/group"));
707 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
708 "rewriting /etc/group", group, -1, 0);
713 if (is_shadowgrp && !sgr_close ()) {
714 fprintf (stderr, _("%s: can't re-write shadow file\n"), Prog);
715 SYSLOG ((LOG_WARN, "cannot re-write /etc/gshadow"));
717 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
718 "rewriting /etc/gshadow", group, -1, 0);
726 fprintf (stderr, _("%s: can't unlock file\n"), Prog);
728 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
729 "unlocking group file", group, -1, 0);
734 nscd_flush_cache ("group");