2 * Copyright (c) 1990 - 1994, Julianne Frances Haugh
3 * Copyright (c) 1996 - 2000, Marek Michałkiewicz
4 * Copyright (c) 2001 - 2006, Tomasz Kłoczko
5 * Copyright (c) 2007 - 2008, 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.
35 #ident "$Id: newgrp.c 3034 2009-07-22 13:30:06Z nekral-guest $"
44 #include "prototypes.h"
46 #include "exitcodes.h"
53 extern char **newenvp;
54 extern char **environ;
58 static /*@null@*/ /*@only@*/GETGROUPS_T *grouplist;
61 static bool is_newgrp;
64 static char audit_buf[80];
67 /* local function prototypes */
68 static void usage (void);
69 static void check_perms (const struct group *grp,
71 const char *groupname);
72 static void syslog_sg (const char *name, const char *group);
75 * usage - print command usage message
77 static void usage (void)
80 (void) fputs (_("Usage: newgrp [-] [group]\n"), stderr);
82 (void) fputs (_("Usage: sg group [[-c] command]\n"), stderr);
87 * find_matching_group - search all groups of a given group id for
88 * membership of a given username
90 static /*@null@*/struct group *find_matching_group (const char *name, gid_t gid)
97 while ((gr = getgrent ()) != NULL) {
98 if (gr->gr_gid != gid) {
103 * A group with matching GID was found.
104 * Test for membership of 'name'.
107 while ((NULL != *look) && notfound) {
108 notfound = (strcmp (*look, name) != 0);
120 * check_perms - check if the user is allowed to switch to this group
122 * If needed, the user will be authenticated.
124 * It will not return if the user could not be authenticated.
126 static void check_perms (const struct group *grp,
128 const char *groupname)
130 bool needspasswd = false;
136 * see if she is a member of this group (i.e. in the list of
137 * members of the group, or if the group is her primary group).
139 * If she isn't a member, she needs to provide the group password.
140 * If there is no group password, she will be denied access
144 if ( (grp->gr_gid != pwd->pw_gid)
145 && !is_on_list (grp->gr_mem, pwd->pw_name)) {
150 * If she does not have either a shadowed password, or a regular
151 * password, and the group has a password, she needs to give the
154 spwd = xgetspnam (pwd->pw_name);
156 pwd->pw_passwd = spwd->sp_pwdp;
159 if ((pwd->pw_passwd[0] == '\0') && (grp->gr_passwd[0] != '\0')) {
164 * Now I see about letting her into the group she requested. If she
165 * is the root user, I'll let her in without having to prompt for
166 * the password. Otherwise I ask for a password if she flunked one
167 * of the tests above.
169 if ((getuid () != 0) && needspasswd) {
171 * get the password from her, and set the salt for
172 * the decryption from the group file.
174 cp = getpass (_("Password: "));
180 * encrypt the key she gave us using the salt from the
181 * password in the group file. The result of this encryption
182 * must match the previously encrypted value in the file.
184 cpasswd = pw_encrypt (cp, grp->gr_passwd);
187 if (grp->gr_passwd[0] == '\0' ||
188 strcmp (cpasswd, grp->gr_passwd) != 0) {
190 snprintf (audit_buf, sizeof(audit_buf),
191 "authentication new-gid=%lu",
192 (unsigned long) grp->gr_gid);
193 audit_logger (AUDIT_GRP_AUTH, Prog,
195 (unsigned int) getuid (), 0);
198 "Invalid password for group '%s' from '%s'",
199 groupname, pwd->pw_name));
201 (void) fputs (_("Invalid password.\n"), stderr);
205 snprintf (audit_buf, sizeof(audit_buf),
206 "authentication new-gid=%lu",
207 (unsigned long) grp->gr_gid);
208 audit_logger (AUDIT_GRP_AUTH, Prog,
210 (unsigned int) getuid (), 1);
217 /* The closelog is probably unnecessary, but it does no
223 snprintf (audit_buf, sizeof(audit_buf),
224 "changing new-group=%s", groupname);
225 audit_logger (AUDIT_CHGRP_ID, Prog,
227 (unsigned int) getuid (), 0);
229 audit_logger (AUDIT_CHGRP_ID, Prog,
231 (unsigned int) getuid (), 0);
239 * syslog_sg - log the change of group to syslog
241 * The loggout will also be logged when the user will quit the
244 static void syslog_sg (const char *name, const char *group)
246 const char *loginname = getlogin ();
247 const char *tty = ttyname (0);
249 if (loginname != NULL) {
250 loginname = xstrdup (loginname);
256 if (loginname == NULL) {
261 } else if (strncmp (tty, "/dev/", 5) == 0) {
265 "user '%s' (login '%s' on %s) switched to group '%s'",
266 name, loginname, tty, group));
269 * We want to fork and exec the new shell in the child, leaving the
270 * parent waiting to log the session close.
272 * The parent must ignore signals generated from the console
273 * (SIGINT, SIGQUIT, SIGHUP) which might make the parent terminate
274 * before its child. When bash is exec'ed as the subshell, it
275 * generates a new process group id for itself, and consequently
276 * only SIGHUP, which is sent to all process groups in the session,
277 * can reach the parent. However, since arbitrary programs can be
278 * specified as login shells, there is no such guarantee in general.
279 * For the same reason, we must also ignore stop signals generated
280 * from the console (SIGTSTP, SIGTTIN, and SIGTTOU) in order to
281 * avoid any possibility of the parent being stopped when it
282 * receives SIGCHLD from the terminating subshell. -- JWP
287 /* Ignore these signals. The signal handlers will later be
288 * restored to the default handlers. */
289 (void) signal (SIGINT, SIG_IGN);
290 (void) signal (SIGQUIT, SIG_IGN);
291 (void) signal (SIGHUP, SIG_IGN);
292 (void) signal (SIGTSTP, SIG_IGN);
293 (void) signal (SIGTTIN, SIG_IGN);
294 (void) signal (SIGTTOU, SIG_IGN);
296 if ((pid_t)-1 == child) {
297 /* error in fork() */
298 fprintf (stderr, _("%s: failure forking: %s\n"),
299 is_newgrp ? "newgrp" : "sg", strerror (errno));
302 snprintf (audit_buf, sizeof(audit_buf),
303 "changing new-group=%s", group);
304 audit_logger (AUDIT_CHGRP_ID, Prog,
306 (unsigned int) getuid (), 0);
308 audit_logger (AUDIT_CHGRP_ID, Prog,
310 (unsigned int) getuid (), 0);
314 } else if (child != 0) {
315 /* parent - wait for child to finish, then log session close */
317 gid_t gid = getgid();
318 struct group *grp = getgrgid (gid);
322 pid = waitpid (child, &cst, WUNTRACED);
323 if ((pid == child) && (WIFSTOPPED (cst) != 0)) {
324 /* stop when child stops */
325 kill (getpid (), WSTOPSIG(cst));
326 /* wake child when resumed */
327 kill (child, SIGCONT);
329 } while ( ((pid == child) && (WIFSTOPPED (cst) != 0))
330 || ((pid != child) && (errno == EINTR)));
331 /* local, no need for xgetgrgid */
334 "user '%s' (login '%s' on %s) returned to group '%s'",
335 name, loginname, tty, grp->gr_name));
338 "user '%s' (login '%s' on %s) returned to group '%lu'",
339 name, loginname, tty,
340 (unsigned long) gid));
341 /* Either the user's passwd entry has a
342 * GID that does not match with any group,
343 * or the group was deleted while the user
344 * was in a newgrp session.*/
346 "unknown GID '%lu' used by user '%s'",
347 (unsigned long) gid, name));
350 exit ((0 != WIFEXITED (cst)) ? WEXITSTATUS (cst)
351 : WTERMSIG (cst) + 128);
354 /* child - restore signals to their default state */
355 (void) signal (SIGINT, SIG_DFL);
356 (void) signal (SIGQUIT, SIG_DFL);
357 (void) signal (SIGHUP, SIG_DFL);
358 (void) signal (SIGTSTP, SIG_DFL);
359 (void) signal (SIGTTIN, SIG_DFL);
360 (void) signal (SIGTTOU, SIG_DFL);
364 #endif /* USE_SYSLOG */
367 * newgrp - change the invokers current real and effective group id
369 int main (int argc, char **argv)
371 bool initflag = false;
377 const char *name, *prog;
379 char *command = NULL;
380 char **envp = environ;
382 /*@null@*/struct group *grp;
391 (void) setlocale (LC_ALL, "");
392 (void) bindtextdomain (PACKAGE, LOCALEDIR);
393 (void) textdomain (PACKAGE);
396 * Save my name for error messages and save my real gid incase of
397 * errors. If there is an error i have to exec a new login shell for
398 * the user since her old shell won't have fork'd to create the
399 * process. Skip over the program name to the next command line
402 * This historical comment, and the code itself, suggest that the
403 * behavior of the system/shell on which it was written differed
404 * significantly from the one I am using. If this process was
405 * started from a shell (including the login shell), it was fork'ed
406 * and exec'ed as a child by that shell. In order to get the user
407 * back to that shell, it is only necessary to exit from this
408 * process which terminates the child of the fork. The parent shell,
409 * which is blocked waiting for a signal, will then receive a
410 * SIGCHLD and will continue; any changes made to the process
411 * persona or the environment after the fork never occurred in the
414 * Bottom line: we want to save the name and real gid for messages,
415 * but we do not need to restore the previous process persona and we
416 * don't need to re-exec anything. -- JWP
418 Prog = Basename (argv[0]);
419 is_newgrp = (strcmp (Prog, "newgrp") == 0);
420 OPENLOG (is_newgrp ? "newgrp" : "sg");
427 pwd = get_my_pwent ();
429 fprintf (stderr, _("%s: Cannot determine your user name.\n"),
432 audit_logger (AUDIT_CHGRP_ID, Prog,
434 (unsigned int) getuid (), 0);
436 SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
437 (unsigned long) getuid ()));
444 * Parse the command line. There are two accepted flags. The first
445 * is "-", which for newgrp means to re-create the entire
446 * environment as though a login had been performed, and "-c", which
447 * for sg causes a command string to be executed.
449 * The next argument, if present, must be the new group name. Any
450 * remaining remaining arguments will be used to execute a command
451 * as the named group. If the group name isn't present, I just use
452 * the login group ID of the current user.
454 * The valid syntax are
455 * newgrp [-] [groupid]
456 * newgrp [-l] [groupid]
458 * sg [-] groupid [[-c command]
461 && ( (strcmp (argv[0], "-") == 0)
462 || (strcmp (argv[0], "-l") == 0))) {
469 * Do the command line for everything that is
472 if ((argc > 0) && (argv[0][0] != '-')) {
484 * skip -c if specified so both forms work:
485 * "sg group -c command" (as in the man page) or
486 * "sg group command" (as in the usage message).
488 if ((argc > 1) && (strcmp (argv[0], "-c") == 0)) {
497 * Do the command line for "newgrp". It's just making sure
498 * there aren't any flags and getting the new group name.
500 if ((argc > 0) && (argv[0][0] == '-')) {
503 } else if (argv[0] != (char *) 0) {
507 * get the group file entry for her login group id.
508 * the entry must exist, simply to be annoying.
510 * Perhaps in the past, but the default behavior now depends on the
511 * group entry, so it had better exist. -- JWP
513 grp = xgetgrgid (pwd->pw_gid);
516 _("%s: GID '%lu' does not exist\n"),
517 Prog, (unsigned long) pwd->pw_gid);
518 SYSLOG ((LOG_CRIT, "GID '%lu' does not exist",
519 (unsigned long) pwd->pw_gid));
522 group = grp->gr_name;
527 #ifdef HAVE_SETGROUPS
529 * get the current users groupset. The new group will be added to
530 * the concurrent groupset if there is room, otherwise you get a
531 * nasty message but at least your real and effective group id's are
534 /* don't use getgroups(0, 0) - it doesn't work on some systems */
537 grouplist = (GETGROUPS_T *) xmalloc (i * sizeof (GETGROUPS_T));
538 ngroups = getgroups (i, grouplist);
539 if (i > ngroups && !(ngroups == -1 && errno == EINVAL)) {
542 /* not enough room, so try allocating a larger buffer */
547 perror ("getgroups");
550 snprintf (audit_buf, sizeof(audit_buf),
551 "changing new-group=%s", group);
552 audit_logger (AUDIT_CHGRP_ID, Prog,
554 (unsigned int) getuid (), 0);
556 audit_logger (AUDIT_CHGRP_ID, Prog,
558 (unsigned int) getuid (), 0);
563 #endif /* HAVE_SETGROUPS */
566 * now we put her in the new group. The password file entry for her
567 * current user id has been gotten. If there was no optional group
568 * argument she will have her real and effective group id set to the
569 * set to the value from her password file entry.
571 * If run as newgrp, or as sg with no command, this process exec's
572 * an interactive subshell with the effective GID of the new group.
573 * If run as sg with a command, that command is exec'ed in this
574 * subshell. When this process terminates, either because the user
575 * exits, or the command completes, the parent of this process
576 * resumes with the current GID.
578 * If a group is explicitly specified on the command line, the
579 * interactive shell or command is run with that effective GID.
580 * Access will be denied if no entry for that group can be found in
581 * /etc/group. If the current user name appears in the members list
582 * for that group, access will be granted immediately; if not, the
583 * user will be challenged for that group's password. If the
584 * password response is incorrect, if the specified group does not
585 * have a password, or if that group has been locked by gpasswd -R,
586 * access will be denied. This is true even if the group specified
587 * has the user's login GID (as shown in /etc/passwd). If no group
588 * is explicitly specified on the command line, the effect is
589 * exactly the same as if a group name matching the user's login GID
590 * had been explicitly specified. Root, however, is never
591 * challenged for passwords, and is always allowed access.
593 * The previous behavior was to allow access to the login group if
594 * no explicit group was specified, irrespective of the group
595 * control file(s). This behavior is usually not desirable. A user
596 * wishing to return to the login group has only to exit back to the
597 * login shell. Generating yet more shell levels in order to
598 * provide a convenient "return" to the default group has the
599 * undesirable side effects of confusing the user, scrambling the
600 * history file, and consuming system resources. The default now is
601 * to lock out such behavior. A sys admin can allow it by explicitly
602 * including the user's name in the member list of the user's login
605 grp = getgrnam (group); /* local, no need for xgetgrnam */
607 fprintf (stderr, _("%s: group '%s' does not exist\n"), Prog, group);
612 * For splitted groups (due to limitations of NIS), check all
613 * groups of the same GID like the requested group for
614 * membership of the current user.
616 grp = find_matching_group (name, grp->gr_gid);
619 * No matching group found. As we already know that
620 * the group exists, this happens only in the case
621 * of a requested group where the user is not member.
623 * Re-read the group entry for further processing.
625 grp = xgetgrnam (group);
626 assert (NULL != grp);
629 sgrp = getsgnam (group);
631 grp->gr_passwd = sgrp->sg_passwd;
632 grp->gr_mem = sgrp->sg_mem;
637 * Check if the user is allowed to access this group.
639 check_perms (grp, pwd, group);
642 * all successful validations pass through this point. The group id
643 * will be set, and the group added to the concurrent groupset.
646 if (getdef_bool ("SYSLOG_SG_ENAB")) {
647 syslog_sg (name, group);
649 #endif /* USE_SYSLOG */
653 #ifdef HAVE_SETGROUPS
655 * I am going to try to add her new group id to her concurrent group
656 * set. If the group id is already present i'll just skip this part.
657 * If the group doesn't fit, i'll complain loudly and skip this
660 for (i = 0; i < ngroups; i++) {
661 if (gid == grouplist[i]) {
666 if (ngroups >= sysconf (_SC_NGROUPS_MAX)) {
667 (void) fputs (_("too many groups\n"), stderr);
669 grouplist[ngroups++] = gid;
670 if (setgroups (ngroups, grouplist) != 0) {
671 perror ("setgroups");
678 * Close all files before changing the user/group IDs.
680 * The needed structure should have been copied before, or
681 * permission to read the database will be required.
691 * Set the effective GID to the new group id and the effective UID
692 * to the real UID. For root, this also sets the real GID to the
695 if (setgid (gid) != 0) {
698 snprintf (audit_buf, sizeof(audit_buf),
699 "changing new-gid=%lu", (unsigned long) gid);
700 audit_logger (AUDIT_CHGRP_ID, Prog,
702 (unsigned int) getuid (), 0);
707 if (setuid (getuid ()) != 0) {
710 snprintf (audit_buf, sizeof(audit_buf),
711 "changing new-gid=%lu", (unsigned long) gid);
712 audit_logger (AUDIT_CHGRP_ID, Prog,
714 (unsigned int) getuid (), 0);
720 * See if the "-c" flag was used. If it was, i just create a shell
721 * command for her using the argument that followed the "-c" flag.
725 execl (SHELL, "sh", "-c", command, (char *) 0);
727 snprintf (audit_buf, sizeof(audit_buf),
728 "changing new-gid=%lu", (unsigned long) gid);
729 audit_logger (AUDIT_CHGRP_ID, Prog,
731 (unsigned int) getuid (), 0);
734 exit ((errno == ENOENT) ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
738 * I have to get the pathname of her login shell. As a favor, i'll
739 * try her environment for a $SHELL value first, and then try the
740 * password file entry. Obviously this shouldn't be in the
741 * restricted command directory since it could be used to leave the
742 * restricted environment.
744 * Note that the following assumes this user's entry in /etc/passwd
745 * does not have a chroot * prefix. If it does, the * will be copied
746 * verbatim into the exec path. This is probably not an issue
747 * because if this user is operating in a chroot jail, her entry in
748 * the version of /etc/passwd that is accessible here should
749 * probably never have a chroot shell entry (but entries for other
750 * users might). If I have missed something, and this causes you a
751 * problem, try using $SHELL as a workaround; also please notify me
752 * at jparmele@wildbear.com -- JWP
754 cp = getenv ("SHELL");
755 if (!initflag && (NULL != cp)) {
757 } else if ((NULL != pwd->pw_shell) && ('\0' != pwd->pw_shell[0])) {
758 prog = pwd->pw_shell;
764 * Now I try to find the basename of the login shell. This will
765 * become argv[0] of the spawned command.
767 cp = Basename ((char *) prog);
770 * Switch back to her home directory if i am doing login
774 if (chdir (pwd->pw_dir) != 0) {
778 while (NULL != *envp) {
779 if (strncmp (*envp, "PATH=", 5) == 0 ||
780 strncmp (*envp, "HOME=", 5) == 0 ||
781 strncmp (*envp, "SHELL=", 6) == 0 ||
782 strncmp (*envp, "TERM=", 5) == 0)
783 addenv (*envp, NULL);
788 while (NULL != *envp) {
789 addenv (*envp, NULL);
795 snprintf (audit_buf, sizeof(audit_buf), "changing new-gid=%lu",
796 (unsigned long) gid);
797 audit_logger (AUDIT_CHGRP_ID, Prog,
799 (unsigned int) getuid (), 1);
802 * Exec the login shell and go away. We are trying to get back to
803 * the previous environment which should be the user's login shell.
805 err = shell (prog, initflag ? (char *) 0 : cp, newenvp);
806 exit ((err == ENOENT) ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
811 * The previous code, when run as newgrp, re-exec'ed the shell in
812 * the current process with the original gid on error conditions.
813 * See the comment above. This historical behavior now has the
814 * effect of creating unlogged extraneous shell layers when the
815 * command line has an error or there is an authentication failure.
816 * We now just want to exit with error status back to the parent
817 * process. The closelog is probably unnecessary, but it does no
823 snprintf (audit_buf, sizeof(audit_buf),
824 "changing new-group=%s", group);
825 audit_logger (AUDIT_CHGRP_ID, Prog,
827 (unsigned int) getuid (), 0);
829 audit_logger (AUDIT_CHGRP_ID, Prog,
831 (unsigned int) getuid (), 0);