1 /*
2 * SPDX-License-Identifier: ISC
3 *
4 * Copyright (c) 2012-2018 Todd C. Miller <Todd.Miller@sudo.ws>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 /*
20 * This is an open source non-commercial project. Dear PVS-Studio, please check it.
21 * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
22 */
23
24 #include <config.h>
25
26 #include <sys/types.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 #else
33 # include <sys/param.h>
34 #endif
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <limits.h>
42 #include <dirent.h>
43
44 #include "pathnames.h"
45 #include "sudo_compat.h"
46 #include "sudo_debug.h"
47 #include "sudo_conf.h"
48 #include "sudo_util.h"
49
50 #if defined(HAVE_DEVNAME)
51 /*
52 * Like ttyname() but uses a dev_t instead of an open fd.
53 * Returns name on success and NULL on failure, setting errno.
54 * The BSD version uses devname().
55 */
56 char *
sudo_ttyname_dev_v1(dev_t tdev,char * name,size_t namelen)57 sudo_ttyname_dev_v1(dev_t tdev, char *name, size_t namelen)
58 {
59 char *dev;
60 debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL);
61
62 /* Some versions of devname() return NULL on failure, others do not. */
63 dev = devname(tdev, S_IFCHR);
64 if (dev != NULL && *dev != '?' && *dev != '#') {
65 if (strlcpy(name, _PATH_DEV, namelen) < namelen &&
66 strlcat(name, dev, namelen) < namelen)
67 debug_return_str(name);
68 errno = ERANGE;
69 } else {
70 /* Not all versions of devname() set errno. */
71 errno = ENOENT;
72 }
73 debug_return_str(NULL);
74 }
75 #elif defined(HAVE__TTYNAME_DEV)
76 extern char *_ttyname_dev(dev_t rdev, char *buffer, size_t buflen);
77
78 /*
79 * Like ttyname() but uses a dev_t instead of an open fd.
80 * Returns name on success and NULL on failure, setting errno.
81 * This version is just a wrapper around _ttyname_dev().
82 */
83 char *
sudo_ttyname_dev_v1(dev_t tdev,char * name,size_t namelen)84 sudo_ttyname_dev_v1(dev_t tdev, char *name, size_t namelen)
85 {
86 int serrno = errno;
87 debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL);
88
89 /*
90 * _ttyname_dev() sets errno to ERANGE if namelen is too small
91 * but does not modify it if tdev is not found.
92 */
93 errno = ENOENT;
94 if (_ttyname_dev(tdev, name, namelen) == NULL)
95 debug_return_str(NULL);
96 errno = serrno;
97
98 debug_return_str(name);
99 }
100 #else
101 /*
102 * Device nodes to ignore.
103 */
104 static const char *ignore_devs[] = {
105 _PATH_DEV "stdin",
106 _PATH_DEV "stdout",
107 _PATH_DEV "stderr",
108 NULL
109 };
110
111 /*
112 * Do a scan of a directory looking for the specified device.
113 * Does not descend into subdirectories.
114 * Returns name on success and NULL on failure, setting errno.
115 */
116 static char *
sudo_ttyname_scan(const char * dir,dev_t rdev,char * name,size_t namelen)117 sudo_ttyname_scan(const char *dir, dev_t rdev, char *name, size_t namelen)
118 {
119 size_t sdlen;
120 char pathbuf[PATH_MAX];
121 char *ret = NULL;
122 struct dirent *dp;
123 struct stat sb;
124 unsigned int i;
125 DIR *d = NULL;
126 debug_decl(sudo_ttyname_scan, SUDO_DEBUG_UTIL);
127
128 if (dir[0] == '\0') {
129 errno = ENOENT;
130 goto done;
131 }
132 if ((d = opendir(dir)) == NULL)
133 goto done;
134
135 if (fstat(dirfd(d), &sb) == -1) {
136 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
137 "unable to fstat %s", dir);
138 goto done;
139 }
140 if ((sb.st_mode & S_IWOTH) != 0) {
141 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
142 "ignoring world-writable directory %s", dir);
143 errno = ENOENT;
144 goto done;
145 }
146
147 sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
148 "scanning for dev %u in %s", (unsigned int)rdev, dir);
149
150 sdlen = strlen(dir);
151 while (sdlen > 0 && dir[sdlen - 1] == '/')
152 sdlen--;
153 if (sdlen + 1 >= sizeof(pathbuf)) {
154 errno = ERANGE;
155 goto done;
156 }
157 memcpy(pathbuf, dir, sdlen);
158 pathbuf[sdlen++] = '/';
159
160 while ((dp = readdir(d)) != NULL) {
161 /* Skip anything starting with "." */
162 if (dp->d_name[0] == '.')
163 continue;
164
165 pathbuf[sdlen] = '\0';
166 if (strlcat(pathbuf, dp->d_name, sizeof(pathbuf)) >= sizeof(pathbuf)) {
167 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
168 "%s%s is too big to fit in pathbuf", pathbuf, dp->d_name);
169 continue;
170 }
171
172 /* Ignore device nodes listed in ignore_devs[]. */
173 for (i = 0; ignore_devs[i] != NULL; i++) {
174 if (strcmp(pathbuf, ignore_devs[i]) == 0)
175 break;
176 }
177 if (ignore_devs[i] != NULL) {
178 sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
179 "ignoring %s", pathbuf);
180 continue;
181 }
182
183 # if defined(HAVE_STRUCT_DIRENT_D_TYPE)
184 /*
185 * Avoid excessive stat() calls by checking dp->d_type.
186 */
187 switch (dp->d_type) {
188 case DT_CHR:
189 case DT_LNK:
190 case DT_UNKNOWN:
191 break;
192 default:
193 /* Not a character device or link, skip it. */
194 sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
195 "skipping non-device %s", pathbuf);
196 continue;
197 }
198 # endif
199 if (stat(pathbuf, &sb) == -1) {
200 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
201 "unable to stat %s", pathbuf);
202 continue;
203 }
204 if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) {
205 sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
206 "resolved dev %u as %s", (unsigned int)rdev, pathbuf);
207 if (strlcpy(name, pathbuf, namelen) < namelen) {
208 ret = name;
209 } else {
210 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
211 "unable to store %s, have %zu, need %zu",
212 pathbuf, namelen, strlen(pathbuf) + 1);
213 errno = ERANGE;
214 }
215 goto done;
216 }
217 }
218
219 done:
220 if (d != NULL)
221 closedir(d);
222 debug_return_str(ret);
223 }
224
225 static char *
sudo_dev_check(dev_t rdev,const char * devname,char * buf,size_t buflen)226 sudo_dev_check(dev_t rdev, const char *devname, char *buf, size_t buflen)
227 {
228 struct stat sb;
229 debug_decl(sudo_dev_check, SUDO_DEBUG_UTIL);
230
231 if (stat(devname, &sb) == 0) {
232 if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) {
233 sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
234 "comparing dev %u to %s: match!",
235 (unsigned int)rdev, devname);
236 if (strlcpy(buf, devname, buflen) < buflen)
237 debug_return_str(buf);
238 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
239 "unable to store %s, have %zu, need %zu",
240 devname, buflen, strlen(devname) + 1);
241 errno = ERANGE;
242 }
243 }
244 sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
245 "comparing dev %u to %s: no", (unsigned int)rdev, devname);
246 debug_return_str(NULL);
247 }
248
249 /*
250 * Like ttyname() but uses a dev_t instead of an open fd.
251 * Returns name on success and NULL on failure, setting errno.
252 * Generic version.
253 */
254 char *
sudo_ttyname_dev_v1(dev_t rdev,char * buf,size_t buflen)255 sudo_ttyname_dev_v1(dev_t rdev, char *buf, size_t buflen)
256 {
257 const char *devsearch, *devsearch_end;
258 char path[PATH_MAX], *ret;
259 const char *cp, *ep;
260 size_t len;
261 debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL);
262
263 /*
264 * First, check /dev/console.
265 */
266 ret = sudo_dev_check(rdev, _PATH_DEV "console", buf, buflen);
267 if (ret != NULL)
268 goto done;
269
270 /*
271 * Then check the device search path.
272 */
273 devsearch = sudo_conf_devsearch_path();
274 devsearch_end = devsearch + strlen(devsearch);
275 for (cp = sudo_strsplit(devsearch, devsearch_end, ":", &ep);
276 cp != NULL; cp = sudo_strsplit(NULL, devsearch_end, ":", &ep)) {
277
278 len = (size_t)(ep - cp);
279 if (len >= sizeof(path)) {
280 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
281 "devsearch entry %.*s too long", (int)len, cp);
282 continue;
283 }
284 memcpy(path, cp, len);
285 path[len] = '\0';
286
287 if (strcmp(path, _PATH_DEV "pts") == 0) {
288 /* Special case /dev/pts */
289 len = (size_t)snprintf(path, sizeof(path), "%spts/%u",
290 _PATH_DEV, (unsigned int)minor(rdev));
291 if (len >= sizeof(path)) {
292 sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
293 "devsearch entry %spts/%u too long",
294 _PATH_DEV, (unsigned int)minor(rdev));
295 continue;
296 }
297 ret = sudo_dev_check(rdev, path, buf, buflen);
298 if (ret != NULL)
299 goto done;
300 } else {
301 /* Scan path, looking for rdev. */
302 ret = sudo_ttyname_scan(path, rdev, buf, buflen);
303 if (ret != NULL || errno == ENOMEM)
304 goto done;
305 }
306 }
307
308 done:
309 debug_return_str(ret);
310 }
311 #endif
312