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