]> granicus.if.org Git - sudo/commitdiff
Rototill code to determine the tty. For Linux, we now look up the
authorTodd C. Miller <Todd.Miller@courtesan.com>
Wed, 23 May 2012 16:46:39 +0000 (12:46 -0400)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Wed, 23 May 2012 16:46:39 +0000 (12:46 -0400)
tty device in /proc/pid/stat instead of trying to open /proc/pid/fd/[0-2].
The sudo_ttyname_dev() function maps the given device number to a
string.  On BSD, we can use devname().  On Solaris, _ttyname_dev()
does what we want.  For others we do a breadth-first search of /dev.

--HG--
branch : 1.7

config.h.in
configure
configure.in
ttyname.c

index e3d38cb164f8587ce4819ebaf8ca4f73de3ec7b8..652f48ce919ca609a9711346f8965d4d02a05ef7 100644 (file)
 /* Define to 1 if you have the `posix_openpt' function. */
 #undef HAVE_POSIX_OPENPT
 
+/* Define to 1 if you have the <procfs.h> header file. */
+#undef HAVE_PROCFS_H
+
 /* Define to 1 if you have the <project.h> header file. */
 #undef HAVE_PROJECT_H
 
 /* Define to 1 if `p_tdev' is a member of `struct kinfo_proc'. */
 #undef HAVE_STRUCT_KINFO_PROC_P_TDEV
 
+/* Define to 1 if `pr_ttydev' is a member of `struct psinfo'. */
+#undef HAVE_STRUCT_PSINFO_PR_TTYDEV
+
 /* Define to 1 if the system has the type `struct timespec'. */
 #undef HAVE_STRUCT_TIMESPEC
 
    */
 #undef HAVE_SYS_NDIR_H
 
+/* Define to 1 if you have the <sys/procfs.h> header file. */
+#undef HAVE_SYS_PROCFS_H
+
 /* Define to 1 if you have the <sys/select.h> header file. */
 #undef HAVE_SYS_SELECT_H
 
 /* Define to 1 if you have the `_innetgr' function. */
 #undef HAVE__INNETGR
 
+/* Define to 1 if you have the `_ttyname_dev' function. */
+#undef HAVE__TTYNAME_DEV
+
 /* Define to 1 if your crt0.o defines the __progname symbol for you. */
 #undef HAVE___PROGNAME
 
 /* The user or email address that sudo mail is sent to. */
 #undef MAILTO
 
+/* Define to 1 if `major', `minor', and `makedev' are declared in <mkdev.h>.
+   */
+#undef MAJOR_IN_MKDEV
+
+/* Define to 1 if `major', `minor', and `makedev' are declared in
+   <sysmacros.h>. */
+#undef MAJOR_IN_SYSMACROS
+
 /* The max number of chars per log file line (for line wrapping). */
 #undef MAXLOGFILELEN
 
index f8332105aff9d03aa0b0f738673c617b80d8d0da..1b80062b3379ef01114989c0ea21fca102c667df 100755 (executable)
--- a/configure
+++ b/configure
@@ -2087,6 +2087,63 @@ fi
 
 } # ac_fn_c_check_header_mongrel
 
