]> granicus.if.org Git - sudo/commitdiff
Add a new "devsearch" Path setting to sudo.conf for configuring the
authorTodd C. Miller <Todd.Miller@courtesan.com>
Tue, 30 May 2017 16:44:11 +0000 (10:44 -0600)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Tue, 30 May 2017 16:44:11 +0000 (10:44 -0600)
/dev paths to traverse instead of hard-coding a list in ttyname.c
The default value can be set at configure time.

INSTALL
configure [changed mode: 0644->0755]
configure.ac
doc/sudo.conf.cat
doc/sudo.conf.man.in
doc/sudo.conf.mdoc.in
include/sudo_conf.h
lib/util/sudo_conf.c
lib/util/util.exp.in
pathnames.h.in
src/ttyname.c

diff --git a/INSTALL b/INSTALL
index c674ac71a140538cacc25bcb5c9958589e6656d7..e1d3ba658602b3d1d8875c1bb34a54b9937a40fd 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -368,6 +368,12 @@ Operating system-specific options:
        Enable the creation of an Ubuntu-style admin flag file
        the first time sudo is run.
 
+  --enable-devsearch=PATH
+        Set a system-specific search path of directories to look in
+        for device nodes.  Sudo uses this when mapping the process's
+       tty device number to a device name.  The default value is:
+            /dev/pts:/dev/vt:/dev/term:/dev/zcons:/dev/pty:/dev
+
   --with-bsm-audit
         Enable support for sudo BSM audit logs on systems that support it.
        This includes recent versions of FreeBSD, Mac OS X and Solaris.
old mode 100644 (file)
new mode 100755 (executable)
index ae9d434..35f7718
--- a/configure
+++ b/configure
@@ -722,6 +722,7 @@ timeout
 vardir
 rundir
 iolog_dir
+devsearch
 FILEDIGEST
 exampledir
 TMPFILES_D
@@ -959,6 +960,7 @@ enable_rpath
 enable_static_sudoers
 enable_shared_libutil
 enable_tmpfiles_d
+enable_devsearch
 with_selinux
 enable_gss_krb5_ccache_name
 enable_shared
@@ -1641,6 +1643,8 @@ Optional Features:
   --disable-shared-libutil
                           Disable use of the libsudo_util shared library.
   --enable-tmpfiles.d=DIR Set the path to the systemd tmpfiles.d directory.
+  --enable-devsearch=PATH The colon-delimited path to search for device nodes
+                          when determing the tty name.
   --enable-gss-krb5-ccache-name
                           Use GSS-API to set the Kerberos V cred cache name
   --enable-shared[=PKGS]  build shared libraries [default=yes]
@@ -3051,6 +3055,7 @@ $as_echo "$as_me: Configuring Sudo version $PACKAGE_VERSION" >&6;}
 
 
 
+
 
 
 #
@@ -3098,6 +3103,7 @@ pam_session=on
 pam_login_service=sudo
 PLUGINDIR=/usr/local/libexec/sudo
 FILEDIGEST=filedigest.lo
+devsearch="/dev/pts:/dev/vt:/dev/term:/dev/zcons:/dev/pty:/dev"
 #
 # End initial values for man page substitution
 #
@@ -6664,6 +6670,23 @@ else
 fi
 
 
+# Check whether --enable-devsearch was given.
+if test "${enable_devsearch+set}" = set; then :
+  enableval=$enable_devsearch; case $enableval in
+    yes)       # use default value
+               ;;
+    no)                { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Ignoring attempt to disable the device search path" >&5
+$as_echo "$as_me: WARNING: Ignoring attempt to disable the device search path" >&2;}
+               ;;
+    *)         devsearch="$enableval"
+esac
+fi
+
+cat >>confdefs.h <<EOF
+#define _PATH_SUDO_DEVSEARCH "$devsearch"
+EOF
+
+
 
 # Check whether --with-selinux was given.
 if test "${with_selinux+set}" = set; then :
index 15f0c930f34d63893fff4c1c22a8e61cebb5ae68..be36a6b6eaf5ddb51748dd3fbd0a43d82f2089a6 100644 (file)
@@ -97,6 +97,7 @@ AC_SUBST([COMPAT_EXP])
 AC_SUBST([TMPFILES_D])
 AC_SUBST([exampledir])
 AC_SUBST([FILEDIGEST])
