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"
52 /* The name of this command, as it is invoked */
56 /* Indicate if shadow groups are enabled on the system
57 * (/etc/gshadow present) */
58 static int is_shadowgrp;
62 aflg = 0, Aflg = 0, dflg = 0, Mflg = 0, rflg = 0, Rflg = 0;
64 /* The UID of the caller */
65 unsigned long bywho = -1;
67 /* The number of retries for th user to provide and repeat a new password */
72 /* local function prototypes */
73 static void usage (void);
74 static RETSIGTYPE catch_signals (int killed);
75 static int check_list (const char *users);
78 * usage - display usage message
80 static void usage (void)
82 fprintf (stderr, _("Usage: %s [-r|-R] group\n"), Prog);
83 fprintf (stderr, _(" %s [-a user] group\n"), Prog);
84 fprintf (stderr, _(" %s [-d user] group\n"), Prog);
87 _(" %s [-A user,...] [-M user,...] group\n"), Prog);
89 fprintf (stderr, _(" %s [-M user,...] group\n"), Prog);
95 * catch_signals - set or reset termio modes.
97 * catch_signals() is called before processing begins. signal() is then
98 * called with catch_signals() as the signal handler. If signal later
99 * calls catch_signals() with a signal number, the terminal modes are
102 static RETSIGTYPE catch_signals (int killed)
119 * check_list - check a comma-separated list of user names for validity
121 * check_list scans a comma-separated list of user names and checks
122 * that each listed name exists.
124 static int check_list (const char *users)
126 const char *start, *end;
131 for (start = users; start && *start; start = end) {
132 if ((end = strchr (start, ','))) {
136 len = strlen (start);
139 if (len > sizeof (username) - 1)
140 len = sizeof (username) - 1;
141 strncpy (username, start, len);
142 username[len] = '\0';
145 * This user must exist.
148 if (!getpwnam (username)) { /* local, no need for xgetpwnam */
149 fprintf (stderr, _("%s: unknown user %s\n"),
157 static void failure (void)
159 fprintf (stderr, _("%s: Permission denied.\n"), Prog);
164 * gpasswd - administer the /etc/group file
166 * -a user add user to the named group
167 * -d user remove user from the named group
168 * -r remove password from the named group
169 * -R restrict access to the named group
170 * -A user,... make list of users the administrative users
171 * -M user,... make list of users the group members
173 int main (int argc, char **argv)
179 struct group const*gr = NULL;
181 static char pass[BUFSIZ];
184 struct sgrp const*sg = NULL;
188 struct passwd *pw = NULL;
192 char *members = NULL;
199 setlocale (LC_ALL, "");
200 bindtextdomain (PACKAGE, LOCALEDIR);
201 textdomain (PACKAGE);
204 * Make a note of whether or not this command was invoked by root.
205 * This will be used to bypass certain checks later on. Also, set
206 * the real user ID to match the effective user ID. This will
207 * prevent the invoker from issuing signals which would interfer
210 amroot = getuid () == 0;
212 Prog = Basename (argv[0]);
215 setbuf (stdout, NULL);
216 setbuf (stderr, NULL);
219 is_shadowgrp = sgr_file_present ();
221 while ((flag = getopt (argc, argv, "a:A:d:gM:rR")) != EOF) {
223 case 'a': /* add a user */
225 /* local, no need for xgetpwnam */
226 if (!getpwnam (user)) {
228 _("%s: unknown user %s\n"), Prog,
231 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
232 "adding to group", user, -1, 0);
242 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
243 "Listing administrators", NULL,
251 ("%s: shadow group passwords required for -A\n"),
256 if (check_list (admins))
261 case 'd': /* delete a user */
265 case 'g': /* no-op from normal password */
270 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
271 "listing members", NULL, bywho,
277 if (check_list (members))
281 case 'r': /* remove group password */
284 case 'R': /* restrict group password */
293 * Make sure exclusive flags are exclusive
296 if (aflg + dflg + rflg + Rflg + (Aflg || Mflg) > 1)
300 * Determine the name of the user that invoked this command. This
301 * is really hit or miss because there are so many ways that command
302 * can be executed and so many ways to trip up the routines that
303 * report the user name.
306 pw = get_my_pwent ();
308 fprintf (stderr, _("Who are you?\n"));
310 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "user lookup", NULL,
315 myname = xstrdup (pw->pw_name);
318 * Get the name of the group that is being affected. The group entry
319 * will be completely replicated so it may be modified later on.
322 if (!(group = argv[optind]))
325 if (!gr_open (O_RDONLY)) {
326 fprintf (stderr, _("%s: can't open file\n"), Prog);
327 SYSLOG ((LOG_WARN, "cannot open /etc/group"));
329 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "opening /etc/group",
335 if (!(gr = gr_locate (group))) {
336 fprintf (stderr, _("unknown group: %s\n"), group);
338 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "group lookup", group,
344 grent.gr_name = xstrdup (gr->gr_name);
345 grent.gr_passwd = xstrdup (gr->gr_passwd);
347 grent.gr_mem = dup_list (gr->gr_mem);
349 fprintf (stderr, _("%s: can't close file\n"), Prog);
350 SYSLOG ((LOG_WARN, "cannot close /etc/group"));
352 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
353 "closing /etc/group", group, -1, 0);
358 if (!sgr_open (O_RDONLY)) {
359 fprintf (stderr, _("%s: can't open shadow file\n"), Prog);
360 SYSLOG ((LOG_WARN, "cannot open /etc/gshadow"));
362 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
363 "opening /etc/gshadow", group, -1, 0);
367 if ((sg = sgr_locate (group))) {
369 sgent.sg_name = xstrdup (sg->sg_name);
370 sgent.sg_passwd = xstrdup (sg->sg_passwd);
372 sgent.sg_mem = dup_list (sg->sg_mem);
373 sgent.sg_adm = dup_list (sg->sg_adm);
375 sgent.sg_name = xstrdup (group);
376 sgent.sg_passwd = grent.gr_passwd;
377 grent.gr_passwd = "!"; /* XXX warning: const */
379 sgent.sg_mem = dup_list (grent.gr_mem);
381 sgent.sg_adm = (char **) xmalloc (sizeof (char *) * 2);
382 #ifdef FIRST_MEMBER_IS_ADMIN
383 if (sgent.sg_mem[0]) {
384 sgent.sg_adm[0] = xstrdup (sgent.sg_mem[0]);
393 fprintf (stderr, _("%s: can't close shadow file\n"), Prog);
394 SYSLOG ((LOG_WARN, "cannot close /etc/gshadow"));
396 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
397 "closing /etc/gshadow", group, -1, 0);
403 * The policy here for changing a group is that 1) you must be root
404 * or 2). you must be listed as an administrative member.
405 * Administrative members can do anything to a group that the root
408 if (!amroot && !is_on_list (sgent.sg_adm, myname)) {
410 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "modify group", group,
415 #else /* ! SHADOWGRP */
417 #ifdef FIRST_MEMBER_IS_ADMIN
419 * The policy here for changing a group is that 1) you must bes root
420 * or 2) you must be the first listed member of the group. The
421 * first listed member of a group can do anything to that group that
422 * the root user can. The rationale for this hack is that the FIRST
423 * user is probably the most important user in this entire group.
426 if (grent.gr_mem[0] == (char *) 0) {
428 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
429 "modifying group", group, -1, 0);
434 if (strcmp (grent.gr_mem[0], myname) != 0) {
436 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
437 "modifying group", myname, -1, 0);
444 * This feature enabled by default could be a security problem when
445 * installed on existing systems where the first group member might
446 * be just a normal user. --marekm
450 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "modifying group",
457 #endif /* SHADOWGRP */
460 * Removing a password is straight forward. Just set the password
464 grent.gr_passwd = ""; /* XXX warning: const */
466 sgent.sg_passwd = ""; /* XXX warning: const */
469 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
470 "deleting group password", group, -1, 1);
472 SYSLOG ((LOG_INFO, "remove password from group %s by %s",
477 * Same thing for restricting the group. Set the password
480 grent.gr_passwd = "!"; /* XXX warning: const */
482 sgent.sg_passwd = "!"; /* XXX warning: const */
485 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
486 "restrict access to group", group, -1, 1);
488 SYSLOG ((LOG_INFO, "restrict access to group %s by %s",
494 * Adding a member to a member list is pretty straightforward as
495 * well. Call the appropriate routine and split.
498 printf (_("Adding user %s to group %s\n"), user, group);
499 grent.gr_mem = add_list (grent.gr_mem, user);
501 sgent.sg_mem = add_list (sgent.sg_mem, user);
504 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "adding group member",
507 SYSLOG ((LOG_INFO, "add member %s to group %s by %s", user,
513 * Removing a member from the member list is the same deal as adding
514 * one, except the routine is different.
519 printf (_("Removing user %s from group %s\n"), user, group);
521 if (is_on_list (grent.gr_mem, user)) {
523 grent.gr_mem = del_list (grent.gr_mem, user);
526 if (is_on_list (sgent.sg_mem, user)) {
528 sgent.sg_mem = del_list (sgent.sg_mem, user);
532 fprintf (stderr, _("%s: unknown member %s\n"),
535 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
536 "deleting member", user, -1, 0);
541 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "deleting member",
544 SYSLOG ((LOG_INFO, "remove member %s from group %s by %s",
545 user, group, myname));
550 * Replacing the entire list of administators is simple. Check the
551 * list to make sure everyone is a real user. Then slap the new list
556 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "setting group admin",
559 SYSLOG ((LOG_INFO, "set administrators of %s to %s",
561 sgent.sg_adm = comma_to_list (admins);
568 * Replacing the entire list of members is simple. Check the list to
569 * make sure everyone is a real user. Then slap the new list in
574 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
575 "setting group members", group, -1, 1);
577 SYSLOG ((LOG_INFO, "set members of %s to %s", group, members));
579 sgent.sg_mem = comma_to_list (members);
581 grent.gr_mem = comma_to_list (members);
586 * If the password is being changed, the input and output must both
587 * be a tty. The typical keyboard signals are caught so the termio
588 * modes can be restored.
590 if (!isatty (0) || !isatty (1)) {
591 fprintf (stderr, _("%s: Not a tty\n"), Prog);
593 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "changing password",
599 catch_signals (0); /* save tty modes */
601 signal (SIGHUP, catch_signals);
602 signal (SIGINT, catch_signals);
603 signal (SIGQUIT, catch_signals);
604 signal (SIGTERM, catch_signals);
606 signal (SIGTSTP, catch_signals);
610 * A new password is to be entered and it must be encrypted, etc.
611 * The password will be prompted for twice, and both entries must be
612 * identical. There is no need to validate the old password since
613 * the invoker is either the group owner, or root.
615 printf (_("Changing the password for group %s\n"), group);
617 for (retries = 0; retries < RETRIES; retries++) {
618 if (!(cp = getpass (_("New Password: "))))
623 if (!(cp = getpass (_("Re-enter new password: "))))
626 if (strcmp (pass, cp) == 0) {
632 memzero (pass, sizeof pass);
634 if (retries + 1 < RETRIES) {
635 puts (_("They don't match; try again"));
637 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
638 "changing password", group, -1, 0);
643 if (retries == RETRIES) {
644 fprintf (stderr, _("%s: Try again later\n"), Prog);
648 cp = pw_encrypt (pass, crypt_make_salt (NULL, NULL));
649 memzero (pass, sizeof pass);
652 sgent.sg_passwd = cp;
655 grent.gr_passwd = cp;
657 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "changing password", group,
660 SYSLOG ((LOG_INFO, "change the password for group %s by %s", group,
664 * This is the common arrival point to output the new group file.
665 * The freshly crafted entry is in allocated space. The group file
666 * will be locked and opened for writing. The new entry will be
671 fprintf (stderr, _("Cannot change ID to root.\n"));
672 SYSLOG ((LOG_ERR, "can't setuid(0)"));
674 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "changing id to root",
683 fprintf (stderr, _("%s: can't get lock\n"), Prog);
684 SYSLOG ((LOG_WARN, "failed to get lock for /etc/group"));
686 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "locking /etc/group",
692 if (is_shadowgrp && !sgr_lock ()) {
693 fprintf (stderr, _("%s: can't get shadow lock\n"), Prog);
694 SYSLOG ((LOG_WARN, "failed to get lock for /etc/gshadow"));
696 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
697 "locking /etc/gshadow", group, -1, 0);
702 if (!gr_open (O_RDWR)) {
703 fprintf (stderr, _("%s: can't open file\n"), Prog);
704 SYSLOG ((LOG_WARN, "cannot open /etc/group"));
706 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "opening /etc/group",
712 if (is_shadowgrp && !sgr_open (O_RDWR)) {
713 fprintf (stderr, _("%s: can't open shadow file\n"), Prog);
714 SYSLOG ((LOG_WARN, "cannot open /etc/gshadow"));
716 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
717 "opening /etc/gshadow", group, -1, 0);
722 if (!gr_update (&grent)) {
723 fprintf (stderr, _("%s: can't update entry\n"), Prog);
724 SYSLOG ((LOG_WARN, "cannot update /etc/group"));
726 audit_logger (AUDIT_USER_CHAUTHTOK, Prog, "updating /etc/group",
732 if (is_shadowgrp && !sgr_update (&sgent)) {
733 fprintf (stderr, _("%s: can't update shadow entry\n"), Prog);
734 SYSLOG ((LOG_WARN, "cannot update /etc/gshadow"));
736 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
737 "updating /etc/gshadow", group, -1, 0);
743 fprintf (stderr, _("%s: can't re-write file\n"), Prog);
744 SYSLOG ((LOG_WARN, "cannot re-write /etc/group"));
746 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
747 "rewriting /etc/group", group, -1, 0);
752 if (is_shadowgrp && !sgr_close ()) {
753 fprintf (stderr, _("%s: can't re-write shadow file\n"), Prog);
754 SYSLOG ((LOG_WARN, "cannot re-write /etc/gshadow"));
756 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
757 "rewriting /etc/gshadow", group, -1, 0);
765 fprintf (stderr, _("%s: can't unlock file\n"), Prog);
767 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
768 "unlocking group file", group, -1, 0);
773 nscd_flush_cache ("group");