+# ac_fn_c_check_member LINENO AGGR MEMBER VAR INCLUDES
+# ----------------------------------------------------
+# Tries to find if the field MEMBER exists in type AGGR, after including
+# INCLUDES, setting cache variable VAR accordingly.
+ac_fn_c_check_member ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2.$3" >&5
+$as_echo_n "checking for $2.$3... " >&6; }
+if eval \${$4+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$5
+int
+main ()
+{
+static $2 ac_aggr;
+if (ac_aggr.$3)
+return 0;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  eval "$4=yes"
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$5
+int
+main ()
+{
+static $2 ac_aggr;
+if (sizeof ac_aggr.$3)
+return 0;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  eval "$4=yes"
+else
+  eval "$4=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$4
+              { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_member
+
 # ac_fn_c_check_type LINENO TYPE VAR INCLUDES
 # -------------------------------------------
 # Tests whether TYPE exists after having included INCLUDES, setting cache
@@ -2319,63 +2376,6 @@ rm -f conftest.val
 
 } # ac_fn_c_compute_int
 
-# ac_fn_c_check_member LINENO AGGR MEMBER VAR INCLUDES
-# ----------------------------------------------------
-# Tries to find if the field MEMBER exists in type AGGR, after including
-# INCLUDES, setting cache variable VAR accordingly.
-ac_fn_c_check_member ()
-{
-  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2.$3" >&5
-$as_echo_n "checking for $2.$3... " >&6; }
-if eval \${$4+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-$5
-int
-main ()
-{
-static $2 ac_aggr;
-if (ac_aggr.$3)
-return 0;
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  eval "$4=yes"
-else
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-$5
-int
-main ()
-{
-static $2 ac_aggr;
-if (sizeof ac_aggr.$3)
-return 0;
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  eval "$4=yes"
-else
-  eval "$4=no"
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-eval ac_res=\$$4
-              { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
-$as_echo "$ac_res" >&6; }
-  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
-
-} # ac_fn_c_check_member
-
 # ac_fn_c_check_decl LINENO SYMBOL VAR INCLUDES
 # ---------------------------------------------
 # Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR
@@ -15161,6 +15161,56 @@ $as_echo "#define TIME_WITH_SYS_TIME 1" >>confdefs.h
 
 fi
 
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether sys/types.h defines makedev" >&5
+$as_echo_n "checking whether sys/types.h defines makedev... " >&6; }
+if ${ac_cv_header_sys_types_h_makedev+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <sys/types.h>
+int
+main ()
+{
+return makedev(0, 0);
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_header_sys_types_h_makedev=yes
+else
+  ac_cv_header_sys_types_h_makedev=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_sys_types_h_makedev" >&5
+$as_echo "$ac_cv_header_sys_types_h_makedev" >&6; }
+
+if test $ac_cv_header_sys_types_h_makedev = no; then
+ac_fn_c_check_header_mongrel "$LINENO" "sys/mkdev.h" "ac_cv_header_sys_mkdev_h" "$ac_includes_default"
+if test "x$ac_cv_header_sys_mkdev_h" = xyes; then :
+
+$as_echo "#define MAJOR_IN_MKDEV 1" >>confdefs.h
+
+fi
+
+
+
+  if test $ac_cv_header_sys_mkdev_h = no; then
+    ac_fn_c_check_header_mongrel "$LINENO" "sys/sysmacros.h" "ac_cv_header_sys_sysmacros_h" "$ac_includes_default"
+if test "x$ac_cv_header_sys_sysmacros_h" = xyes; then :
+
+$as_echo "#define MAJOR_IN_SYSMACROS 1" >>confdefs.h
+
+fi
+
+
+  fi
+fi
+
 for ac_header in malloc.h netgroup.h paths.h spawn.h utime.h sys/sockio.h sys/bsdtypes.h sys/select.h sys/stropts.h sys/sysmacros.h
 do :
   as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
 
 done
 
+for ac_header in procfs.h sys/procfs.h
+do :
+  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+  cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+ ac_fn_c_check_member "$LINENO" "struct psinfo" "pr_ttydev" "ac_cv_member_struct_psinfo_pr_ttydev" "$ac_includes_default
+#ifdef HAVE_PROCFS_H
+#include <procfs.h>
+#endif
+#ifdef HAVE_SYS_PROCFS_H
+#include <sys/procfs.h>
+#endif
+
+"
+if test "x$ac_cv_member_struct_psinfo_pr_ttydev" = xyes; then :
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_STRUCT_PSINFO_PR_TTYDEV 1
+_ACEOF
+
+for ac_func in _ttyname_dev
+do :
+  ac_fn_c_check_func "$LINENO" "_ttyname_dev" "ac_cv_func__ttyname_dev"
+if test "x$ac_cv_func__ttyname_dev" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE__TTYNAME_DEV 1
+_ACEOF
+
+fi
+done
+
+fi
+
+break
+fi
+
+done
+
 # Check whether --enable-largefile was given.
 if test "${enable_largefile+set}" = set; then :
   enableval=$enable_largefile;
index 89ea2dea218ca6577bceeb78081085e439909f6d..7407550f40363eb6d8fb600ce5617fadb138a4ab 100644 (file)
@@ -1941,7 +1941,17 @@ dnl
 AC_HEADER_STDC
 AC_HEADER_DIRENT
 AC_HEADER_TIME
+AC_HEADER_MAJOR
 AC_CHECK_HEADERS(malloc.h netgroup.h paths.h spawn.h utime.h sys/sockio.h sys/bsdtypes.h sys/select.h sys/stropts.h sys/sysmacros.h)
+AC_CHECK_HEADERS([procfs.h] [sys/procfs.h], [AC_CHECK_MEMBERS(struct psinfo.pr_ttydev, [AC_CHECK_FUNCS(_ttyname_dev)], [], [AC_INCLUDES_DEFAULT
+#ifdef HAVE_PROCFS_H
+#include <procfs.h>
+#endif
+#ifdef HAVE_SYS_PROCFS_H
+#include <sys/procfs.h>
+#endif
+])]
+break)
 dnl
 dnl Check for large file support.  HP-UX 11.23 has a broken sys/type.h
 dnl when large files support is enabled so work around it.
index e27b3f89d2a769d0f01a8b15cfe63c8ed675e557..6e6a0383102346c6344f2f70120dfc32b02b2ce1 100644 (file)
--- a/ttyname.c
+++ b/ttyname.c
 
 #include <config.h>
 
+/* Large files not supported by procfs.h */
+#if defined(HAVE_PROCFS_H) || defined(HAVE_SYS_PROCFS_H)
+# undef _FILE_OFFSET_BITS
+# undef _LARGE_FILES
+#endif
+
 #include <sys/types.h>
 #include <sys/param.h>
 #include <sys/stat.h>
+#if defined(MAJOR_IN_MKDEV)
+# include <sys/mkdev.h>
+#elif defined(MAJOR_IN_SYSMACROS)
+# include <sys/sysmacros.h>
+#endif
 #include <stdio.h>
 #ifdef STDC_HEADERS
 # include <stdlib.h>
 #endif /* HAVE_UNISTD_H */
 #include <errno.h>
 #include <fcntl.h>
+#include <limits.h>
+#ifdef HAVE_DIRENT_H
+# include <dirent.h>
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+# define dirent direct
+# define NAMLEN(dirent) (dirent)->d_namlen
+# ifdef HAVE_SYS_NDIR_H
+#  include <sys/ndir.h>
+# endif
+# ifdef HAVE_SYS_DIR_H
+#  include <sys/dir.h>
+# endif
+# ifdef HAVE_NDIR_H
+#  include <ndir.h>
+# endif
+#endif
 #if defined(HAVE_STRUCT_KINFO_PROC_P_TDEV) || defined (HAVE_STRUCT_KINFO_PROC_KP_EPROC_E_TDEV) || defined(HAVE_STRUCT_KINFO_PROC2_P_TDEV)
 # include <sys/sysctl.h>
 #elif defined(HAVE_STRUCT_KINFO_PROC_KI_TDEV)
 # include <sys/sysctl.h>
 # include <sys/user.h>
 #endif
+#if defined(HAVE_PROCFS_H)
+# include <procfs.h>
+#elif defined(HAVE_SYS_PROCFS_H)
+# include <sys/procfs.h>
+#endif
 
 #include "sudo.h"
 
 # define sudo_kp_namelen       4
 #endif
 
-#ifdef sudo_kp_tdev
+#if defined(sudo_kp_tdev)
+/*
+ * Like ttyname() but uses a dev_t instead of an open fd.
+ * Caller is responsible for freeing the returned string.
+ * The BSD version uses devname()
+ */
+static char *
+sudo_ttyname_dev(dev_t tdev)
+{
+    char *dev, *tty = NULL;
+
+    /* Some versions of devname() return NULL on failure, others do not. */
+    dev = devname(tdev, S_IFCHR);
+    if (dev != NULL && *dev != '?' && *dev != '#') {
+       if (*dev != '/') {
+           /* devname() doesn't use the /dev/ prefix, add one... */
+           size_t len = sizeof(_PATH_DEV) + strlen(dev);
+           tty = emalloc(len);
+           strlcpy(tty, _PATH_DEV, len);
+           strlcat(tty, dev, len);
+       } else {
+           /* Should not happen but just in case... */
+           tty = estrdup(dev);
+       }
+    }
+    return tty;
+}
+#elif defined(HAVE__TTYNAME_DEV)
+extern char *_ttyname_dev(dev_t rdev, char *buffer, size_t buflen);
+
+/*
+ * Like ttyname() but uses a dev_t instead of an open fd.
+ * Caller is responsible for freeing the returned string.
+ * This version is just a wrapper around _ttyname_dev().
+ */
+static char *
+sudo_ttyname_dev(dev_t tdev)
+{
+    char buf[TTYNAME_MAX], *tty;
+
+    tty = _ttyname_dev(tdev, buf, sizeof(buf));
+
+    return estrdup(tty);
+}
+#else
+/*
+ * Devices to search before doing a breadth-first scan.
+ */
+static char *search_devs[] = {
+    "/dev/console",
+    "/dev/wscons",
+    "/dev/pts/",
+    "/dev/vt/",
+    "/dev/term/",
+    "/dev/zcons/",
+    NULL
+};
+
+static char *ignore_devs[] = {
+    "/dev/fd/",
+    "/dev/stdin",
+    "/dev/stdout",
+    "/dev/stderr",
+    NULL
+};
+
+/*
+ * Do a breadth-first scan of dir looking for the specified device.
+ */
+static
+char *sudo_ttyname_scan(dir, rdev, builtin)
+    const char *dir;
+    dev_t rdev;
+    int builtin;
+{
+    DIR *d;
+    char pathbuf[PATH_MAX], **subdirs = NULL, *devname = NULL;
+    size_t sdlen, d_len, len, num_subdirs = 0, max_subdirs = 0;
+    struct dirent *dp;
+    struct stat sb;
+    int i;
+
+    if (dir[0] == '\0' || (d = opendir(dir)) == NULL)
+       goto done;
+
+    sdlen = strlen(dir);
+    if (dir[sdlen - 1] == '/')
+       sdlen--;
+    if (sdlen + 1 >= sizeof(pathbuf)) {
+       errno = ENAMETOOLONG;
+       warning("%.*s/", (int)sdlen, dir);
+       goto done;
+    }
+    memcpy(pathbuf, dir, sdlen);
+    pathbuf[sdlen++] = '/';
+    pathbuf[sdlen] = '\0';
+
+    while ((dp = readdir(d)) != NULL) {
+       /* Skip anything starting with "." */
+       if (dp->d_name[0] == '.')
+           continue;
+
+       d_len = NAMLEN(dp);
+       if (sdlen + d_len >= sizeof(pathbuf))
+           continue;
+       memcpy(&pathbuf[sdlen], dp->d_name, d_len + 1); /* copy NUL too */
+       d_len += sdlen;
+
+       for (i = 0; ignore_devs[i] != NULL; i++) {
+           len = strlen(ignore_devs[i]);
+           if (ignore_devs[i][len - 1] == '/')
+               len--;
+           if (d_len == len && strncmp(pathbuf, ignore_devs[i], len) == 0)
+               break;
+       }
+       if (ignore_devs[i] != NULL)
+           continue;
+       if (!builtin) {
+           /* Skip entries in search_devs; we already checked them. */
+           for (i = 0; search_devs[i] != NULL; i++) {
+               len = strlen(search_devs[i]);
+               if (search_devs[i][len - 1] == '/')
+                   len--;
+               if (d_len == len && strncmp(pathbuf, search_devs[i], len) == 0)
+                   break;
+           }
+           if (search_devs[i] != NULL)
+               continue;
+       }
+# if defined(HAVE_STRUCT_DIRENT_D_TYPE) && defined(DTTOIF)
+       /* Use d_type to avoid a stat() if possible. */
+       /* Convert d_type to stat-style type bits but follow links. */
+       if (dp->d_type != DT_LNK && dp->d_type != DT_CHR)
+           sb.st_mode = DTTOIF(dp->d_type);
+       else
+# endif
+       if (stat(pathbuf, &sb) == -1)
+           continue;
+       if (S_ISDIR(sb.st_mode)) {
+           if (!builtin) {
+               /* Add to list of subdirs to search. */
+               if (num_subdirs + 1 > max_subdirs) {
+                   max_subdirs += 64;
+                   subdirs = erealloc3(subdirs, max_subdirs, sizeof(char *));
+               }
+               subdirs[num_subdirs++] = estrdup(pathbuf);
+           }
+           continue;
+       }
+       if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) {
+           devname = estrdup(pathbuf);
+           break;
+       }
+    }
+    closedir(d);
+
+    /* Search subdirs if we didn't find it in the root level. */
+    for (i = 0; devname == NULL && i < num_subdirs; i++)
+       devname = sudo_ttyname_scan(subdirs[i], rdev, false);
+
+done:
+    for (i = 0; i < num_subdirs; i++)
+       efree(subdirs[i]);
+    efree(subdirs);
+    return devname;
+}
+
+/*
+ * Like ttyname() but uses a dev_t instead of an open fd.
+ * Caller is responsible for freeing the returned string.
+ * Generic version.
+ */
+static char *
+sudo_ttyname_dev(rdev)
+    dev_t rdev;
+{
+    struct stat sb;
+    size_t len;
+    char buf[PATH_MAX], **sd, *devname, *tty = NULL;
+
+    /*
+     * First check search_devs.
+     */
+    for (sd = search_devs; (devname = *sd) != NULL; sd++) {
+       len = strlen(devname);
+       if (devname[len - 1] == '/') {
+           /* Special case /dev/pts */
+           if (strcmp(devname, "/dev/pts/") == 0) {
+               (void)snprintf(buf, sizeof(buf), "%spts/%u", _PATH_DEV,
+                   (unsigned int)minor(rdev));
+               if (stat(buf, &sb) == 0) {
+                   if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) {
+                       tty = estrdup(buf);
+                       break;
+                   }
+               }
+               continue;
+           }
+           /* Traverse directory */
+           tty = sudo_ttyname_scan(devname, rdev, 1);
+       } else {
+           if (stat(devname, &sb) == 0) {
+               if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) {
+                   tty = estrdup(devname);
+                   break;
+               }
+           }
+       }
+    }
+
+    /*
+     * Not found?  Do a breadth-first traversal of /dev/.
+     */
+    if (tty == NULL)
+       tty = sudo_ttyname_scan(_PATH_DEV, rdev, 0);
+
+    return tty;
+}
+#endif
+
+#if defined(sudo_kp_tdev)
 /*
  * Return a string from ttyname() containing the tty to which the process is
  * attached or NULL if there is no tty associated with the process (or its
@@ -108,19 +361,8 @@ get_process_ttyname()
            rc = sysctl(mib, sudo_kp_namelen, ki_proc, &size, NULL, 0);
        } while (rc == -1 && errno == ENOMEM);
        if (rc != -1) {
-           char *dev = devname(ki_proc->sudo_kp_tdev, S_IFCHR);
-           /* Some versions of devname() return NULL, others do not. */
-           if (dev != NULL && *dev != '?' && *dev != '#') {
-               if (*dev != '/') {
-                   /* devname() doesn't use the /dev/ prefix, add one... */
-                   size_t len = sizeof(_PATH_DEV) + strlen(dev);
-                   tty = emalloc(len);
-                   strlcpy(tty, _PATH_DEV, len);
-                   strlcat(tty, dev, len);
-               } else {
-                   /* Should not happen but just in case... */
-                   tty = estrdup(dev);
-               }
+           if (ki_proc->sudo_kp_tdev != (dev_t)-1) {
+               tty = sudo_ttyname_dev(ki_proc->sudo_kp_tdev);
            }
        }
     }
@@ -136,36 +378,114 @@ get_process_ttyname()
 
     return tty;
 }