+AC_SUBST([devsearch])
 dnl
 dnl Variables that get substituted in docs (not overridden by environment)
 dnl
@@ -185,6 +186,7 @@ pam_session=on
 pam_login_service=sudo
 PLUGINDIR=/usr/local/libexec/sudo
 FILEDIGEST=filedigest.lo
+devsearch="/dev/pts:/dev/vt:/dev/term:/dev/zcons:/dev/pty:/dev"
 #
 # End initial values for man page substitution
 #
@@ -1531,6 +1533,17 @@ esac], [
     test -f /usr/lib/tmpfiles.d/systemd.conf && TMPFILES_D=/usr/lib/tmpfiles.d
 ])
 
+AC_ARG_ENABLE(devsearch,
+[AS_HELP_STRING([--enable-devsearch=PATH], [The colon-delimited path to search for device nodes when determing the tty name.])],
+[case $enableval in
+    yes)       # use default value
+               ;;
+    no)                AC_MSG_WARN([Ignoring attempt to disable the device search path])
+               ;;
+    *)         devsearch="$enableval"
+esac])
+SUDO_DEFINE_UNQUOTED(_PATH_SUDO_DEVSEARCH, "$devsearch")
+
 AC_ARG_WITH(selinux, [AS_HELP_STRING([--with-selinux], [enable SELinux support])],
 [case $with_selinux in
     yes)       SELINUX_USAGE="[[-r role]] [[-t type]] "
index 05197d12b8a7dedc690e0173e9d5abcef3728ee2..5fe43c3e1f6e97bfa7a448826c9cc5d298c187d6 100644 (file)
@@ -106,6 +106,15 @@ D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
                _\ba_\bs_\bk_\bp_\ba_\bs_\bs may be overridden by the SUDO_ASKPASS environment
                variable.
 
+     devsearch
+               An ordered, colon-separated search path of directories to look
+               in for device nodes.  This is used when mapping the process's
+               tty device number to a device name.  Sudo will _\bn_\bo_\bt recurse into
+               subdirectories.  If terminal devices may be located in a
+               subdirectory of _\b/_\bd_\be_\bv, that path must be explicitly listed in
+               _\bd_\be_\bv_\bs_\be_\ba_\br_\bc_\bh.  The default value is:
+               /dev/pts:/dev/vt:/dev/term:/dev/zcons:/dev/pty:/dev
+
      noexec    The fully-qualified path to a shared library containing
                wrappers for the e\bex\bxe\bec\bcl\bl(), e\bex\bxe\bec\bcl\ble\be(), e\bex\bxe\bec\bcl\blp\bp(), e\bex\bxe\bec\bct\bt(), e\bex\bxe\bec\bcv\bv(),
                e\bex\bxe\bec\bcv\bve\be(), e\bex\bxe\bec\bcv\bvP\bP(), e\bex\bxe\bec\bcv\bvp\bp(), e\bex\bxe\bec\bcv\bvp\bpe\be(), f\bfe\bex\bxe\bec\bcv\bve\be(), p\bpo\bop\bpe\ben\bn(),
@@ -418,4 +427,4 @@ D\bDI\bIS\bSC\bCL\bLA\bAI\bIM\bME\bER\bR
      file distributed with s\bsu\bud\bdo\bo or https://www.sudo.ws/license.html for
      complete details.
 
-Sudo 1.8.20                    October 15, 2016                    Sudo 1.8.20
+Sudo 1.8.21                      May 30, 2017                      Sudo 1.8.21
index 88cb7cb94112a13c23533f3a3c49e60a9b555b8b..853f7ebf6e5dc1f42eb4d5c47b1bd6726a5a35a0 100644 (file)
@@ -1,7 +1,7 @@
 .\" DO NOT EDIT THIS FILE, IT IS NOT THE MASTER!
 .\" IT IS GENERATED AUTOMATICALLY FROM sudo.conf.mdoc.in
 .\"
-.\" Copyright (c) 2010-2016 Todd C. Miller <Todd.Miller@courtesan.com>
+.\" Copyright (c) 2010-2017 Todd C. Miller <Todd.Miller@courtesan.com>
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
@@ -16,7 +16,7 @@
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\" ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.TH "SUDO.CONF" "5" "October 15, 2016" "Sudo @PACKAGE_VERSION@" "File Formats Manual"
+.TH "SUDO.CONF" "5" "May 30, 2017" "Sudo @PACKAGE_VERSION@" "File Formats Manual"
 .nh
 .if n .ad l
 .SH "NAME"
@@ -238,6 +238,21 @@ may be overridden by the
 \fRSUDO_ASKPASS\fR
 environment variable.
 .TP 10n
+devsearch
+.br
+An ordered, colon-separated search path of directories to look in for
+device nodes.
+This is used when mapping the process's tty device number to a device name.
+Sudo will
+\fInot\fR
+recurse into subdirectories.
+If terminal devices may be located in a subdirectory of
+\fI/dev\fR,
+that path must be explicitly listed in
+\fIdevsearch\fR.
+The default value is:
+\fR/dev/pts:/dev/vt:/dev/term:/dev/zcons:/dev/pty:/dev\fR
+.TP 10n
 noexec
 The fully-qualified path to a shared library containing wrappers
 for the
index f10d31cd3f0d00798348b770921d089da2ef1774..013e3c03ef0139be03c2461f871ddc7a2db2e0b4 100644 (file)
@@ -1,5 +1,5 @@
 .\"
-.\" Copyright (c) 2010-2016 Todd C. Miller <Todd.Miller@courtesan.com>
+.\" Copyright (c) 2010-2017 Todd C. Miller <Todd.Miller@courtesan.com>
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
@@ -14,7 +14,7 @@
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\" ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd October 15, 2016
+.Dd May 30, 2017
 .Dt SUDO.CONF @mansectform@
 .Os Sudo @PACKAGE_VERSION@
 .Sh NAME
@@ -215,6 +215,19 @@ The value of
 may be overridden by the
 .Ev SUDO_ASKPASS
 environment variable.
+.It devsearch
+An ordered, colon-separated search path of directories to look in for
+device nodes.
+This is used when mapping the process's tty device number to a device name.
+Sudo will
+.Em not
+recurse into subdirectories.
+If terminal devices may be located in a subdirectory of
+.Pa /dev ,
+that path must be explicitly listed in
+.Em devsearch .
+The default value is:
+.Li /dev/pts:/dev/vt:/dev/term:/dev/zcons:/dev/pty:/dev
 .It noexec
 The fully-qualified path to a shared library containing wrappers
 for the
index 14fe55f16187e6e5a63d626f5403134c9f2854a4..227c7149f547a6b18d8a121d8f640b070b8e56ca 100644 (file)
@@ -59,6 +59,7 @@ __dso_public const char *sudo_conf_askpass_path_v1(void);
 __dso_public const char *sudo_conf_sesh_path_v1(void);
 __dso_public const char *sudo_conf_noexec_path_v1(void);
 __dso_public const char *sudo_conf_plugin_dir_path_v1(void);
+__dso_public const char *sudo_conf_devsearch_path_v1(void);
 __dso_public struct sudo_conf_debug_list *sudo_conf_debugging_v1(void);
 __dso_public struct sudo_conf_debug_file_list *sudo_conf_debug_files_v1(const char *progname);
 __dso_public struct plugin_info_list *sudo_conf_plugins_v1(void);
@@ -71,6 +72,7 @@ __dso_public void sudo_conf_clear_paths_v1(void);
 #define sudo_conf_sesh_path() sudo_conf_sesh_path_v1()
 #define sudo_conf_noexec_path() sudo_conf_noexec_path_v1()
 #define sudo_conf_plugin_dir_path() sudo_conf_plugin_dir_path_v1()
+#define sudo_conf_devsearch_path() sudo_conf_devsearch_path_v1()
 #define sudo_conf_debugging() sudo_conf_debugging_v1()
 #define sudo_conf_debug_files(_a) sudo_conf_debug_files_v1((_a))
 #define sudo_conf_plugins() sudo_conf_plugins_v1()
index 0b88badacde48742bb534f7bab65752598359d7e..6b24182375db0f052d4ce0b664b5a4cec1675daf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2016 Todd C. Miller <Todd.Miller@courtesan.com>
+ * Copyright (c) 2009-2017 Todd C. Miller <Todd.Miller@courtesan.com>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -99,6 +99,7 @@ static struct sudo_conf_table sudo_conf_var_table[] = {
 #define SUDO_CONF_PATH_SESH            1
 #define SUDO_CONF_PATH_NOEXEC          2
 #define SUDO_CONF_PATH_PLUGIN_DIR      3
+#define SUDO_CONF_PATH_DEVSEARCH       4
 
 static struct sudo_conf_data {
     bool disable_coredump;
@@ -107,7 +108,7 @@ static struct sudo_conf_data {
     int max_groups;
     struct sudo_conf_debug_list debugging;
     struct plugin_info_list plugins;
-    struct sudo_conf_path_table path_table[5];
+    struct sudo_conf_path_table path_table[6];
 } sudo_conf_data = {
     true,
     true,
@@ -120,6 +121,7 @@ static struct sudo_conf_data {
        { "sesh", sizeof("sesh") - 1, false, _PATH_SUDO_SESH },
        { "noexec", sizeof("noexec") - 1, false, _PATH_SUDO_NOEXEC },
        { "plugin_dir", sizeof("plugin_dir") - 1, false, _PATH_SUDO_PLUGIN_DIR },
+       { "devsearch", sizeof("devsearch") - 1, false, _PATH_SUDO_DEVSEARCH },
        { NULL }
     }
 };
@@ -451,6 +453,12 @@ sudo_conf_plugin_dir_path_v1(void)
     return sudo_conf_data.path_table[SUDO_CONF_PATH_PLUGIN_DIR].pval;
 }
 
+const char *
+sudo_conf_devsearch_path_v1(void)
+{
+    return sudo_conf_data.path_table[SUDO_CONF_PATH_DEVSEARCH].pval;
+}
+
 int
 sudo_conf_group_source_v1(void)
 {
index 1582a18f7221f3d5fee18c7e7a7a36e088f56916..3ad4d6e2f744081c61efd9c91fc05c37c9e7ad69 100644 (file)
@@ -7,6 +7,7 @@ sudo_conf_disable_coredump_v1
 sudo_conf_group_source_v1
 sudo_conf_max_groups_v1
 sudo_conf_noexec_path_v1
+sudo_conf_devsearch_path_v1
 sudo_conf_plugin_dir_path_v1
 sudo_conf_plugins_v1
 sudo_conf_probe_interfaces_v1
index 9ce3d9b07b5a591ac5e62b9f85e70fff409f3e65..7979f11296e37eb5580868b866e75ebac8e8890d 100644 (file)
 # undef _PATH_SUDO_PLUGIN_DIR
 #endif /* _PATH_SUDO_PLUGIN_DIR */
 
+#ifndef _PATH_SUDO_DEVSEARCH
+# undef _PATH_SUDO_DEVSEARCH
+#endif /* _PATH_SUDO_DEVSEARCH */
+
 #ifndef _PATH_VI
 # undef _PATH_VI
 #endif /* _PATH_VI */
index 355c886068ac23a45577c274c1f1b3359dbc5cbe..f21d4411c1357b025d1b472a24ff29b3941a6412 100644 (file)
 
 #include "sudo.h"
 
-#if defined(HAVE_STRUCT_DIRENT_D_NAMLEN) && HAVE_STRUCT_DIRENT_D_NAMLEN
-# define NAMLEN(dirent)        (dirent)->d_namlen
-#else
-# define NAMLEN(dirent)        strlen((dirent)->d_name)
-#endif
-
 /*
  * How to access the tty device number in struct kinfo_proc.
  */
@@ -145,22 +139,9 @@ sudo_ttyname_dev(dev_t tdev, char *name, size_t namelen)
 }
 #elif defined(HAVE_STRUCT_PSINFO_PR_TTYDEV) || defined(HAVE_PSTAT_GETPROC) || defined(__linux__)
 /*
- * Device nodes and directories to search before searching all of /dev
- */
-static char *search_devs[] = {
-    "/dev/console",
-    "/dev/pts/",       /* POSIX pty */
-    "/dev/vt/",                /* Solaris virtual console */
-    "/dev/term/",      /* Solaris serial ports */
-    "/dev/zcons/",     /* Solaris zone console */
-    "/dev/pty/",       /* HP-UX old-style pty */
-    NULL
-};
-
-/*
- * Device nodes to ignore when searching all of /dev
+ * Device nodes to ignore.
  */
-static char *ignore_devs[] = {
+static const char *ignore_devs[] = {
     "/dev/stdin",
     "/dev/stdout",
     "/dev/stderr",
@@ -207,7 +188,7 @@ sudo_ttyname_scan(const char *dir, dev_t rdev, char *name, size_t namelen)
        "scanning for dev %u in %s", (unsigned int)rdev, dir);
 
     sdlen = strlen(dir);
-    if (dir[sdlen - 1] == '/')
+    while (sdlen > 0 && dir[sdlen - 1] == '/')
        sdlen--;
     if (sdlen + 1 >= sizeof(pathbuf)) {
        errno = ERANGE;
@@ -215,32 +196,33 @@ sudo_ttyname_scan(const char *dir, dev_t rdev, char *name, size_t namelen)
     }
     memcpy(pathbuf, dir, sdlen);
     pathbuf[sdlen++] = '/';
-    pathbuf[sdlen] = '\0';
 
     while ((dp = readdir(d)) != NULL) {
        struct stat sb;
-       size_t d_len, len;
 
        /* Skip anything starting with "." */
        if (dp->d_name[0] == '.')
            continue;
 
-       d_len = NAMLEN(dp);
-       if (sdlen + d_len >= sizeof(pathbuf))
+       pathbuf[sdlen] = '\0';
+       if (strlcat(pathbuf, dp->d_name, sizeof(pathbuf)) >= sizeof(pathbuf)) {
+           sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+               "%s%s is too big to fit in pathbuf", pathbuf, dp->d_name);
            continue;
-       memcpy(&pathbuf[sdlen], dp->d_name, d_len + 1); /* copy NUL too */
-       d_len += sdlen;
+       }
 
+       /* Ignore device nodes listed in ignore_devs[]. */
        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)
+           if (strcmp(pathbuf, ignore_devs[i]) == 0)
                break;
        }
-       if (ignore_devs[i] != NULL)
+       if (ignore_devs[i] != NULL) {
+           sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+               "ignoring %s", pathbuf);
            continue;
-# if defined(HAVE_STRUCT_DIRENT_D_TYPE) && defined(DTTOIF)
+       }
+
+# if defined(HAVE_STRUCT_DIRENT_D_TYPE)
        /*
         * Avoid excessive stat() calls by checking dp->d_type.
         */
