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)) { /* local, no need for xgetpwnam */
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 /* local, no need for xgetpwnam */
221 if (!getpwnam (user)) {
223 _("%s: unknown user %s\n"), Prog,
226 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
227 "adding to group", user, -1, 0);
237 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
238 "Listing administrators", NULL,
246 ("%s: shadow group passwords required for -A\n"),
251 if (check_list (admins))
256 case 'd': /* delete a user */
260 case 'g': /* no-op from normal password */
265 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
266 "listing members", NULL, bywho,
272 if (check_list (members))
276 case 'r': /* remove group password */
279 case 'R': /* restrict group password */
288 * Make sure exclusive flags are exclusive
291 if (aflg + dflg + rflg + Rflg + (Aflg || Mflg) > 1)
295 * Determine the name of the user that invoked this command. This
296 * is really hit or miss because there are so many ways that command
297 * can be executed and so many ways to trip up the routines that
298 * report the user name.
301 pw = get_my_pwent ();
303 fprintf (stderr, _("Who are you?\n"));
305 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "user lookup", NULL,
310 myname = xstrdup (pw->pw_name);
313 * Get the name of the group that is being affected. The group entry
314 * will be completely replicated so it may be modified later on.
318 * XXX - should get the entry using gr_locate() and modify that,
319 * getgrnam() could give us a NIS group. --marekm
321 if (!(group = argv[optind]))
324 if (!(gr = getgrnam (group))) { /* dup, no need for xgetgrnam */
325 fprintf (stderr, _("unknown group: %s\n"), group);
327 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "group lookup", group,
333 grent.gr_name = xstrdup (gr->gr_name);
334 grent.gr_passwd = xstrdup (gr->gr_passwd);
336 grent.gr_mem = dup_list (gr->gr_mem);
338 if ((sg = getsgnam (group))) {
340 sgent.sg_name = xstrdup (sg->sg_name);
341 sgent.sg_passwd = xstrdup (sg->sg_passwd);
343 sgent.sg_mem = dup_list (sg->sg_mem);
344 sgent.sg_adm = dup_list (sg->sg_adm);
346 sgent.sg_name = xstrdup (group);
347 sgent.sg_passwd = grent.gr_passwd;
348 grent.gr_passwd = "!"; /* XXX warning: const */
350 sgent.sg_mem = dup_list (grent.gr_mem);
352 sgent.sg_adm = (char **) xmalloc (sizeof (char *) * 2);
353 #ifdef FIRST_MEMBER_IS_ADMIN
354 if (sgent.sg_mem[0]) {
355 sgent.sg_adm[0] = xstrdup (sgent.sg_mem[0]);
365 * The policy here for changing a group is that 1) you must be root
366 * or 2). you must be listed as an administrative member.
367 * Administrative members can do anything to a group that the root
370 if (!amroot && !is_on_list (sgent.sg_adm, myname)) {
372 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "modify group", group,
377 #else /* ! SHADOWGRP */
379 #ifdef FIRST_MEMBER_IS_ADMIN
381 * The policy here for changing a group is that 1) you must bes root
382 * or 2) you must be the first listed member of the group. The
383 * first listed member of a group can do anything to that group that
384 * the root user can. The rationale for this hack is that the FIRST
385 * user is probably the most important user in this entire group.
388 if (grent.gr_mem[0] == (char *) 0) {
390 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
391 "modifying group", group, -1, 0);
396 if (strcmp (grent.gr_mem[0], myname) != 0) {
398 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
399 "modifying group", myname, -1, 0);
406 * This feature enabled by default could be a security problem when
407 * installed on existing systems where the first group member might
408 * be just a normal user. --marekm
412 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "modifying group",
419 #endif /* SHADOWGRP */
422 * Removing a password is straight forward. Just set the password
426 grent.gr_passwd = ""; /* XXX warning: const */
428 sgent.sg_passwd = ""; /* XXX warning: const */
431 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
432 "deleting group password", group, -1, 1);
434 SYSLOG ((LOG_INFO, "remove password from group %s by %s",
439 * Same thing for restricting the group. Set the password
442 grent.gr_passwd = "!"; /* XXX warning: const */
444 sgent.sg_passwd = "!"; /* XXX warning: const */
447 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
448 "restrict access to group", group, -1, 1);
450 SYSLOG ((LOG_INFO, "restrict access to group %s by %s",
456 * Adding a member to a member list is pretty straightforward as
457 * well. Call the appropriate routine and split.
460 printf (_("Adding user %s to group %s\n"), user, group);
461 grent.gr_mem = add_list (grent.gr_mem, user);
463 sgent.sg_mem = add_list (sgent.sg_mem, user);
466 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "adding group member",
469 SYSLOG ((LOG_INFO, "add member %s to group %s by %s", user,
475 * Removing a member from the member list is the same deal as adding
476 * one, except the routine is different.
481 printf (_("Removing user %s from group %s\n"), user, group);
483 if (is_on_list (grent.gr_mem, user)) {
485 grent.gr_mem = del_list (grent.gr_mem, user);
488 if (is_on_list (sgent.sg_mem, user)) {
490 sgent.sg_mem = del_list (sgent.sg_mem, user);
494 fprintf (stderr, _("%s: unknown member %s\n"),
497 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
498 "deleting member", user, -1, 0);
503 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "deleting member",
506 SYSLOG ((LOG_INFO, "remove member %s from group %s by %s",
507 user, group, myname));
512 * Replacing the entire list of administators is simple. Check the
513 * list to make sure everyone is a real user. Then slap the new list
518 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "setting group admin",
521 SYSLOG ((LOG_INFO, "set administrators of %s to %s",
523 sgent.sg_adm = comma_to_list (admins);
530 * Replacing the entire list of members is simple. Check the list to
531 * make sure everyone is a real user. Then slap the new list in
536 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
537 "setting group members", group, -1, 1);
539 SYSLOG ((LOG_INFO, "set members of %s to %s", group, members));
541 sgent.sg_mem = comma_to_list (members);
543 grent.gr_mem = comma_to_list (members);
548 * If the password is being changed, the input and output must both
549 * be a tty. The typical keyboard signals are caught so the termio
550 * modes can be restored.
552 if (!isatty (0) || !isatty (1)) {
553 fprintf (stderr, _("%s: Not a tty\n"), Prog);
555 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "changing password",
561 catch_signals (0); /* save tty modes */
563 signal (SIGHUP, catch_signals);
564 signal (SIGINT, catch_signals);
565 signal (SIGQUIT, catch_signals);
566 signal (SIGTERM, catch_signals);
568 signal (SIGTSTP, catch_signals);
572 * A new password is to be entered and it must be encrypted, etc.
573 * The password will be prompted for twice, and both entries must be
574 * identical. There is no need to validate the old password since
575 * the invoker is either the group owner, or root.
577 printf (_("Changing the password for group %s\n"), group);
579 for (retries = 0; retries < RETRIES; retries++) {
580 if (!(cp = getpass (_("New Password: "))))
585 if (!(cp = getpass (_("Re-enter new password: "))))
588 if (strcmp (pass, cp) == 0) {
594 memzero (pass, sizeof pass);
596 if (retries + 1 < RETRIES) {
597 puts (_("They don't match; try again"));
599 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
600 "changing password", group, -1, 0);
605 if (retries == RETRIES) {
606 fprintf (stderr, _("%s: Try again later\n"), Prog);
610 cp = pw_encrypt (pass, crypt_make_salt (NULL, NULL));
611 memzero (pass, sizeof pass);
614 sgent.sg_passwd = cp;
617 grent.gr_passwd = cp;
619 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "changing password", group,
622 SYSLOG ((LOG_INFO, "change the password for group %s by %s", group,
626 * This is the common arrival point to output the new group file.
627 * The freshly crafted entry is in allocated space. The group file
628 * will be locked and opened for writing. The new entry will be
633 fprintf (stderr, _("Cannot change ID to root.\n"));
634 SYSLOG ((LOG_ERR, "can't setuid(0)"));
636 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "changing id to root",
645 fprintf (stderr, _("%s: can't get lock\n"), Prog);
646 SYSLOG ((LOG_WARN, "failed to get lock for /etc/group"));
648 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "locking /etc/group",
654 if (is_shadowgrp && !sgr_lock ()) {
655 fprintf (stderr, _("%s: can't get shadow lock\n"), Prog);
656 SYSLOG ((LOG_WARN, "failed to get lock for /etc/gshadow"));
658 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
659 "locking /etc/gshadow", group, -1, 0);
664 if (!gr_open (O_RDWR)) {
665 fprintf (stderr, _("%s: can't open file\n"), Prog);
666 SYSLOG ((LOG_WARN, "cannot open /etc/group"));
668 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "opening /etc/group",
674 if (is_shadowgrp && !sgr_open (O_RDWR)) {
675 fprintf (stderr, _("%s: can't open shadow file\n"), Prog);
676 SYSLOG ((LOG_WARN, "cannot open /etc/gshadow"));
678 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
679 "opening /etc/gshadow", group, -1, 0);
684 if (!gr_update (&grent)) {
685 fprintf (stderr, _("%s: can't update entry\n"), Prog);
686 SYSLOG ((LOG_WARN, "cannot update /etc/group"));
688 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "updating /etc/group",
694 if (is_shadowgrp && !sgr_update (&sgent)) {
695 fprintf (stderr, _("%s: can't update shadow entry\n"), Prog);
696 SYSLOG ((LOG_WARN, "cannot update /etc/gshadow"));
698 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
699 "updating /etc/gshadow", group, -1, 0);
705 fprintf (stderr, _("%s: can't re-write file\n"), Prog);
706 SYSLOG ((LOG_WARN, "cannot re-write /etc/group"));
708 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
709 "rewriting /etc/group", group, -1, 0);
714 if (is_shadowgrp && !sgr_close ()) {
715 fprintf (stderr, _("%s: can't re-write shadow file\n"), Prog);
716 SYSLOG ((LOG_WARN, "cannot re-write /etc/gshadow"));
718 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
719 "rewriting /etc/gshadow", group, -1, 0);
727 fprintf (stderr, _("%s: can't unlock file\n"), Prog);
729 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
730 "unlocking group file", group, -1, 0);
735 nscd_flush_cache ("group");