Correct BSD License clause numbering from 1-2-4 to 1-2-3.
[dragonfly.git] / usr.bin / su / su.c
1 /*
2  * Copyright (c) 2002, 2005 Networks Associates Technologies, Inc.
3  * All rights reserved.
4  *
5  * Portions of this software were developed for the FreeBSD Project by
6  * ThinkSec AS and NAI Labs, the Security Research Division of Network
7  * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
8  * ("CBOSS"), as part of the DARPA CHATS research program.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 /*-
32  * Copyright (c) 1988, 1993, 1994
33  *      The Regents of the University of California.  All rights reserved.
34  *
35  * Redistribution and use in source and binary forms, with or without
36  * modification, are permitted provided that the following conditions
37  * are met:
38  * 1. Redistributions of source code must retain the above copyright
39  *    notice, this list of conditions and the following disclaimer.
40  * 2. Redistributions in binary form must reproduce the above copyright
41  *    notice, this list of conditions and the following disclaimer in the
42  *    documentation and/or other materials provided with the distribution.
43  * 3. Neither the name of the University nor the names of its contributors
44  *    may be used to endorse or promote products derived from this software
45  *    without specific prior written permission.
46  *
47  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
48  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
49  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
50  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
51  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
52  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
53  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
54  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
55  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
56  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
57  * SUCH DAMAGE.
58  *
59  * @(#)su.c     8.3 (Berkeley) 4/2/94
60  * $FreeBSD: src/usr.bin/su/su.c,v 1.88 2008/06/04 19:16:54 dwmalone Exp $
61  * $DragonFly: src/usr.bin/su/su.c,v 1.9 2006/01/12 13:43:11 corecode Exp $
62  */
63
64 #include <sys/param.h>
65 #include <sys/time.h>
66 #include <sys/resource.h>
67 #include <sys/wait.h>
68
69 #ifdef USE_BSM_AUDIT
70 #include <bsm/libbsm.h>
71 #include <bsm/audit_uevents.h>
72 #endif
73
74 #include <err.h>
75 #include <errno.h>
76 #include <grp.h>
77 #include <login_cap.h>
78 #include <paths.h>
79 #include <pwd.h>
80 #include <signal.h>
81 #include <stdio.h>
82 #include <stdlib.h>
83 #include <string.h>
84 #include <syslog.h>
85 #include <unistd.h>
86 #include <stdarg.h>
87
88 #include <security/pam_appl.h>
89 #include <security/openpam.h>
90
91 #define PAM_END() do {                                                  \
92         int local_ret;                                                  \
93         if (pamh != NULL) {                                             \
94                 local_ret = pam_setcred(pamh, PAM_DELETE_CRED);         \
95                 if (local_ret != PAM_SUCCESS)                           \
96                         syslog(LOG_ERR, "pam_setcred: %s",              \
97                                 pam_strerror(pamh, local_ret));         \
98                 if (asthem) {                                           \
99                         local_ret = pam_close_session(pamh, 0);         \
100                         if (local_ret != PAM_SUCCESS)                   \
101                                 syslog(LOG_ERR, "pam_close_session: %s",\
102                                         pam_strerror(pamh, local_ret)); \
103                 }                                                       \
104                 local_ret = pam_end(pamh, local_ret);                   \
105                 if (local_ret != PAM_SUCCESS)                           \
106                         syslog(LOG_ERR, "pam_end: %s",                  \
107                                 pam_strerror(pamh, local_ret));         \
108         }                                                               \
109 } while (0)
110
111
112 #define PAM_SET_ITEM(what, item) do {                                   \
113         int local_ret;                                                  \
114         local_ret = pam_set_item(pamh, what, item);                     \
115         if (local_ret != PAM_SUCCESS) {                                 \
116                 syslog(LOG_ERR, "pam_set_item(" #what "): %s",          \
117                         pam_strerror(pamh, local_ret));                 \
118                 errx(1, "pam_set_item(" #what "): %s",                  \
119                         pam_strerror(pamh, local_ret));                 \
120                 /* NOTREACHED */                                        \
121         }                                                               \
122 } while (0)
123
124 enum tristate { UNSET, YES, NO };
125
126 static pam_handle_t *pamh = NULL;
127 static char     **environ_pam;
128
129 static char     *ontty(void);
130 static int      chshell(const char *);
131 static void     usage(void) __dead2;
132 static void     export_pam_environment(void);
133 static int      ok_to_export(const char *);
134
135 extern char     **environ;
136
137 int
138 main(int argc, char **argv)
139 {
140         static char     *cleanenv;
141         struct passwd   *pwd;
142         struct pam_conv conv = { openpam_ttyconv, NULL };
143         enum tristate   iscsh;
144         login_cap_t     *lc;
145         union {
146                 const char      **a;
147                 char            * const *b;
148         }               np;
149         uid_t           ruid;
150         pid_t           child_pid, child_pgrp, pid;
151         int             asme, ch, asthem, fastlogin, prio, i, retcode,
152                         statusp;
153         u_int           setwhat;
154         char            *username, *class, shellbuf[MAXPATHLEN];
155         const char      *p = p, *user, *shell, *mytty, **nargv;
156         const void      *v;
157         struct sigaction sa, sa_int, sa_quit, sa_pipe;
158         int temp, fds[2];
159 #ifdef USE_BSM_AUDIT
160         const char      *aerr;
161         au_id_t          auid;
162 #endif
163
164         shell = class = cleanenv = NULL;
165         asme = asthem = fastlogin = statusp = 0;
166         user = "root";
167         iscsh = UNSET;
168
169         while ((ch = getopt(argc, argv, "-flmc:")) != -1)
170                 switch ((char)ch) {
171                 case 'f':
172                         fastlogin = 1;
173                         break;
174                 case '-':
175                 case 'l':
176                         asme = 0;
177                         asthem = 1;
178                         break;
179                 case 'm':
180                         asme = 1;
181                         asthem = 0;
182                         break;
183                 case 'c':
184                         class = optarg;
185                         break;
186                 case '?':
187                 default:
188                         usage();
189                 /* NOTREACHED */
190                 }
191
192         if (optind < argc && strcmp(argv[optind], "-") == 0) {
193                 asme = 0;
194                 asthem = 1;
195                 optind++;
196         }
197
198         if (optind < argc)
199                 user = argv[optind++];
200
201         if (user == NULL)
202                 usage();
203         /* NOTREACHED */
204
205         /*
206          * Try to provide more helpful debugging output if su(1) is running
207          * non-setuid, or was run from a file system not mounted setuid.
208          */
209         if (geteuid() != 0)
210                 errx(1, "not running setuid");
211
212 #ifdef USE_BSM_AUDIT
213         if (getauid(&auid) < 0 && errno != ENOSYS) {
214                 syslog(LOG_AUTH | LOG_ERR, "getauid: %s", strerror(errno));
215                 errx(1, "Permission denied");
216         }
217 #endif
218         if (strlen(user) > MAXLOGNAME - 1) {
219 #ifdef USE_BSM_AUDIT
220                 if (audit_submit(AUE_su, auid,
221                     1, EPERM, "username too long: '%s'", user))
222                         errx(1, "Permission denied");
223 #endif
224                 errx(1, "username too long");
225         }
226
227         nargv = malloc(sizeof(char *) * (size_t)(argc + 4));
228         if (nargv == NULL)
229                 errx(1, "malloc failure");
230
231         nargv[argc + 3] = NULL;
232         for (i = argc; i >= optind; i--)
233                 nargv[i + 3] = argv[i];
234         np.a = &nargv[i + 3];
235
236         argv += optind;
237
238         errno = 0;
239         prio = getpriority(PRIO_PROCESS, 0);
240         if (errno)
241                 prio = 0;
242
243         setpriority(PRIO_PROCESS, 0, -2);
244         openlog("su", LOG_CONS, LOG_AUTH);
245
246         /* get current login name, real uid and shell */
247         ruid = getuid();
248         username = getlogin();
249         pwd = getpwnam(username);
250         if (username == NULL || pwd == NULL || pwd->pw_uid != ruid)
251                 pwd = getpwuid(ruid);
252         if (pwd == NULL) {
253 #ifdef USE_BSM_AUDIT
254                 if (audit_submit(AUE_su, auid, 1, EPERM,
255                     "unable to determine invoking subject: '%s'", username))
256                         errx(1, "Permission denied");
257 #endif
258                 errx(1, "who are you?");
259         }
260
261         username = strdup(pwd->pw_name);
262         if (username == NULL)
263                 err(1, "strdup failure");
264
265         if (asme) {
266                 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
267                         /* must copy - pwd memory is recycled */
268                         shell = strncpy(shellbuf, pwd->pw_shell,
269                             sizeof(shellbuf));
270                         shellbuf[sizeof(shellbuf) - 1] = '\0';
271                 }
272                 else {
273                         shell = _PATH_BSHELL;
274                         iscsh = NO;
275                 }
276         }
277
278         /* Do the whole PAM startup thing */
279         retcode = pam_start("su", user, &conv, &pamh);
280         if (retcode != PAM_SUCCESS) {
281                 syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
282                 errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
283         }
284
285         PAM_SET_ITEM(PAM_RUSER, username);
286
287         mytty = ttyname(STDERR_FILENO);
288         if (!mytty)
289                 mytty = "tty";
290         PAM_SET_ITEM(PAM_TTY, mytty);
291
292         retcode = pam_authenticate(pamh, 0);
293         if (retcode != PAM_SUCCESS) {
294 #ifdef USE_BSM_AUDIT
295                 if (audit_submit(AUE_su, auid, 1, EPERM, "bad su %s to %s on %s",
296                     username, user, mytty))
297                         errx(1, "Permission denied");
298 #endif
299                 syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s on %s",
300                     username, user, mytty);
301                 errx(1, "Sorry");
302         }
303 #ifdef USE_BSM_AUDIT
304         if (audit_submit(AUE_su, auid, 0, 0, "successful authentication"))
305                 errx(1, "Permission denied");
306 #endif
307         retcode = pam_get_item(pamh, PAM_USER, &v);
308         if (retcode == PAM_SUCCESS)
309                 user = v;
310         else
311                 syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
312                     pam_strerror(pamh, retcode));
313         pwd = getpwnam(user);
314         if (pwd == NULL) {
315 #ifdef USE_BSM_AUDIT
316                 if (audit_submit(AUE_su, auid, 1, EPERM,
317                     "unknown subject: %s", user))
318                         errx(1, "Permission denied");
319 #endif
320                 errx(1, "unknown login: %s", user);
321         }
322
323         retcode = pam_acct_mgmt(pamh, 0);
324         if (retcode == PAM_NEW_AUTHTOK_REQD) {
325                 retcode = pam_chauthtok(pamh,
326                         PAM_CHANGE_EXPIRED_AUTHTOK);
327                 if (retcode != PAM_SUCCESS) {
328 #ifdef USE_BSM_AUDIT
329                         aerr = pam_strerror(pamh, retcode);
330                         if (aerr == NULL)
331                                 aerr = "Unknown PAM error";
332                         if (audit_submit(AUE_su, auid, 1, EPERM,
333                             "pam_chauthtok: %s", aerr))
334                                 errx(1, "Permission denied");
335 #endif
336                         syslog(LOG_ERR, "pam_chauthtok: %s",
337                             pam_strerror(pamh, retcode));
338                         errx(1, "Sorry");
339                 }
340         }
341         if (retcode != PAM_SUCCESS) {
342 #ifdef USE_BSM_AUDIT
343                 if (audit_submit(AUE_su, auid, 1, EPERM, "pam_acct_mgmt: %s",
344                     pam_strerror(pamh, retcode)))
345                         errx(1, "Permission denied");
346 #endif
347                 syslog(LOG_ERR, "pam_acct_mgmt: %s",
348                         pam_strerror(pamh, retcode));
349                 errx(1, "Sorry");
350         }
351
352         /* get target login information */
353         if (class == NULL)
354                 lc = login_getpwclass(pwd);
355         else {
356                 if (ruid != 0) {
357 #ifdef USE_BSM_AUDIT
358                         if (audit_submit(AUE_su, auid, 1, EPERM,
359                             "only root may use -c"))
360                                 errx(1, "Permission denied");
361 #endif
362                         errx(1, "only root may use -c");
363                 }
364                 lc = login_getclass(class);
365                 if (lc == NULL)
366                         errx(1, "unknown class: %s", class);
367         }
368
369         /* if asme and non-standard target shell, must be root */
370         if (asme) {
371                 if (ruid != 0 && !chshell(pwd->pw_shell))
372                         errx(1, "permission denied (shell)");
373         }
374         else if (pwd->pw_shell && *pwd->pw_shell) {
375                 shell = pwd->pw_shell;
376                 iscsh = UNSET;
377         }
378         else {
379                 shell = _PATH_BSHELL;
380                 iscsh = NO;
381         }
382
383         /* if we're forking a csh, we want to slightly muck the args */
384         if (iscsh == UNSET) {
385                 p = strrchr(shell, '/');
386                 if (p)
387                         ++p;
388                 else
389                         p = shell;
390                 iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES;
391         }
392         setpriority(PRIO_PROCESS, 0, prio);
393
394         /*
395          * PAM modules might add supplementary groups in pam_setcred(), so
396          * initialize them first.
397          */
398         if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
399                 err(1, "setusercontext");
400
401         retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
402         if (retcode != PAM_SUCCESS) {
403                 syslog(LOG_ERR, "pam_setcred: %s",
404                     pam_strerror(pamh, retcode));
405                 errx(1, "failed to establish credentials.");
406         }
407         if (asthem) {
408                 retcode = pam_open_session(pamh, 0);
409                 if (retcode != PAM_SUCCESS) {
410                         syslog(LOG_ERR, "pam_open_session: %s",
411                             pam_strerror(pamh, retcode));
412                         errx(1, "failed to open session.");
413                 }
414         }
415
416         /*
417          * We must fork() before setuid() because we need to call
418          * pam_setcred(pamh, PAM_DELETE_CRED) as root.
419          */
420         sa.sa_flags = SA_RESTART;
421         sa.sa_handler = SIG_IGN;
422         sigemptyset(&sa.sa_mask);
423         sigaction(SIGINT, &sa, &sa_int);
424         sigaction(SIGQUIT, &sa, &sa_quit);
425         sigaction(SIGPIPE, &sa, &sa_pipe);
426         sa.sa_handler = SIG_DFL;
427         sigaction(SIGTSTP, &sa, NULL);
428         statusp = 1;
429         if (pipe(fds) == -1) {
430                 PAM_END();
431                 err(1, "pipe");
432         }
433         child_pid = fork();
434         switch (child_pid) {
435         default:
436                 sa.sa_handler = SIG_IGN;
437                 sigaction(SIGTTOU, &sa, NULL);
438                 close(fds[0]);
439                 setpgid(child_pid, child_pid);
440                 if (tcgetpgrp(STDERR_FILENO) == getpgrp())
441                         tcsetpgrp(STDERR_FILENO, child_pid);
442                 close(fds[1]);
443                 sigaction(SIGPIPE, &sa_pipe, NULL);
444                 while ((pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
445                         if (WIFSTOPPED(statusp)) {
446                                 child_pgrp = getpgid(child_pid);
447                                 if (tcgetpgrp(STDERR_FILENO) == child_pgrp)
448                                         tcsetpgrp(STDERR_FILENO, getpgrp());
449                                 kill(getpid(), SIGSTOP);
450                                 if (tcgetpgrp(STDERR_FILENO) == getpgrp()) {
451                                         child_pgrp = getpgid(child_pid);
452                                         tcsetpgrp(STDERR_FILENO, child_pgrp);
453                                 }
454                                 kill(child_pid, SIGCONT);
455                                 statusp = 1;
456                                 continue;
457                         }
458                         break;
459                 }
460                 tcsetpgrp(STDERR_FILENO, getpgrp());
461                 if (pid == -1)
462                         err(1, "waitpid");
463                 PAM_END();
464                 exit(WEXITSTATUS(statusp));
465         case -1:
466                 PAM_END();
467                 err(1, "fork");
468         case 0:
469                 close(fds[1]);
470                 read(fds[0], &temp, 1);
471                 close(fds[0]);
472                 sigaction(SIGPIPE, &sa_pipe, NULL);
473                 sigaction(SIGINT, &sa_int, NULL);
474                 sigaction(SIGQUIT, &sa_quit, NULL);
475
476                 /*
477                  * Set all user context except for: Environmental variables
478                  * Umask Login records (wtmp, etc) Path
479                  * XXX Missing LOGIN_SETMAC
480                  */
481                 setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
482                            LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP);
483 #if 0
484                 /*
485                  * If -s is present, also set the MAC label.
486                  */
487                 if (setmaclabel)
488                         setwhat |= LOGIN_SETMAC;
489 #endif
490                 /*
491                  * Don't touch resource/priority settings if -m has been used
492                  * or -l and -c hasn't, and we're not su'ing to root.
493                  */
494                 if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
495                         setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
496                 if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
497                         err(1, "setusercontext");
498
499                 if (!asme) {
500                         if (asthem) {
501                                 p = getenv("TERM");
502                                 environ = &cleanenv;
503                         }
504
505                         if (asthem || pwd->pw_uid) {
506                                 if (setenv("USER", pwd->pw_name, 1) == -1) {
507                                         err(1, "setenv: cannot set USER=%s",
508                                             pwd->pw_name);
509                                 }
510                         }
511                         if (setenv("HOME", pwd->pw_dir, 1) == -1) {
512                                 err(1, "setenv: cannot set HOME=%s",
513                                     pwd->pw_dir);
514                         }
515                         if (setenv("SHELL", shell, 1) == -1)
516                                 err(1, "setenv: cannot set SHELL=%s", shell);
517
518                         if (asthem) {
519                                 /*
520                                  * Add any environmental variables that the
521                                  * PAM modules may have set.
522                                  */
523                                 environ_pam = pam_getenvlist(pamh);
524                                 if (environ_pam)
525                                         export_pam_environment();
526
527                                 /* set the su'd user's environment & umask */
528                                 setusercontext(lc, pwd, pwd->pw_uid,
529                                         LOGIN_SETPATH | LOGIN_SETUMASK |
530                                         LOGIN_SETENV);
531                                 if (p) {
532                                         if (setenv("TERM", p, 1) == -1) {
533                                                 err(1,
534                                                     "setenv: cannot set TERM=%s",
535                                                     p);
536                                         }
537                                 }
538
539                                 p = pam_getenv(pamh, "HOME");
540                                 if (chdir(p ? p : pwd->pw_dir) < 0)
541                                         errx(1, "no directory");
542                         }
543                 }
544                 login_close(lc);
545
546                 if (iscsh == YES) {
547                         if (fastlogin)
548                                 *np.a-- = "-f";
549                         if (asme)
550                                 *np.a-- = "-m";
551                 }
552                 /* csh strips the first character... */
553                 *np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
554
555                 if (ruid != 0)
556                         syslog(LOG_NOTICE, "%s to %s%s", username, user,
557                             ontty());
558
559                 execv(shell, np.b);
560                 err(1, "%s", shell);
561         }
562 }
563
564 static void
565 export_pam_environment(void)
566 {
567         char    **pp;
568         char    *p;
569
570         for (pp = environ_pam; *pp != NULL; pp++) {
571                 if (ok_to_export(*pp)) {
572                         p = strchr(*pp, '=');
573                         *p = '\0';
574                         if (setenv(*pp, p + 1, 1) == -1)
575                                 err(1, "setenv: cannot set %s=%s", *pp, p + 1);
576                 }
577                 free(*pp);
578         }
579 }
580
581 /*
582  * Sanity checks on PAM environmental variables:
583  * - Make sure there is an '=' in the string.
584  * - Make sure the string doesn't run on too long.
585  * - Do not export certain variables.  This list was taken from the
586  *   Solaris pam_putenv(3) man page.
587  * Note that if the user is chrooted, PAM may have a better idea than we
588  * do of where her home directory is.
589  */
590 static int
591 ok_to_export(const char *s)
592 {
593         static const char *noexport[] = {
594                 "SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
595                 "IFS", "PATH", NULL
596         };
597         const char **pp;
598         size_t n;
599
600         if (strlen(s) > 1024 || strchr(s, '=') == NULL)
601                 return 0;
602         if (strncmp(s, "LD_", 3) == 0)
603                 return 0;
604         for (pp = noexport; *pp != NULL; pp++) {
605                 n = strlen(*pp);
606                 if (s[n] == '=' && strncmp(s, *pp, n) == 0)
607                         return 0;
608         }
609         return 1;
610 }
611
612 static void
613 usage(void)
614 {
615
616         fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n");
617         exit(1);
618         /* NOTREACHED */
619 }
620
621 static int
622 chshell(const char *sh)
623 {
624         int r;
625         char *cp;
626
627         r = 0;
628         setusershell();
629         while ((cp = getusershell()) != NULL && !r)
630             r = (strcmp(cp, sh) == 0);
631         endusershell();
632         return r;
633 }
634
635 static char *
636 ontty(void)
637 {
638         char *p;
639         static char buf[MAXPATHLEN + 4];
640
641         buf[0] = 0;
642         p = ttyname(STDERR_FILENO);
643         if (p)
644                 snprintf(buf, sizeof(buf), " on %s", p);
645         return buf;
646 }