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