-#else
+#elif defined(HAVE_STRUCT_PSINFO_PR_TTYDEV)
 /*
  * Return a string from ttyname() containing the tty to which the process is
  * attached or NULL if there is no tty associated with the process (or its
- * parent).  First tries std{in,out,err} then falls back to the parent's /proc
- * entry.  We could try following the parent all the way to pid 1 but
- * /proc/%d/status is system-specific (text on Linux, a struct on Solaris).
+ * parent).  First tries /proc/pid/psinfo, then /proc/ppid/psinfo.
+ * Falls back on ttyname of std{in,out,err} if that fails.
  */
 char *
 get_process_ttyname()
 {
     char path[PATH_MAX], *tty = NULL;
-    pid_t ppid;
+    struct stat sb;
+    struct psinfo psinfo;
+    ssize_t nread;
     int i, fd;
 
-    if ((tty = ttyname(STDIN_FILENO)) == NULL &&
-       (tty = ttyname(STDOUT_FILENO)) == NULL &&
-       (tty = ttyname(STDERR_FILENO)) == NULL) {
-       /* No tty for child, check the parent via /proc. */
-       ppid = getppid();
-       for (i = STDIN_FILENO; i < STDERR_FILENO && tty == NULL; i++) {
-           snprintf(path, sizeof(path), "/proc/%d/fd/%d", (int)ppid, i);
-           fd = open(path, O_RDONLY|O_NOCTTY, 0);
-           if (fd != -1) {
-               tty = ttyname(fd);
-               close(fd);
+    /* Try to determine the tty from pr_ttydev in /proc/pid/psinfo. */
+    for (i = 0; tty == NULL && i < 2; i++) {
+       (void)snprintf(path, sizeof(path), "/proc/%u/psinfo",
+           i ? (unsigned int)getppid() : (unsigned int)getpid());
+       if ((fd = open(path, O_RDONLY, 0)) == -1)
+           continue;
+       nread = read(fd, &psinfo, sizeof(psinfo));
+       close(fd);
+       if (nread == (ssize_t)sizeof(psinfo) && psinfo.pr_ttydev != (dev_t)-1) {
+           tty = sudo_ttyname_dev(psinfo.pr_ttydev);
+       }
+    }
+
+    /* If all else fails, fall back on ttyname(). */
+    if (tty == NULL) {
+       if ((tty = ttyname(STDIN_FILENO)) != NULL ||
+           (tty = ttyname(STDOUT_FILENO)) != NULL ||
+           (tty = ttyname(STDERR_FILENO)) != NULL)
+           tty = estrdup(tty);
+    }
+
+    return tty;
+}
+#elif defined(__linux__)
+/*
+ * Return a string from ttyname() containing the tty to which the process is
+ * attached or NULL if there is no tty associated with the process (or its
+ * parent).  First tries field 7 in /proc/pid/stat, then /proc/ppid/stat.
+ * Falls back on ttyname of std{in,out,err} if that fails.
+ */
+char *
+get_process_ttyname()
+{
+    char *line = NULL, *tty = NULL;
+    size_t linesize = 0;
+    ssize_t len;
+    int i;
+
+    /* Try to determine the tty from pr_ttydev in /proc/pid/psinfo. */
+    for (i = 0; tty == NULL && i < 2; i++) {
+       FILE *fp;
+       char path[PATH_MAX];
+       (void)snprintf(path, sizeof(path), "/proc/%u/stat",
+           i ? (unsigned int)getppid() : (unsigned int)getpid());
+       if ((fp = fopen(path, "r")) == NULL)
+           continue;
+       len = getline(&line, &linesize, fp);
+       fclose(fp);
+       if (len != -1) {
+           /* Field 7 is the tty dev (0 if no tty) */
+           char *cp = line;
+           int field = 1;
+           while (*cp != '\0') {
+               if (*cp++ == ' ') {
+                   if (++field == 7) {
+                       dev_t tdev = (dev_t)atoi(cp);
+                       if (tdev > 0)
+                           tty = sudo_ttyname_dev(tdev);
+                       break;
+                   }
+               }
            }
        }
     }
+    efree(line);
+
+    /* If all else fails, fall back on ttyname(). */
+    if (tty == NULL) {
+       if ((tty = ttyname(STDIN_FILENO)) != NULL ||
+           (tty = ttyname(STDOUT_FILENO)) != NULL ||
+           (tty = ttyname(STDERR_FILENO)) != NULL)
+           tty = estrdup(tty);
+    }
+
+    return tty;
+}
+#else
+/*
+ * Return a string from ttyname() containing the tty to which the process is
+ * attached or NULL if there is no tty associated with the process.
+ * parent).
+ */
+char *
+get_process_ttyname()
+{
+    char *tty;
+
+    if ((tty = ttyname(STDIN_FILENO)) == NULL) {
+       if ((tty = ttyname(STDOUT_FILENO)) == NULL)
+           tty = ttyname(STDERR_FILENO);
+    }
 
     return estrdup(tty);
 }
-#endif /* sudo_kp_tdev */
+#endif