@@ -248,18 +230,19 @@ sudo_ttyname_scan(const char *dir, dev_t rdev, char *name, size_t namelen)
            case DT_CHR:
            case DT_LNK:
            case DT_UNKNOWN:
-               /* Could be a character device, stat() it. */
-               if (stat(pathbuf, &sb) == -1)
-                   continue;
                break;
            default:
                /* Not a character device or link, skip it. */
+               sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
+                   "skipping non-device %s", pathbuf);
                continue;
        }
-# else
-       if (stat(pathbuf, &sb) == -1)
-           continue;
 # endif
+       if (stat(pathbuf, &sb) == -1) {
+           sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
+               "unable to stat %s", pathbuf);
+           continue;
+       }
        if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) {
            sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
                "resolved dev %u as %s", (unsigned int)rdev, pathbuf);
@@ -281,68 +264,89 @@ done:
     debug_return_str(ret);
 }
 
+static char *
+sudo_dev_check(dev_t rdev, const char *devname, char *buf, size_t buflen)
+{
+    struct stat sb;
+    debug_decl(sudo_dev_check, SUDO_DEBUG_UTIL)
+
+    if (stat(devname, &sb) == 0) {
+       if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) {
+           sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+               "comparing dev %u to %s: match!",
+               (unsigned int)rdev, devname);
+           if (strlcpy(buf, devname, buflen) < buflen)
+               debug_return_str(buf);
+           sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+               "unable to store %s, have %zu, need %zu",
+               devname, buflen, strlen(devname) + 1);
+           errno = ERANGE;
+       }
+    }
+    sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
+       "comparing dev %u to %s: no", (unsigned int)rdev, devname);
+    debug_return_str(NULL);
+}
+
 /*
  * Like ttyname() but uses a dev_t instead of an open fd.
  * Returns name on success and NULL on failure, setting errno.
  * Generic version.
  */
 static char *
