su.c revision 105362
11590Srgrimes/* 21590Srgrimes * Copyright (c) 1988, 1993, 1994 31590Srgrimes * The Regents of the University of California. All rights reserved. 497377Sdes * Copyright (c) 2002 Networks Associates Technologies, Inc. 597377Sdes * All rights reserved. 61590Srgrimes * 797377Sdes * Portions of this software were developed for the FreeBSD Project by 897377Sdes * ThinkSec AS and NAI Labs, the Security Research Division of Network 997377Sdes * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 1097377Sdes * ("CBOSS"), as part of the DARPA CHATS research program. 1197377Sdes * 121590Srgrimes * Redistribution and use in source and binary forms, with or without 131590Srgrimes * modification, are permitted provided that the following conditions 141590Srgrimes * are met: 151590Srgrimes * 1. Redistributions of source code must retain the above copyright 161590Srgrimes * notice, this list of conditions and the following disclaimer. 171590Srgrimes * 2. Redistributions in binary form must reproduce the above copyright 181590Srgrimes * notice, this list of conditions and the following disclaimer in the 191590Srgrimes * documentation and/or other materials provided with the distribution. 201590Srgrimes * 3. All advertising materials mentioning features or use of this software 211590Srgrimes * must display the following acknowledgement: 221590Srgrimes * This product includes software developed by the University of 231590Srgrimes * California, Berkeley and its contributors. 241590Srgrimes * 4. Neither the name of the University nor the names of its contributors 251590Srgrimes * may be used to endorse or promote products derived from this software 261590Srgrimes * without specific prior written permission. 271590Srgrimes * 281590Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 291590Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 301590Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 311590Srgrimes * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 321590Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 331590Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 341590Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 351590Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 361590Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 371590Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 381590Srgrimes * SUCH DAMAGE. 391590Srgrimes */ 401590Srgrimes 411590Srgrimes#ifndef lint 4214440Smarkmstatic const char copyright[] = 431590Srgrimes"@(#) Copyright (c) 1988, 1993, 1994\n\ 441590Srgrimes The Regents of the University of California. All rights reserved.\n"; 451590Srgrimes#endif /* not lint */ 461590Srgrimes 471590Srgrimes#ifndef lint 4828099Scharnier#if 0 491590Srgrimesstatic char sccsid[] = "@(#)su.c 8.3 (Berkeley) 4/2/94"; 5028099Scharnier#endif 5114440Smarkmstatic const char rcsid[] = 5250477Speter "$FreeBSD: head/usr.bin/su/su.c 105362 2002-10-17 23:32:44Z tjr $"; 531590Srgrimes#endif /* not lint */ 541590Srgrimes 551590Srgrimes#include <sys/param.h> 561590Srgrimes#include <sys/time.h> 571590Srgrimes#include <sys/resource.h> 5877220Smarkm#include <sys/wait.h> 591590Srgrimes 601590Srgrimes#include <err.h> 611590Srgrimes#include <errno.h> 621590Srgrimes#include <grp.h> 6377220Smarkm#include <libutil.h> 6477220Smarkm#include <login_cap.h> 651590Srgrimes#include <paths.h> 661590Srgrimes#include <pwd.h> 6777220Smarkm#include <signal.h> 681590Srgrimes#include <stdio.h> 691590Srgrimes#include <stdlib.h> 701590Srgrimes#include <string.h> 711590Srgrimes#include <syslog.h> 721590Srgrimes#include <unistd.h> 7321646Sdavidn 7474874Smarkm#include <security/pam_appl.h> 7591745Sdes#include <security/openpam.h> 7674874Smarkm 7781528Smarkm#define PAM_END() do { \ 7877220Smarkm int local_ret; \ 7977220Smarkm if (pamh != NULL && creds_set) { \ 8077220Smarkm local_ret = pam_setcred(pamh, PAM_DELETE_CRED); \ 8177220Smarkm if (local_ret != PAM_SUCCESS) \ 8277220Smarkm syslog(LOG_ERR, "pam_setcred: %s", \ 8377220Smarkm pam_strerror(pamh, local_ret)); \ 8477220Smarkm local_ret = pam_end(pamh, local_ret); \ 8577220Smarkm if (local_ret != PAM_SUCCESS) \ 8677220Smarkm syslog(LOG_ERR, "pam_end: %s", \ 8777220Smarkm pam_strerror(pamh, local_ret)); \ 8877220Smarkm } \ 8977220Smarkm} while (0) 9074874Smarkm 9174874Smarkm 9277220Smarkm#define PAM_SET_ITEM(what, item) do { \ 9377220Smarkm int local_ret; \ 9477220Smarkm local_ret = pam_set_item(pamh, what, item); \ 9577220Smarkm if (local_ret != PAM_SUCCESS) { \ 9677220Smarkm syslog(LOG_ERR, "pam_set_item(" #what "): %s", \ 9777220Smarkm pam_strerror(pamh, local_ret)); \ 9877220Smarkm errx(1, "pam_set_item(" #what "): %s", \ 9977220Smarkm pam_strerror(pamh, local_ret)); \ 10077220Smarkm } \ 10177220Smarkm} while (0) 1023702Spst 10377220Smarkmenum tristate { UNSET, YES, NO }; 1041590Srgrimes 10577220Smarkmstatic pam_handle_t *pamh = NULL; 10677220Smarkmstatic int creds_set = 0; 10777220Smarkmstatic char **environ_pam; 10877220Smarkm 10977220Smarkmstatic char *ontty(void); 11077220Smarkmstatic int chshell(char *); 11177220Smarkmstatic void usage(void); 11277220Smarkmstatic int export_pam_environment(void); 11377220Smarkmstatic int ok_to_export(const char *); 11477220Smarkm 11577220Smarkmextern char **environ; 11677220Smarkm 1171590Srgrimesint 11877220Smarkmmain(int argc, char *argv[]) 1191590Srgrimes{ 12077220Smarkm struct passwd *pwd; 12191745Sdes struct pam_conv conv = { openpam_ttyconv, NULL }; 12277220Smarkm enum tristate iscsh; 12377220Smarkm login_cap_t *lc; 12483373Smarkm union { 12583373Smarkm const char **a; 12683373Smarkm char * const *b; 12797377Sdes } np; 12877220Smarkm uid_t ruid; 12977220Smarkm int asme, ch, asthem, fastlogin, prio, i, setwhat, retcode, 130101446Sache statusp, child_pid, child_pgrp, ret_pid; 13189746Sdes char *username, *cleanenv, *class, shellbuf[MAXPATHLEN]; 13283373Smarkm const char *p, *user, *shell, *mytty, **nargv; 1331590Srgrimes 13498837Sdillon struct sigaction sa, sa_int, sa_quit, sa_tstp; 13598837Sdillon 13677220Smarkm shell = class = cleanenv = NULL; 13777220Smarkm asme = asthem = fastlogin = statusp = 0; 13810586Sjoerg user = "root"; 13977220Smarkm iscsh = UNSET; 14077220Smarkm 14181703Sru while ((ch = getopt(argc, argv, "-flmc:")) != -1) 14277220Smarkm switch ((char)ch) { 1431590Srgrimes case 'f': 1441590Srgrimes fastlogin = 1; 1451590Srgrimes break; 1461590Srgrimes case '-': 1471590Srgrimes case 'l': 1481590Srgrimes asme = 0; 1491590Srgrimes asthem = 1; 1501590Srgrimes break; 1511590Srgrimes case 'm': 1521590Srgrimes asme = 1; 1531590Srgrimes asthem = 0; 1541590Srgrimes break; 15530793Sguido case 'c': 15630793Sguido class = optarg; 15730793Sguido break; 1581590Srgrimes case '?': 1591590Srgrimes default: 16028099Scharnier usage(); 16128099Scharnier } 16239538Sroberto 16339538Sroberto if (optind < argc) 16410586Sjoerg user = argv[optind++]; 16510586Sjoerg 16628612Sjoerg if (user == NULL) 16728612Sjoerg usage(); 16828612Sjoerg 16977220Smarkm if (strlen(user) > MAXLOGNAME - 1) 17077220Smarkm errx(1, "username too long"); 17110586Sjoerg 17277220Smarkm nargv = malloc(sizeof(char *) * (argc + 4)); 17377220Smarkm if (nargv == NULL) 17477220Smarkm errx(1, "malloc failure"); 17577220Smarkm 17610586Sjoerg nargv[argc + 3] = NULL; 17710586Sjoerg for (i = argc; i >= optind; i--) 17877220Smarkm nargv[i + 3] = argv[i]; 17983373Smarkm np.a = &nargv[i + 3]; 18010586Sjoerg 1811590Srgrimes argv += optind; 1821590Srgrimes 1831590Srgrimes errno = 0; 1841590Srgrimes prio = getpriority(PRIO_PROCESS, 0); 1851590Srgrimes if (errno) 1861590Srgrimes prio = 0; 18777220Smarkm 18877220Smarkm setpriority(PRIO_PROCESS, 0, -2); 18974874Smarkm openlog("su", LOG_CONS, LOG_AUTH); 1901590Srgrimes 19177220Smarkm /* get current login name, real uid and shell */ 1921590Srgrimes ruid = getuid(); 1931590Srgrimes username = getlogin(); 19477220Smarkm pwd = getpwnam(username); 19577220Smarkm if (username == NULL || pwd == NULL || pwd->pw_uid != ruid) 1961590Srgrimes pwd = getpwuid(ruid); 1971590Srgrimes if (pwd == NULL) 1981590Srgrimes errx(1, "who are you?"); 19977220Smarkm 2001590Srgrimes username = strdup(pwd->pw_name); 2011590Srgrimes if (username == NULL) 20277220Smarkm err(1, "strdup failure"); 20377220Smarkm 20421646Sdavidn if (asme) { 20521646Sdavidn if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') { 20677220Smarkm /* must copy - pwd memory is recycled */ 20777220Smarkm shell = strncpy(shellbuf, pwd->pw_shell, 20877220Smarkm sizeof(shellbuf)); 20977220Smarkm shellbuf[sizeof(shellbuf) - 1] = '\0'; 21077220Smarkm } 21177220Smarkm else { 2121590Srgrimes shell = _PATH_BSHELL; 2131590Srgrimes iscsh = NO; 2141590Srgrimes } 21521646Sdavidn } 2161590Srgrimes 21777220Smarkm /* Do the whole PAM startup thing */ 21874874Smarkm retcode = pam_start("su", user, &conv, &pamh); 21974874Smarkm if (retcode != PAM_SUCCESS) { 22074874Smarkm syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode)); 22174874Smarkm errx(1, "pam_start: %s", pam_strerror(pamh, retcode)); 22274874Smarkm } 22374874Smarkm 22481529Smarkm PAM_SET_ITEM(PAM_RUSER, getlogin()); 22581529Smarkm 22674874Smarkm mytty = ttyname(STDERR_FILENO); 22774874Smarkm if (!mytty) 22874874Smarkm mytty = "tty"; 22977220Smarkm PAM_SET_ITEM(PAM_TTY, mytty); 23077220Smarkm 23177220Smarkm retcode = pam_authenticate(pamh, 0); 23274874Smarkm if (retcode != PAM_SUCCESS) { 23377220Smarkm syslog(LOG_ERR, "pam_authenticate: %s", 23477220Smarkm pam_strerror(pamh, retcode)); 23577220Smarkm errx(1, "Sorry"); 23674874Smarkm } 23777220Smarkm retcode = pam_get_item(pamh, PAM_USER, (const void **)&p); 23877220Smarkm if (retcode == PAM_SUCCESS) 23977220Smarkm user = p; 24077220Smarkm else 24177220Smarkm syslog(LOG_ERR, "pam_get_item(PAM_USER): %s", 24277220Smarkm pam_strerror(pamh, retcode)); 24374874Smarkm 24477220Smarkm retcode = pam_acct_mgmt(pamh, 0); 24577220Smarkm if (retcode == PAM_NEW_AUTHTOK_REQD) { 24677220Smarkm retcode = pam_chauthtok(pamh, 24777220Smarkm PAM_CHANGE_EXPIRED_AUTHTOK); 24874874Smarkm if (retcode != PAM_SUCCESS) { 24977220Smarkm syslog(LOG_ERR, "pam_chauthtok: %s", 25077220Smarkm pam_strerror(pamh, retcode)); 25174874Smarkm errx(1, "Sorry"); 25274874Smarkm } 25374874Smarkm } 25477220Smarkm if (retcode != PAM_SUCCESS) { 25577220Smarkm syslog(LOG_ERR, "pam_acct_mgmt: %s", 25677220Smarkm pam_strerror(pamh, retcode)); 25777220Smarkm errx(1, "Sorry"); 25877220Smarkm } 25974874Smarkm 2601590Srgrimes /* get target login information, default to root */ 26177220Smarkm pwd = getpwnam(user); 26277220Smarkm if (pwd == NULL) 2639502Swollman errx(1, "unknown login: %s", user); 26477220Smarkm if (class == NULL) 26530793Sguido lc = login_getpwclass(pwd); 26677220Smarkm else { 26777220Smarkm if (ruid != 0) 26830793Sguido errx(1, "only root may use -c"); 26930793Sguido lc = login_getclass(class); 27030793Sguido if (lc == NULL) 27130793Sguido errx(1, "unknown class: %s", class); 27230793Sguido } 2731590Srgrimes 27477220Smarkm /* if asme and non-standard target shell, must be root */ 2751590Srgrimes if (asme) { 27677220Smarkm if (ruid != 0 && !chshell(pwd->pw_shell)) 2771590Srgrimes errx(1, "permission denied (shell)."); 27877220Smarkm } 27977220Smarkm else if (pwd->pw_shell && *pwd->pw_shell) { 2801590Srgrimes shell = pwd->pw_shell; 2811590Srgrimes iscsh = UNSET; 28277220Smarkm } 28377220Smarkm else { 2841590Srgrimes shell = _PATH_BSHELL; 2851590Srgrimes iscsh = NO; 2861590Srgrimes } 2871590Srgrimes 2881590Srgrimes /* if we're forking a csh, we want to slightly muck the args */ 2891590Srgrimes if (iscsh == UNSET) { 29014440Smarkm p = strrchr(shell, '/'); 29114440Smarkm if (p) 2921590Srgrimes ++p; 2931590Srgrimes else 2941590Srgrimes p = shell; 29577220Smarkm iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES; 2961590Srgrimes } 29777220Smarkm setpriority(PRIO_PROCESS, 0, prio); 2981590Srgrimes 29921646Sdavidn /* 30077220Smarkm * PAM modules might add supplementary groups in pam_setcred(), so 30177220Smarkm * initialize them first. 30274874Smarkm */ 30374874Smarkm if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0) 30474874Smarkm err(1, "setusercontext"); 30574874Smarkm 30674874Smarkm retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED); 30777220Smarkm if (retcode != PAM_SUCCESS) 30877220Smarkm syslog(LOG_ERR, "pam_setcred(pamh, PAM_ESTABLISH_CRED): %s", 30977220Smarkm pam_strerror(pamh, retcode)); 31077220Smarkm else 31177220Smarkm creds_set = 1; 31274874Smarkm 31374874Smarkm /* 31474874Smarkm * We must fork() before setuid() because we need to call 31574874Smarkm * pam_setcred(pamh, PAM_DELETE_CRED) as root. 31674874Smarkm */ 31798837Sdillon sa.sa_flags = SA_RESTART; 318105362Stjr sa.sa_handler = SIG_IGN; 31998837Sdillon sigemptyset(&sa.sa_mask); 32098837Sdillon sigaction(SIGINT, &sa, &sa_int); 32198837Sdillon sigaction(SIGQUIT, &sa, &sa_quit); 32298837Sdillon sigaction(SIGTSTP, &sa, &sa_tstp); 32374874Smarkm 32474874Smarkm statusp = 1; 32577220Smarkm child_pid = fork(); 32677220Smarkm switch (child_pid) { 32774874Smarkm default: 32877220Smarkm while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) { 32977220Smarkm if (WIFSTOPPED(statusp)) { 33077220Smarkm kill(getpid(), SIGSTOP); 331101722Sache child_pgrp = getpgid(child_pid); 332101749Sache if (tcgetpgrp(1) == getpgrp()) { 333101722Sache tcsetpgrp(1, child_pgrp); 334101722Sache kill(child_pid, SIGCONT); 335101722Sache } 33677220Smarkm statusp = 1; 33777220Smarkm continue; 33877220Smarkm } 33977220Smarkm break; 34074874Smarkm } 34177220Smarkm if (ret_pid == -1) 34277220Smarkm err(1, "waitpid"); 34381528Smarkm PAM_END(); 34477220Smarkm exit(statusp); 34574874Smarkm case -1: 34677220Smarkm err(1, "fork"); 34781528Smarkm PAM_END(); 34877220Smarkm exit(1); 34974874Smarkm case 0: 35098837Sdillon sigaction(SIGINT, &sa_int, NULL); 35198837Sdillon sigaction(SIGQUIT, &sa_quit, NULL); 35298837Sdillon sigaction(SIGTSTP, &sa_tstp, NULL); 35377220Smarkm /* 35477220Smarkm * Set all user context except for: Environmental variables 35577220Smarkm * Umask Login records (wtmp, etc) Path 35677220Smarkm */ 35777220Smarkm setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK | 35877220Smarkm LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP); 35977220Smarkm /* 36077220Smarkm * Don't touch resource/priority settings if -m has been used 36177220Smarkm * or -l and -c hasn't, and we're not su'ing to root. 36277220Smarkm */ 36377220Smarkm if ((asme || (!asthem && class == NULL)) && pwd->pw_uid) 36477220Smarkm setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES); 36577220Smarkm if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0) 36677220Smarkm err(1, "setusercontext"); 36774874Smarkm 36877220Smarkm if (!asme) { 36977220Smarkm if (asthem) { 37077220Smarkm p = getenv("TERM"); 37177220Smarkm environ = &cleanenv; 37269427Srwatson 37377220Smarkm /* 37477220Smarkm * Add any environmental variables that the 37577220Smarkm * PAM modules may have set. 37677220Smarkm */ 37777220Smarkm environ_pam = pam_getenvlist(pamh); 37877220Smarkm if (environ_pam) 37977220Smarkm export_pam_environment(); 3801590Srgrimes 38177220Smarkm /* set the su'd user's environment & umask */ 38277220Smarkm setusercontext(lc, pwd, pwd->pw_uid, 38377220Smarkm LOGIN_SETPATH | LOGIN_SETUMASK | 38477220Smarkm LOGIN_SETENV); 38577220Smarkm if (p) 38677220Smarkm setenv("TERM", p, 1); 38777220Smarkm if (chdir(pwd->pw_dir) < 0) 38877220Smarkm errx(1, "no directory"); 38977220Smarkm } 39077220Smarkm if (asthem || pwd->pw_uid) 39177220Smarkm setenv("USER", pwd->pw_name, 1); 39277220Smarkm setenv("HOME", pwd->pw_dir, 1); 39377220Smarkm setenv("SHELL", shell, 1); 39477220Smarkm } 39577220Smarkm login_close(lc); 39674874Smarkm 39777220Smarkm if (iscsh == YES) { 39877220Smarkm if (fastlogin) 39983373Smarkm *np.a-- = "-f"; 40077220Smarkm if (asme) 40183373Smarkm *np.a-- = "-m"; 4021590Srgrimes } 40377220Smarkm /* csh strips the first character... */ 40483373Smarkm *np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su"; 40574874Smarkm 40677220Smarkm if (ruid != 0) 40777220Smarkm syslog(LOG_NOTICE, "%s to %s%s", username, user, 40877220Smarkm ontty()); 40974874Smarkm 41083373Smarkm execv(shell, np.b); 41177220Smarkm err(1, "%s", shell); 4121590Srgrimes } 4131590Srgrimes} 4141590Srgrimes 41574874Smarkmstatic int 41677220Smarkmexport_pam_environment(void) 41774874Smarkm{ 41874874Smarkm char **pp; 41974874Smarkm 42074874Smarkm for (pp = environ_pam; *pp != NULL; pp++) { 42174874Smarkm if (ok_to_export(*pp)) 42277220Smarkm putenv(*pp); 42374874Smarkm free(*pp); 42474874Smarkm } 42574874Smarkm return PAM_SUCCESS; 42674874Smarkm} 42774874Smarkm 42874874Smarkm/* 42974874Smarkm * Sanity checks on PAM environmental variables: 43074874Smarkm * - Make sure there is an '=' in the string. 43174874Smarkm * - Make sure the string doesn't run on too long. 43274874Smarkm * - Do not export certain variables. This list was taken from the 43374874Smarkm * Solaris pam_putenv(3) man page. 43474874Smarkm */ 43574874Smarkmstatic int 43677220Smarkmok_to_export(const char *s) 43774874Smarkm{ 43874874Smarkm static const char *noexport[] = { 43974874Smarkm "SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH", 44074874Smarkm "IFS", "PATH", NULL 44174874Smarkm }; 44274874Smarkm const char **pp; 44374874Smarkm size_t n; 44474874Smarkm 44574874Smarkm if (strlen(s) > 1024 || strchr(s, '=') == NULL) 44674874Smarkm return 0; 44774874Smarkm if (strncmp(s, "LD_", 3) == 0) 44874874Smarkm return 0; 44974874Smarkm for (pp = noexport; *pp != NULL; pp++) { 45074874Smarkm n = strlen(*pp); 45174874Smarkm if (s[n] == '=' && strncmp(s, *pp, n) == 0) 45274874Smarkm return 0; 45374874Smarkm } 45474874Smarkm return 1; 45574874Smarkm} 45674874Smarkm 45728099Scharnierstatic void 45877220Smarkmusage(void) 45928099Scharnier{ 46081702Sru 46181971Smarkm fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n"); 46281702Sru exit(1); 46328099Scharnier} 46428099Scharnier 46577220Smarkmstatic int 46677220Smarkmchshell(char *sh) 4671590Srgrimes{ 46877220Smarkm int r; 4691590Srgrimes char *cp; 4701590Srgrimes 47177220Smarkm r = 0; 47221646Sdavidn setusershell(); 47377220Smarkm do { 47477220Smarkm cp = getusershell(); 47577220Smarkm r = strcmp(cp, sh); 47677220Smarkm } while (!r && cp != NULL); 47721646Sdavidn endusershell(); 47821646Sdavidn return r; 4791590Srgrimes} 4801590Srgrimes 48177220Smarkmstatic char * 48277220Smarkmontty(void) 4831590Srgrimes{ 4841590Srgrimes char *p; 4851590Srgrimes static char buf[MAXPATHLEN + 4]; 4861590Srgrimes 4871590Srgrimes buf[0] = 0; 48814440Smarkm p = ttyname(STDERR_FILENO); 48914440Smarkm if (p) 4901590Srgrimes snprintf(buf, sizeof(buf), " on %s", p); 49177220Smarkm return buf; 4921590Srgrimes} 493