]> granicus.if.org Git - shadow/blob - src/newgrp.c
51c2045dafc601c9fc8685c35694b5c894a8f061
[shadow] / src / newgrp.c
1 /*
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
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
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.
19  *
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.
31  */
32
33 #include <config.h>
34
35 #ident "$Id$"
36
37 #include <errno.h>
38 #include <grp.h>
39 #include <pwd.h>
40 #include <stdio.h>
41 #include <assert.h>
42 #include "defines.h"
43 #include "getdef.h"
44 #include "prototypes.h"
45 #include "exitcodes.h"
46 /*
47  * Global variables
48  */
49 char *Prog;
50
51 extern char **newenvp;
52 extern char **environ;
53
54 #ifdef HAVE_SETGROUPS
55 static int ngroups;
56 static /*@null@*/ /*@only@*/GETGROUPS_T *grouplist;
57 #endif
58
59 static bool is_newgrp;
60
61 #ifdef WITH_AUDIT
62 static char audit_buf[80];
63 #endif
64
65 /* local function prototypes */
66 static void usage (void);
67 static void check_perms (const struct group *grp,
68                          struct passwd *pwd,
69                          const char *groupname);
70 static void syslog_sg (const char *name, const char *group);
71
72 /*
73  * usage - print command usage message
74  */
75 static void usage (void)
76 {
77         if (is_newgrp) {
78                 (void) fputs (_("Usage: newgrp [-] [group]\n"), stderr);
79         } else {
80                 (void) fputs (_("Usage: sg group [[-c] command]\n"), stderr);
81         }
82 }
83
84 /*
85  * find_matching_group - search all groups of a given group id for
86  *                       membership of a given username
87  */
88 static /*@null@*/struct group *find_matching_group (const char *name, gid_t gid)
89 {
90         struct group *gr;
91         char **look;
92         bool notfound = true;
93
94         setgrent ();
95         while ((gr = getgrent ()) != NULL) {
96                 if (gr->gr_gid != gid) {
97                         continue;
98                 }
99
100                 /*
101                  * A group with matching GID was found.
102                  * Test for membership of 'name'.
103                  */
104                 look = gr->gr_mem;
105                 while ((NULL != *look) && notfound) {
106                         notfound = (strcmp (*look, name) != 0);
107                         look++;
108                 }
109                 if (!notfound) {
110                         break;
111                 }
112         }
113         endgrent ();
114         return gr;
115 }
116
117 /*
118  * check_perms - check if the user is allowed to switch to this group
119  *
120  *      If needed, the user will be authenticated.
121  *
122  *      It will not return if the user could not be authenticated.
123  */
124 static void check_perms (const struct group *grp,
125                          struct passwd *pwd,
126                          const char *groupname)
127 {
128         bool needspasswd = false;
129         struct spwd *spwd;
130         char *cp;
131         const char *cpasswd;
132
133         /*
134          * see if she is a member of this group (i.e. in the list of
135          * members of the group, or if the group is her primary group).
136          *
137          * If she isn't a member, she needs to provide the group password.
138          * If there is no group password, she will be denied access
139          * anyway.
140          *
141          */
142         if (   (grp->gr_gid != pwd->pw_gid)
143             && !is_on_list (grp->gr_mem, pwd->pw_name)) {
144                 needspasswd = true;
145         }
146
147         /*
148          * If she does not have either a shadowed password, or a regular
149          * password, and the group has a password, she needs to give the
150          * group password.
151          */
152         spwd = xgetspnam (pwd->pw_name);
153         if (NULL != spwd) {
154                 pwd->pw_passwd = spwd->sp_pwdp;
155         }
156
157         if ((pwd->pw_passwd[0] == '\0') && (grp->gr_passwd[0] != '\0')) {
158                 needspasswd = true;
159         }
160
161         /*
162          * Now I see about letting her into the group she requested. If she
163          * is the root user, I'll let her in without having to prompt for
164          * the password. Otherwise I ask for a password if she flunked one
165          * of the tests above.
166          */
167         if ((getuid () != 0) && needspasswd) {
168                 /*
169                  * get the password from her, and set the salt for
170                  * the decryption from the group file.
171                  */
172                 cp = getpass (_("Password: "));
173                 if (NULL == cp) {
174                         goto failure;
175                 }
176
177                 /*
178                  * encrypt the key she gave us using the salt from the
179                  * password in the group file. The result of this encryption
180                  * must match the previously encrypted value in the file.
181                  */
182                 cpasswd = pw_encrypt (cp, grp->gr_passwd);
183                 strzero (cp);
184
185                 if (grp->gr_passwd[0] == '\0' ||
186                     strcmp (cpasswd, grp->gr_passwd) != 0) {
187 #ifdef WITH_AUDIT
188                         snprintf (audit_buf, sizeof(audit_buf),
189                                   "authentication new-gid=%lu",
190                                   (unsigned long) grp->gr_gid);
191                         audit_logger (AUDIT_GRP_AUTH, Prog,
192                                       audit_buf, NULL,
193                                       (unsigned int) getuid (), 0);
194 #endif
195                         SYSLOG ((LOG_INFO,
196                                  "Invalid password for group '%s' from '%s'",
197                                  groupname, pwd->pw_name));
198                         (void) sleep (1);
199                         (void) fputs (_("Invalid password.\n"), stderr);
200                         goto failure;
201                 }
202 #ifdef WITH_AUDIT
203                 snprintf (audit_buf, sizeof(audit_buf),
204                           "authentication new-gid=%lu",
205                           (unsigned long) grp->gr_gid);
206                 audit_logger (AUDIT_GRP_AUTH, Prog,
207                               audit_buf, NULL,
208                               (unsigned int) getuid (), 1);
209 #endif
210         }
211
212         return;
213
214 failure:
215         /* The closelog is probably unnecessary, but it does no
216          * harm.  -- JWP
217          */
218         closelog ();
219 #ifdef WITH_AUDIT
220         if (groupname) {
221                 snprintf (audit_buf, sizeof(audit_buf),
222                           "changing new-group=%s", groupname);
223                 audit_logger (AUDIT_CHGRP_ID, Prog,
224                               audit_buf, NULL,
225                               (unsigned int) getuid (), 0);
226         } else {
227                 audit_logger (AUDIT_CHGRP_ID, Prog,
228                               "changing", NULL,
229                               (unsigned int) getuid (), 0);
230         }
231 #endif
232         exit (EXIT_FAILURE);
233 }
234
235 #ifdef USE_SYSLOG
236 /*
237  * syslog_sg - log the change of group to syslog
238  *
239  *      The loggout will also be logged when the user will quit the
240  *      sg/newgrp session.
241  */
242 static void syslog_sg (const char *name, const char *group)
243 {
244         const char *loginname = getlogin ();
245         const char *tty = ttyname (0);
246
247         if (loginname != NULL) {
248                 loginname = xstrdup (loginname);
249         }
250         if (tty != NULL) {
251                 tty = xstrdup (tty);
252         }
253
254         if (loginname == NULL) {
255                 loginname = "???";
256         }
257         if (tty == NULL) {
258                 tty = "???";
259         } else if (strncmp (tty, "/dev/", 5) == 0) {
260                 tty += 5;
261         }
262         SYSLOG ((LOG_INFO,
263                  "user '%s' (login '%s' on %s) switched to group '%s'",
264                  name, loginname, tty, group));
265 #ifdef USE_PAM
266         /*
267          * We want to fork and exec the new shell in the child, leaving the
268          * parent waiting to log the session close.
269          *
270          * The parent must ignore signals generated from the console
271          * (SIGINT, SIGQUIT, SIGHUP) which might make the parent terminate
272          * before its child. When bash is exec'ed as the subshell, it
273          * generates a new process group id for itself, and consequently
274          * only SIGHUP, which is sent to all process groups in the session,
275          * can reach the parent. However, since arbitrary programs can be
276          * specified as login shells, there is no such guarantee in general.
277          * For the same reason, we must also ignore stop signals generated
278          * from the console (SIGTSTP, SIGTTIN, and SIGTTOU) in order to
279          * avoid any possibility of the parent being stopped when it
280          * receives SIGCHLD from the terminating subshell.  -- JWP
281          */
282         {
283                 pid_t child, pid;
284
285                 /* Ignore these signals. The signal handlers will later be
286                  * restored to the default handlers. */
287                 (void) signal (SIGINT, SIG_IGN);
288                 (void) signal (SIGQUIT, SIG_IGN);
289                 (void) signal (SIGHUP, SIG_IGN);
290                 (void) signal (SIGTSTP, SIG_IGN);
291                 (void) signal (SIGTTIN, SIG_IGN);
292                 (void) signal (SIGTTOU, SIG_IGN);
293                 child = fork ();
294                 if ((pid_t)-1 == child) {
295                         /* error in fork() */
296                         fprintf (stderr, _("%s: failure forking: %s\n"),
297                                  is_newgrp ? "newgrp" : "sg", strerror (errno));
298 #ifdef WITH_AUDIT
299                         if (group) {
300                                 snprintf (audit_buf, sizeof(audit_buf),
301                                           "changing new-group=%s", group);
302                                 audit_logger (AUDIT_CHGRP_ID, Prog,
303                                               audit_buf, NULL,
304                                               (unsigned int) getuid (), 0);
305                         } else {
306                                 audit_logger (AUDIT_CHGRP_ID, Prog,
307                                               "changing", NULL,
308                                               (unsigned int) getuid (), 0);
309                         }
310 #endif
311                         exit (EXIT_FAILURE);
312                 } else if (child != 0) {
313                         /* parent - wait for child to finish, then log session close */
314                         int cst = 0;
315                         gid_t gid = getgid();
316                         struct group *grp = getgrgid (gid);
317
318                         do {
319                                 errno = 0;
320                                 pid = waitpid (child, &cst, WUNTRACED);
321                                 if ((pid == child) && (WIFSTOPPED (cst) != 0)) {
322                                         /* stop when child stops */
323                                         kill (getpid (), WSTOPSIG(cst));
324                                         /* wake child when resumed */
325                                         kill (child, SIGCONT);
326                                 }
327                         } while (   ((pid == child) && (WIFSTOPPED (cst) != 0))
328                                  || ((pid != child) && (errno == EINTR)));
329                         /* local, no need for xgetgrgid */
330                         if (NULL != grp) {
331                                 SYSLOG ((LOG_INFO,
332                                          "user '%s' (login '%s' on %s) returned to group '%s'",
333                                          name, loginname, tty, grp->gr_name));
334                         } else {
335                                 SYSLOG ((LOG_INFO,
336                                          "user '%s' (login '%s' on %s) returned to group '%lu'",
337                                          name, loginname, tty,
338                                          (unsigned long) gid));
339                                 /* Either the user's passwd entry has a
340                                  * GID that does not match with any group,
341                                  * or the group was deleted while the user
342                                  * was in a newgrp session.*/
343                                 SYSLOG ((LOG_WARN,
344                                          "unknown GID '%lu' used by user '%s'",
345                                          (unsigned long) gid, name));
346                         }
347                         closelog ();
348                         exit (EXIT_SUCCESS);
349                 }
350
351                 /* child - restore signals to their default state */
352                 (void) signal (SIGINT, SIG_DFL);
353                 (void) signal (SIGQUIT, SIG_DFL);
354                 (void) signal (SIGHUP, SIG_DFL);
355                 (void) signal (SIGTSTP, SIG_DFL);
356                 (void) signal (SIGTTIN, SIG_DFL);
357                 (void) signal (SIGTTOU, SIG_DFL);
358         }
359 #endif                          /* USE_PAM */
360 }
361 #endif                          /* USE_SYSLOG */
362
363 /*
364  * newgrp - change the invokers current real and effective group id
365  */
366 int main (int argc, char **argv)
367 {
368         bool initflag = false;
369         int i;
370         bool cflag = false;
371         int err = 0;
372         gid_t gid;
373         char *cp;
374         const char *name, *prog;
375         char *group = NULL;
376         char *command = NULL;
377         char **envp = environ;
378         struct passwd *pwd;
379         /*@null@*/struct group *grp;
380
381 #ifdef SHADOWGRP
382         struct sgrp *sgrp;
383 #endif
384
385 #ifdef WITH_AUDIT
386         audit_help_open ();
387 #endif
388         (void) setlocale (LC_ALL, "");
389         (void) bindtextdomain (PACKAGE, LOCALEDIR);
390         (void) textdomain (PACKAGE);
391
392         /*
393          * Save my name for error messages and save my real gid incase of
394          * errors. If there is an error i have to exec a new login shell for
395          * the user since her old shell won't have fork'd to create the
396          * process. Skip over the program name to the next command line
397          * argument.
398          *
399          * This historical comment, and the code itself, suggest that the
400          * behavior of the system/shell on which it was written differed
401          * significantly from the one I am using. If this process was
402          * started from a shell (including the login shell), it was fork'ed
403          * and exec'ed as a child by that shell. In order to get the user
404          * back to that shell, it is only necessary to exit from this
405          * process which terminates the child of the fork. The parent shell,
406          * which is blocked waiting for a signal, will then receive a
407          * SIGCHLD and will continue; any changes made to the process
408          * persona or the environment after the fork never occurred in the
409          * parent process.
410          *
411          * Bottom line: we want to save the name and real gid for messages,
412          * but we do not need to restore the previous process persona and we
413          * don't need to re-exec anything.  -- JWP
414          */
415         Prog = Basename (argv[0]);
416         is_newgrp = (strcmp (Prog, "newgrp") == 0);
417         OPENLOG (is_newgrp ? "newgrp" : "sg");
418         gid = getgid ();
419         argc--;
420         argv++;
421
422         initenv ();
423
424         pwd = get_my_pwent ();
425         if (NULL == pwd) {
426                 fprintf (stderr, _("%s: Cannot determine your user name.\n"),
427                          Prog);
428 #ifdef WITH_AUDIT
429                 audit_logger (AUDIT_CHGRP_ID, Prog,
430                               "changing", NULL,
431                               (unsigned int) getuid (), 0);
432 #endif
433                 SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
434                          (unsigned long) getuid ()));
435                 closelog ();
436                 exit (EXIT_FAILURE);
437         }
438         name = pwd->pw_name;
439
440         /*
441          * Parse the command line. There are two accepted flags. The first
442          * is "-", which for newgrp means to re-create the entire
443          * environment as though a login had been performed, and "-c", which
444          * for sg causes a command string to be executed.
445          *
446          * The next argument, if present, must be the new group name. Any
447          * remaining remaining arguments will be used to execute a command
448          * as the named group. If the group name isn't present, I just use
449          * the login group ID of the current user.
450          *
451          * The valid syntax are
452          *      newgrp [-] [groupid]
453          *      newgrp [-l] [groupid]
454          *      sg [-]
455          *      sg [-] groupid [[-c command]
456          */
457         if (   (argc > 0)
458             && (   (strcmp (argv[0], "-")  == 0)
459                 || (strcmp (argv[0], "-l") == 0))) {
460                 argc--;
461                 argv++;
462                 initflag = true;
463         }
464         if (!is_newgrp) {
465                 /* 
466                  * Do the command line for everything that is
467                  * not "newgrp".
468                  */
469                 if ((argc > 0) && (argv[0][0] != '-')) {
470                         group = argv[0];
471                         argc--;
472                         argv++;
473                 } else {
474                         usage ();
475                         closelog ();
476                         exit (EXIT_FAILURE);
477                 }
478                 if (argc > 0) {
479
480                         /*
481                          * skip -c if specified so both forms work:
482                          * "sg group -c command" (as in the man page) or
483                          * "sg group command" (as in the usage message).
484                          */
485                         if ((argc > 1) && (strcmp (argv[0], "-c") == 0)) {
486                                 command = argv[1];
487                         } else {
488                                 command = argv[0];
489                         }
490                         cflag = true;
491                 }
492         } else {
493                 /*
494                  * Do the command line for "newgrp". It's just making sure
495                  * there aren't any flags and getting the new group name.
496                  */
497                 if ((argc > 0) && (argv[0][0] == '-')) {
498                         usage ();
499                         goto failure;
500                 } else if (argv[0] != (char *) 0) {
501                         group = argv[0];
502                 } else {
503                         /*
504                          * get the group file entry for her login group id. 
505                          * the entry must exist, simply to be annoying.
506                          *
507                          * Perhaps in the past, but the default behavior now depends on the
508                          * group entry, so it had better exist.  -- JWP
509                          */
510                         grp = xgetgrgid (pwd->pw_gid);
511                         if (NULL == grp) {
512                                 fprintf (stderr,
513                                          _("%s: GID '%lu' does not exist\n"),
514                                          Prog, (unsigned long) pwd->pw_gid);
515                                 SYSLOG ((LOG_CRIT, "GID '%lu' does not exist",
516                                         (unsigned long) pwd->pw_gid));
517                                 goto failure;
518                         } else {
519                                 group = grp->gr_name;
520                         }
521                 }
522         }
523
524 #ifdef HAVE_SETGROUPS
525         /*
526          * get the current users groupset. The new group will be added to
527          * the concurrent groupset if there is room, otherwise you get a
528          * nasty message but at least your real and effective group id's are
529          * set.
530          */
531         /* don't use getgroups(0, 0) - it doesn't work on some systems */
532         i = 16;
533         for (;;) {
534                 grouplist = (GETGROUPS_T *) xmalloc (i * sizeof (GETGROUPS_T));
535                 ngroups = getgroups (i, grouplist);
536                 if (i > ngroups && !(ngroups == -1 && errno == EINVAL)) {
537                         break;
538                 }
539                 /* not enough room, so try allocating a larger buffer */
540                 free (grouplist);
541                 i *= 2;
542         }
543         if (ngroups < 0) {
544                 perror ("getgroups");
545 #ifdef WITH_AUDIT
546                 if (group) {
547                         snprintf (audit_buf, sizeof(audit_buf),
548                                   "changing new-group=%s", group);
549                         audit_logger (AUDIT_CHGRP_ID, Prog,
550                                       audit_buf, NULL,
551                                       (unsigned int) getuid (), 0);
552                 } else {
553                         audit_logger (AUDIT_CHGRP_ID, Prog,
554                                       "changing", NULL,
555                                       (unsigned int) getuid (), 0);
556                 }
557 #endif
558                 exit (EXIT_FAILURE);
559         }
560 #endif                          /* HAVE_SETGROUPS */
561
562         /*
563          * now we put her in the new group. The password file entry for her
564          * current user id has been gotten. If there was no optional group
565          * argument she will have her real and effective group id set to the
566          * set to the value from her password file entry.  
567          *
568          * If run as newgrp, or as sg with no command, this process exec's
569          * an interactive subshell with the effective GID of the new group. 
570          * If run as sg with a command, that command is exec'ed in this
571          * subshell. When this process terminates, either because the user
572          * exits, or the command completes, the parent of this process
573          * resumes with the current GID.
574          *
575          * If a group is explicitly specified on the command line, the
576          * interactive shell or command is run with that effective GID. 
577          * Access will be denied if no entry for that group can be found in
578          * /etc/group. If the current user name appears in the members list
579          * for that group, access will be granted immediately; if not, the
580          * user will be challenged for that group's password. If the
581          * password response is incorrect, if the specified group does not
582          * have a password, or if that group has been locked by gpasswd -R,
583          * access will be denied. This is true even if the group specified
584          * has the user's login GID (as shown in /etc/passwd). If no group
585          * is explicitly specified on the command line, the effect is
586          * exactly the same as if a group name matching the user's login GID
587          * had been explicitly specified. Root, however, is never
588          * challenged for passwords, and is always allowed access.
589          *
590          * The previous behavior was to allow access to the login group if
591          * no explicit group was specified, irrespective of the group
592          * control file(s). This behavior is usually not desirable. A user
593          * wishing to return to the login group has only to exit back to the
594          * login shell. Generating yet more shell levels in order to
595          * provide a convenient "return" to the default group has the
596          * undesirable side effects of confusing the user, scrambling the
597          * history file, and consuming system resources. The default now is
598          * to lock out such behavior. A sys admin can allow it by explicitly
599          * including the user's name in the member list of the user's login
600          * group.  -- JWP
601          */
602         grp = getgrnam (group); /* local, no need for xgetgrnam */
603         if (NULL == grp) {
604                 fprintf (stderr, _("%s: group '%s' does not exist\n"), Prog, group);
605                 goto failure;
606         }
607
608         /*
609          * For splitted groups (due to limitations of NIS), check all 
610          * groups of the same GID like the requested group for
611          * membership of the current user.
612          */
613         grp = find_matching_group (name, grp->gr_gid);
614         if (NULL == grp) {
615                 /*
616                  * No matching group found. As we already know that
617                  * the group exists, this happens only in the case
618                  * of a requested group where the user is not member.
619                  *
620                  * Re-read the group entry for further processing.
621                  */
622                 grp = xgetgrnam (group);
623                 assert (NULL != grp);
624         }
625 #ifdef SHADOWGRP
626         sgrp = getsgnam (group);
627         if (NULL != sgrp) {
628                 grp->gr_passwd = sgrp->sg_passwd;
629                 grp->gr_mem = sgrp->sg_mem;
630         }
631 #endif
632
633         /*
634          * Check if the user is allowed to access this group.
635          */
636         check_perms (grp, pwd, group);
637
638         /*
639          * all successful validations pass through this point. The group id
640          * will be set, and the group added to the concurrent groupset.
641          */
642 #ifdef  USE_SYSLOG
643         if (getdef_bool ("SYSLOG_SG_ENAB")) {
644                 syslog_sg (name, group);
645         }
646 #endif                          /* USE_SYSLOG */
647
648         gid = grp->gr_gid;
649
650 #ifdef HAVE_SETGROUPS
651         /*
652          * I am going to try to add her new group id to her concurrent group
653          * set. If the group id is already present i'll just skip this part. 
654          * If the group doesn't fit, i'll complain loudly and skip this
655          * part.
656          */
657         for (i = 0; i < ngroups; i++) {
658                 if (gid == grouplist[i]) {
659                         break;
660                 }
661         }
662         if (i == ngroups) {
663                 if (ngroups >= sysconf (_SC_NGROUPS_MAX)) {
664                         (void) fputs (_("too many groups\n"), stderr);
665                 } else {
666                         grouplist[ngroups++] = gid;
667                         if (setgroups (ngroups, grouplist) != 0) {
668                                 perror ("setgroups");
669                         }
670                 }
671         }
672 #endif
673
674         /*
675          * Set the effective GID to the new group id and the effective UID
676          * to the real UID. For root, this also sets the real GID to the
677          * new group id.
678          */
679         if (setgid (gid) != 0) {
680                 perror ("setgid");
681 #ifdef WITH_AUDIT
682                 snprintf (audit_buf, sizeof(audit_buf),
683                           "changing new-gid=%lu", (unsigned long) gid);
684                 audit_logger (AUDIT_CHGRP_ID, Prog,
685                               audit_buf, NULL,
686                               (unsigned int) getuid (), 0);
687 #endif
688                 exit (EXIT_FAILURE);
689         }
690
691         if (setuid (getuid ()) != 0) {
692                 perror ("setuid");
693 #ifdef WITH_AUDIT
694                 snprintf (audit_buf, sizeof(audit_buf),
695                           "changing new-gid=%lu", (unsigned long) gid);
696                 audit_logger (AUDIT_CHGRP_ID, Prog,
697                               audit_buf, NULL,
698                               (unsigned int) getuid (), 0);
699 #endif
700                 exit (EXIT_FAILURE);
701         }
702
703         /*
704          * See if the "-c" flag was used. If it was, i just create a shell
705          * command for her using the argument that followed the "-c" flag.
706          */
707         if (cflag) {
708                 closelog ();
709                 execl ("/bin/sh", "sh", "-c", command, (char *) 0);
710 #ifdef WITH_AUDIT
711                 snprintf (audit_buf, sizeof(audit_buf),
712                           "changing new-gid=%lu", (unsigned long) gid);
713                 audit_logger (AUDIT_CHGRP_ID, Prog,
714                               audit_buf, NULL,
715                               (unsigned int) getuid (), 0);
716 #endif
717                 perror ("/bin/sh");
718                 exit (errno == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
719         }
720
721         /*
722          * I have to get the pathname of her login shell. As a favor, i'll
723          * try her environment for a $SHELL value first, and then try the
724          * password file entry. Obviously this shouldn't be in the
725          * restricted command directory since it could be used to leave the
726          * restricted environment.
727          *
728          * Note that the following assumes this user's entry in /etc/passwd
729          * does not have a chroot * prefix. If it does, the * will be copied
730          * verbatim into the exec path. This is probably not an issue
731          * because if this user is operating in a chroot jail, her entry in
732          * the version of /etc/passwd that is accessible here should
733          * probably never have a chroot shell entry (but entries for other
734          * users might). If I have missed something, and this causes you a
735          * problem, try using $SHELL as a workaround; also please notify me
736          * at jparmele@wildbear.com -- JWP
737          */
738         cp = getenv ("SHELL");
739         if (!initflag && (NULL != cp)) {
740                 prog = cp;
741         } else if ((NULL != pwd->pw_shell) && ('\0' != pwd->pw_shell[0])) {
742                 prog = pwd->pw_shell;
743         } else {
744                 prog = "/bin/sh";
745         }
746
747         /*
748          * Now I try to find the basename of the login shell. This will
749          * become argv[0] of the spawned command.
750          */
751         cp = Basename ((char *) prog);
752
753         endspent ();
754 #ifdef  SHADOWGRP
755         endsgent ();
756 #endif
757         endpwent ();
758         endgrent ();
759
760         /*
761          * Switch back to her home directory if i am doing login
762          * initialization.
763          */
764         if (initflag) {
765                 if (chdir (pwd->pw_dir) != 0) {
766                         perror ("chdir");
767                 }
768
769                 while (NULL != *envp) {
770                         if (strncmp (*envp, "PATH=", 5) == 0 ||
771                             strncmp (*envp, "HOME=", 5) == 0 ||
772                             strncmp (*envp, "SHELL=", 6) == 0 ||
773                             strncmp (*envp, "TERM=", 5) == 0)
774                                 addenv (*envp, NULL);
775
776                         envp++;
777                 }
778         } else {
779                 while (NULL != *envp) {
780                         addenv (*envp, NULL);
781                         envp++;
782                 }
783         }
784
785 #ifdef WITH_AUDIT
786         snprintf (audit_buf, sizeof(audit_buf), "changing new-gid=%lu",
787                   (unsigned long) gid);
788         audit_logger (AUDIT_CHGRP_ID, Prog,
789                       audit_buf, NULL,
790                       (unsigned int) getuid (), 1);
791 #endif
792         /*
793          * Exec the login shell and go away. We are trying to get back to
794          * the previous environment which should be the user's login shell.
795          */
796         err = shell (prog, initflag ? (char *) 0 : cp, newenvp);
797         exit (err == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
798         /* @notreached@ */
799       failure:
800
801         /*
802          * The previous code, when run as newgrp, re-exec'ed the shell in
803          * the current process with the original gid on error conditions. 
804          * See the comment above. This historical behavior now has the
805          * effect of creating unlogged extraneous shell layers when the
806          * command line has an error or there is an authentication failure.
807          * We now just want to exit with error status back to the parent
808          * process. The closelog is probably unnecessary, but it does no
809          * harm.  -- JWP
810          */
811         closelog ();
812 #ifdef WITH_AUDIT
813         if (NULL != group) {
814                 snprintf (audit_buf, sizeof(audit_buf),
815                           "changing new-group=%s", group);
816                 audit_logger (AUDIT_CHGRP_ID, Prog, 
817                               audit_buf, NULL,
818                               (unsigned int) getuid (), 0);
819         } else {
820                 audit_logger (AUDIT_CHGRP_ID, Prog,
821                               "changing", NULL,
822                               (unsigned int) getuid (), 0);
823         }
824 #endif
825         exit (EXIT_FAILURE);
826 }
827