-sudo_ttyname_dev(dev_t rdev, char *name, size_t namelen)
+sudo_ttyname_dev(dev_t rdev, char *buf, size_t buflen)
 {
-    char buf[PATH_MAX], **sd, *devname;
-    char *ret = NULL;
-    struct stat sb;
+    const char *devsearch, *devsearch_end;
+    char path[PATH_MAX], *ret;
+    const char *cp, *ep;
     size_t len;
     debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL)
 
     /*
-     * First check search_devs[] for common tty devices.
+     * First, check /dev/console.
+     */
+    ret = sudo_dev_check(rdev, "/dev/console", buf, buflen);
+    if (ret != NULL)
+       goto done;
+
+    /*
+     * Then check the device search path.
      */
-    for (sd = search_devs; (devname = *sd) != NULL; sd++) {
-       len = strlen(devname);
-       if (devname[len - 1] == '/') {
-           if (strcmp(devname, "/dev/pts/") == 0) {
-               /* Special case /dev/pts */
-               (void)snprintf(buf, sizeof(buf), "%spts/%u", _PATH_DEV,
+    devsearch = sudo_conf_devsearch_path();
+    devsearch_end = devsearch + strlen(devsearch);
+    for (cp = sudo_strsplit(devsearch, devsearch_end, ":", &ep);
+       cp != NULL; cp = sudo_strsplit(NULL, devsearch_end, ":", &ep)) {
+
+       len = (size_t)(ep - cp);
+       if (len >= sizeof(path)) {
+           sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+               "devsearch entry %.*s too long", (int)len, cp);
+           continue;
+       }
+       memcpy(path, cp, len);
+       path[len] = '\0';
+
+       if (strcmp(path, "/dev/pts") == 0) {
+           /* Special case /dev/pts */
+           len = (size_t)snprintf(path, sizeof(path), "/dev/pts/%u",
+               (unsigned int)minor(rdev));
+           if (len >= sizeof(path)) {
+               sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+                   "devsearch entry /dev/pts/%u too long",
                    (unsigned int)minor(rdev));
-               if (stat(buf, &sb) == 0) {
-                   if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) {
-                       sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
-                           "comparing dev %u to %s: match!",
-                           (unsigned int)rdev, buf);
-                       if (strlcpy(name, buf, namelen) < namelen)
-                           ret = name;
-                       else
-                           errno = ERANGE;
-                       goto done;
-                   }
-               }
-               sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
-                   "comparing dev %u to %s: no", (unsigned int)rdev, buf);
-           } else {
-               /* Traverse directory */
-               ret = sudo_ttyname_scan(devname, rdev, name, namelen);
-               if (ret != NULL || errno == ENOMEM)
-                   goto done;
+               continue;
            }
+           ret = sudo_dev_check(rdev, path, buf, buflen);
+           if (ret != NULL)
+               goto done;
        } else {
-           if (stat(devname, &sb) == 0) {
-               if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) {
-                   if (strlcpy(name, devname, namelen) < namelen)
-                       ret = name;
-                   else
-                       errno = ERANGE;
-                   goto done;
-               }
-           }
+           /* Scan path, looking for rdev. */
+           ret = sudo_ttyname_scan(path, rdev, buf, buflen);
+           if (ret != NULL || errno == ENOMEM)
+               goto done;
        }
     }
 
-    /*
-     * Not found?  Check all device nodes in /dev.
-     */
-    ret = sudo_ttyname_scan(_PATH_DEV, rdev, name, namelen);
-
 done:
     debug_return_str(ret);
 }