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