1/* 2 * Copyright (c) 2012-2013 Todd C. Miller <Todd.Miller@courtesan.com> 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17#include <config.h> 18 19/* Large files not supported by procfs.h */ 20#if defined(HAVE_PROCFS_H) || defined(HAVE_SYS_PROCFS_H) 21# undef _FILE_OFFSET_BITS 22# undef _LARGE_FILES 23#endif 24 25#include <sys/types.h> 26#include <sys/param.h> 27#include <sys/stat.h> 28#if defined(MAJOR_IN_MKDEV) 29# include <sys/mkdev.h> 30#elif defined(MAJOR_IN_SYSMACROS) 31# include <sys/sysmacros.h> 32#endif 33#include <stdio.h> 34#ifdef STDC_HEADERS 35# include <stdlib.h> 36# include <stddef.h> 37#else 38# ifdef HAVE_STDLIB_H 39# include <stdlib.h> 40# endif 41#endif /* STDC_HEADERS */ 42#ifdef HAVE_STRING_H 43# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS) 44# include <memory.h> 45# endif 46# include <string.h> 47#endif /* HAVE_STRING_H */ 48#ifdef HAVE_STRINGS_H 49# include <strings.h> 50#endif /* HAVE_STRINGS_H */ 51#ifdef HAVE_UNISTD_H 52# include <unistd.h> 53#endif /* HAVE_UNISTD_H */ 54#include <errno.h> 55#include <fcntl.h> 56#include <limits.h> 57#ifdef HAVE_DIRENT_H 58# include <dirent.h> 59# define NAMLEN(dirent) strlen((dirent)->d_name) 60#else 61# define dirent direct 62# define NAMLEN(dirent) (dirent)->d_namlen 63# ifdef HAVE_SYS_NDIR_H 64# include <sys/ndir.h> 65# endif 66# ifdef HAVE_SYS_DIR_H 67# include <sys/dir.h> 68# endif 69# ifdef HAVE_NDIR_H 70# include <ndir.h> 71# endif 72#endif 73#if defined(HAVE_STRUCT_KINFO_PROC_P_TDEV) || defined (HAVE_STRUCT_KINFO_PROC_KP_EPROC_E_TDEV) || defined(HAVE_STRUCT_KINFO_PROC2_P_TDEV) 74# include <sys/sysctl.h> 75#elif defined(HAVE_STRUCT_KINFO_PROC_KI_TDEV) 76# include <sys/sysctl.h> 77# include <sys/user.h> 78#endif 79#if defined(HAVE_PROCFS_H) 80# include <procfs.h> 81#elif defined(HAVE_SYS_PROCFS_H) 82# include <sys/procfs.h> 83#endif 84 85#include "sudo.h" 86 87/* 88 * How to access the tty device number in struct kinfo_proc. 89 */ 90#if defined(HAVE_STRUCT_KINFO_PROC2_P_TDEV) 91# define SUDO_KERN_PROC KERN_PROC2 92# define sudo_kinfo_proc kinfo_proc2 93# define sudo_kp_tdev p_tdev 94# define sudo_kp_namelen 6 95#elif defined(HAVE_STRUCT_KINFO_PROC_P_TDEV) 96# define SUDO_KERN_PROC KERN_PROC 97# define sudo_kinfo_proc kinfo_proc 98# define sudo_kp_tdev p_tdev 99# define sudo_kp_namelen 6 100#elif defined(HAVE_STRUCT_KINFO_PROC_KI_TDEV) 101# define SUDO_KERN_PROC KERN_PROC 102# define sudo_kinfo_proc kinfo_proc 103# define sudo_kp_tdev ki_tdev 104# define sudo_kp_namelen 4 105#elif defined(HAVE_STRUCT_KINFO_PROC_KP_EPROC_E_TDEV) 106# define SUDO_KERN_PROC KERN_PROC 107# define sudo_kinfo_proc kinfo_proc 108# define sudo_kp_tdev kp_eproc.e_tdev 109# define sudo_kp_namelen 4 110#endif 111 112#if defined(sudo_kp_tdev) 113/* 114 * Like ttyname() but uses a dev_t instead of an open fd. 115 * Caller is responsible for freeing the returned string. 116 * The BSD version uses devname() 117 */ 118static char * 119sudo_ttyname_dev(dev_t tdev) 120{ 121 char *dev, *tty = NULL; 122 123 /* Some versions of devname() return NULL on failure, others do not. */ 124 dev = devname(tdev, S_IFCHR); 125 if (dev != NULL && *dev != '?' && *dev != '#') { 126 if (*dev != '/') { 127 /* devname() doesn't use the /dev/ prefix, add one... */ 128 size_t len = sizeof(_PATH_DEV) + strlen(dev); 129 tty = emalloc(len); 130 strlcpy(tty, _PATH_DEV, len); 131 strlcat(tty, dev, len); 132 } else { 133 /* Should not happen but just in case... */ 134 tty = estrdup(dev); 135 } 136 } 137 return tty; 138} 139#elif defined(HAVE__TTYNAME_DEV) 140extern char *_ttyname_dev(dev_t rdev, char *buffer, size_t buflen); 141 142/* 143 * Like ttyname() but uses a dev_t instead of an open fd. 144 * Caller is responsible for freeing the returned string. 145 * This version is just a wrapper around _ttyname_dev(). 146 */ 147static char * 148sudo_ttyname_dev(dev_t tdev) 149{ 150 char buf[TTYNAME_MAX], *tty; 151 152 tty = _ttyname_dev(tdev, buf, sizeof(buf)); 153 154 return estrdup(tty); 155} 156#else 157/* 158 * Devices to search before doing a breadth-first scan. 159 */ 160static char *search_devs[] = { 161 "/dev/console", 162 "/dev/wscons", 163 "/dev/pts/", 164 "/dev/vt/", 165 "/dev/term/", 166 "/dev/zcons/", 167 NULL 168}; 169 170static char *ignore_devs[] = { 171 "/dev/fd/", 172 "/dev/stdin", 173 "/dev/stdout", 174 "/dev/stderr", 175 NULL 176}; 177 178/* 179 * Do a breadth-first scan of dir looking for the specified device. 180 */ 181static 182char *sudo_ttyname_scan(dir, rdev, builtin) 183 const char *dir; 184 dev_t rdev; 185 int builtin; 186{ 187 DIR *d; 188 char pathbuf[PATH_MAX], **subdirs = NULL, *devname = NULL; 189 size_t sdlen, d_len, len, num_subdirs = 0, max_subdirs = 0; 190 struct dirent *dp; 191 struct stat sb; 192 int i; 193 194 if (dir[0] == '\0' || (d = opendir(dir)) == NULL) 195 goto done; 196 197 sdlen = strlen(dir); 198 if (dir[sdlen - 1] == '/') 199 sdlen--; 200 if (sdlen + 1 >= sizeof(pathbuf)) { 201 errno = ENAMETOOLONG; 202 warning("%.*s/", (int)sdlen, dir); 203 goto done; 204 } 205 memcpy(pathbuf, dir, sdlen); 206 pathbuf[sdlen++] = '/'; 207 pathbuf[sdlen] = '\0'; 208 209 while ((dp = readdir(d)) != NULL) { 210 /* Skip anything starting with "." */ 211 if (dp->d_name[0] == '.') 212 continue; 213 214 d_len = NAMLEN(dp); 215 if (sdlen + d_len >= sizeof(pathbuf)) 216 continue; 217 memcpy(&pathbuf[sdlen], dp->d_name, d_len + 1); /* copy NUL too */ 218 d_len += sdlen; 219 220 for (i = 0; ignore_devs[i] != NULL; i++) { 221 len = strlen(ignore_devs[i]); 222 if (ignore_devs[i][len - 1] == '/') 223 len--; 224 if (d_len == len && strncmp(pathbuf, ignore_devs[i], len) == 0) 225 break; 226 } 227 if (ignore_devs[i] != NULL) 228 continue; 229 if (!builtin) { 230 /* Skip entries in search_devs; we already checked them. */ 231 for (i = 0; search_devs[i] != NULL; i++) { 232 len = strlen(search_devs[i]); 233 if (search_devs[i][len - 1] == '/') 234 len--; 235 if (d_len == len && strncmp(pathbuf, search_devs[i], len) == 0) 236 break; 237 } 238 if (search_devs[i] != NULL) 239 continue; 240 } 241# if defined(HAVE_STRUCT_DIRENT_D_TYPE) && defined(DTTOIF) 242 /* Use d_type to avoid a stat() if possible. */ 243 /* Convert d_type to stat-style type bits but follow links. */ 244 if (dp->d_type != DT_LNK && dp->d_type != DT_CHR) 245 sb.st_mode = DTTOIF(dp->d_type); 246 else 247# endif 248 if (stat(pathbuf, &sb) == -1) 249 continue; 250 if (S_ISDIR(sb.st_mode)) { 251 if (!builtin) { 252 /* Add to list of subdirs to search. */ 253 if (num_subdirs + 1 > max_subdirs) { 254 max_subdirs += 64; 255 subdirs = erealloc3(subdirs, max_subdirs, sizeof(char *)); 256 } 257 subdirs[num_subdirs++] = estrdup(pathbuf); 258 } 259 continue; 260 } 261 if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) { 262 devname = estrdup(pathbuf); 263 break; 264 } 265 } 266 closedir(d); 267 268 /* Search subdirs if we didn't find it in the root level. */ 269 for (i = 0; devname == NULL && i < num_subdirs; i++) 270 devname = sudo_ttyname_scan(subdirs[i], rdev, FALSE); 271 272done: 273 for (i = 0; i < num_subdirs; i++) 274 efree(subdirs[i]); 275 efree(subdirs); 276 return devname; 277} 278 279/* 280 * Like ttyname() but uses a dev_t instead of an open fd. 281 * Caller is responsible for freeing the returned string. 282 * Generic version. 283 */ 284static char * 285sudo_ttyname_dev(rdev) 286 dev_t rdev; 287{ 288 struct stat sb; 289 size_t len; 290 char buf[PATH_MAX], **sd, *devname, *tty = NULL; 291 292 /* 293 * First check search_devs. 294 */ 295 for (sd = search_devs; (devname = *sd) != NULL; sd++) { 296 len = strlen(devname); 297 if (devname[len - 1] == '/') { 298 /* Special case /dev/pts */ 299 if (strcmp(devname, "/dev/pts/") == 0) { 300 (void)snprintf(buf, sizeof(buf), "%spts/%u", _PATH_DEV, 301 (unsigned int)minor(rdev)); 302 if (stat(buf, &sb) == 0) { 303 if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) { 304 tty = estrdup(buf); 305 break; 306 } 307 } 308 continue; 309 } 310 /* Traverse directory */ 311 tty = sudo_ttyname_scan(devname, rdev, TRUE); 312 } else { 313 if (stat(devname, &sb) == 0) { 314 if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) { 315 tty = estrdup(devname); 316 break; 317 } 318 } 319 } 320 } 321 322 /* 323 * Not found? Do a breadth-first traversal of /dev/. 324 */ 325 if (tty == NULL) 326 tty = sudo_ttyname_scan(_PATH_DEV, rdev, FALSE); 327 328 return tty; 329} 330#endif 331 332#if defined(sudo_kp_tdev) 333/* 334 * Return a string from ttyname() containing the tty to which the process is 335 * attached or NULL if there is no tty associated with the process (or its 336 * parent). First tries sysctl using the current pid, then the parent's pid. 337 * Falls back on ttyname of std{in,out,err} if that fails. 338 */ 339char * 340get_process_ttyname() 341{ 342 char *tty = NULL; 343 struct sudo_kinfo_proc *ki_proc = NULL; 344 size_t size = sizeof(*ki_proc); 345 int i, mib[6], rc; 346 347 /* 348 * Lookup tty for this process and, failing that, our parent. 349 * Even if we redirect std{in,out,err} the kernel should still know. 350 */ 351 for (i = 0; tty == NULL && i < 2; i++) { 352 mib[0] = CTL_KERN; 353 mib[1] = SUDO_KERN_PROC; 354 mib[2] = KERN_PROC_PID; 355 mib[3] = i ? (int)getppid() : (int)getpid(); 356 mib[4] = sizeof(*ki_proc); 357 mib[5] = 1; 358 do { 359 size += size / 10; 360 ki_proc = erealloc(ki_proc, size); 361 rc = sysctl(mib, sudo_kp_namelen, ki_proc, &size, NULL, 0); 362 } while (rc == -1 && errno == ENOMEM); 363 if (rc != -1) { 364 if (ki_proc->sudo_kp_tdev != (dev_t)-1) { 365 tty = sudo_ttyname_dev(ki_proc->sudo_kp_tdev); 366 } 367 } 368 } 369 efree(ki_proc); 370 371 return tty; 372} 373#elif defined(HAVE_STRUCT_PSINFO_PR_TTYDEV) 374/* 375 * Return a string from ttyname() containing the tty to which the process is 376 * attached or NULL if there is no tty associated with the process (or its 377 * parent). First tries /proc/pid/psinfo, then /proc/ppid/psinfo. 378 * Falls back on ttyname of std{in,out,err} if that fails. 379 */ 380char * 381get_process_ttyname() 382{ 383 char path[PATH_MAX], *tty = NULL; 384 struct stat sb; 385 struct psinfo psinfo; 386 ssize_t nread; 387 int i, fd; 388 389 /* Try to determine the tty from pr_ttydev in /proc/pid/psinfo. */ 390 for (i = 0; tty == NULL && i < 2; i++) { 391 (void)snprintf(path, sizeof(path), "/proc/%u/psinfo", 392 i ? (unsigned int)getppid() : (unsigned int)getpid()); 393 if ((fd = open(path, O_RDONLY, 0)) == -1) 394 continue; 395 nread = read(fd, &psinfo, sizeof(psinfo)); 396 close(fd); 397 if (nread == (ssize_t)sizeof(psinfo) && psinfo.pr_ttydev != (dev_t)-1) { 398 tty = sudo_ttyname_dev(psinfo.pr_ttydev); 399 } 400 } 401 402 return tty; 403} 404#elif defined(__linux__) 405/* 406 * Return a string from ttyname() containing the tty to which the process is 407 * attached or NULL if there is no tty associated with the process (or its 408 * parent). First tries field 7 in /proc/pid/stat, then /proc/ppid/stat. 409 * Falls back on ttyname of std{in,out,err} if that fails. 410 */ 411char * 412get_process_ttyname() 413{ 414 char *line = NULL, *tty = NULL; 415 size_t linesize = 0; 416 ssize_t len; 417 int i; 418 419 /* Try to determine the tty from tty_nr in /proc/pid/stat. */ 420 for (i = 0; tty == NULL && i < 2; i++) { 421 FILE *fp; 422 char path[PATH_MAX]; 423 (void)snprintf(path, sizeof(path), "/proc/%u/stat", 424 i ? (unsigned int)getppid() : (unsigned int)getpid()); 425 if ((fp = fopen(path, "r")) == NULL) 426 continue; 427 len = getline(&line, &linesize, fp); 428 fclose(fp); 429 if (len != -1) { 430 /* Field 7 is the tty dev (0 if no tty) */ 431 char *cp = line; 432 int field = 1; 433 while (*cp != '\0') { 434 if (*cp++ == ' ') { 435 if (++field == 7) { 436 dev_t tdev = (dev_t)atoi(cp); 437 if (tdev > 0) 438 tty = sudo_ttyname_dev(tdev); 439 break; 440 } 441 } 442 } 443 } 444 } 445 efree(line); 446 447 return tty; 448} 449#else 450/* 451 * Return a string from ttyname() containing the tty to which the process is 452 * attached or NULL if there is no tty associated with the process. 453 * parent). 454 */ 455char * 456get_process_ttyname() 457{ 458 char *tty; 459 460 if ((tty = ttyname(STDIN_FILENO)) == NULL) { 461 if ((tty = ttyname(STDOUT_FILENO)) == NULL) 462 tty = ttyname(STDERR_FILENO); 463 } 464 465 return estrdup(tty); 466} 467#endif 468