/usr.bin/su/su.c

https://bitbucket.org/freebsd/freebsd-head/ · C · 640 lines · 476 code · 59 blank · 105 comment · 149 complexity · fc08b9b1c642693c154888afa404321f MD5 · raw file

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