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