1 | /* pathchk - check pathnames for validity and portability */
|
---|
2 |
|
---|
3 | /* Usage: pathchk [-p] path ...
|
---|
4 |
|
---|
5 | For each PATH, print a message if any of these conditions are false:
|
---|
6 | * all existing leading directories in PATH have search (execute) permission
|
---|
7 | * strlen (PATH) <= PATH_MAX
|
---|
8 | * strlen (each_directory_in_PATH) <= NAME_MAX
|
---|
9 |
|
---|
10 | Exit status:
|
---|
11 | 0 All PATH names passed all of the tests.
|
---|
12 | 1 An error occurred.
|
---|
13 |
|
---|
14 | Options:
|
---|
15 | -p Instead of performing length checks on the
|
---|
16 | underlying filesystem, test the length of the
|
---|
17 | pathname and its components against the POSIX.1
|
---|
18 | minimum limits for portability, _POSIX_NAME_MAX
|
---|
19 | and _POSIX_PATH_MAX in 2.9.2. Also check that
|
---|
20 | the pathname contains no character not in the
|
---|
21 | portable filename character set. */
|
---|
22 |
|
---|
23 | /* See Makefile for compilation details. */
|
---|
24 |
|
---|
25 | #include <config.h>
|
---|
26 |
|
---|
27 | #include <sys/types.h>
|
---|
28 | #include "posixstat.h"
|
---|
29 |
|
---|
30 | #if defined (HAVE_UNISTD_H)
|
---|
31 | # include <unistd.h>
|
---|
32 | #endif
|
---|
33 |
|
---|
34 | #if defined (HAVE_LIMITS_H)
|
---|
35 | # include <limits.h>
|
---|
36 | #endif
|
---|
37 |
|
---|
38 | #include "bashansi.h"
|
---|
39 |
|
---|
40 | #include <stdio.h>
|
---|
41 | #include <errno.h>
|
---|
42 |
|
---|
43 | #include "builtins.h"
|
---|
44 | #include "shell.h"
|
---|
45 | #include "stdc.h"
|
---|
46 | #include "bashgetopt.h"
|
---|
47 | #include "maxpath.h"
|
---|
48 |
|
---|
49 | #if !defined (errno)
|
---|
50 | extern int errno;
|
---|
51 | #endif
|
---|
52 |
|
---|
53 | #if !defined (_POSIX_PATH_MAX)
|
---|
54 | # define _POSIX_PATH_MAX 255
|
---|
55 | #endif
|
---|
56 | #if !defined (_POSIX_NAME_MAX)
|
---|
57 | # define _POSIX_NAME_MAX 14
|
---|
58 | #endif
|
---|
59 |
|
---|
60 | /* How do we get PATH_MAX? */
|
---|
61 | #if defined (_POSIX_VERSION) && !defined (PATH_MAX)
|
---|
62 | # define PATH_MAX_FOR(p) pathconf ((p), _PC_PATH_MAX)
|
---|
63 | #endif
|
---|
64 |
|
---|
65 | /* How do we get NAME_MAX? */
|
---|
66 | #if defined (_POSIX_VERSION) && !defined (NAME_MAX)
|
---|
67 | # define NAME_MAX_FOR(p) pathconf ((p), _PC_NAME_MAX)
|
---|
68 | #endif
|
---|
69 |
|
---|
70 | #if !defined (PATH_MAX_FOR)
|
---|
71 | # define PATH_MAX_FOR(p) PATH_MAX
|
---|
72 | #endif
|
---|
73 |
|
---|
74 | #if !defined (NAME_MAX_FOR)
|
---|
75 | # define NAME_MAX_FOR(p) NAME_MAX
|
---|
76 | #endif
|
---|
77 |
|
---|
78 | extern char *strerror ();
|
---|
79 |
|
---|
80 | static int validate_path ();
|
---|
81 |
|
---|
82 | pathchk_builtin (list)
|
---|
83 | WORD_LIST *list;
|
---|
84 | {
|
---|
85 | int retval, pflag, opt;
|
---|
86 |
|
---|
87 | reset_internal_getopt ();
|
---|
88 | while ((opt = internal_getopt (list, "p")) != -1)
|
---|
89 | {
|
---|
90 | switch (opt)
|
---|
91 | {
|
---|
92 | case 'p':
|
---|
93 | pflag = 1;
|
---|
94 | break;
|
---|
95 | default:
|
---|
96 | builtin_usage ();
|
---|
97 | return (EX_USAGE);
|
---|
98 | }
|
---|
99 | }
|
---|
100 | list = loptend;
|
---|
101 |
|
---|
102 | if (list == 0)
|
---|
103 | {
|
---|
104 | builtin_usage ();
|
---|
105 | return (EX_USAGE);
|
---|
106 | }
|
---|
107 |
|
---|
108 | for (retval = 0; list; list = list->next)
|
---|
109 | retval |= validate_path (list->word->word, pflag);
|
---|
110 |
|
---|
111 | return (retval ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
|
---|
112 | }
|
---|
113 |
|
---|
114 | char *pathchk_doc[] = {
|
---|
115 | "Check each pathname argument for validity (i.e., it may be used to",
|
---|
116 | "create or access a file without casuing syntax errors) and portability",
|
---|
117 | "(i.e., no filename truncation will result). If the `-p' option is",
|
---|
118 | "supplied, more extensive portability checks are performed.",
|
---|
119 | (char *)NULL
|
---|
120 | };
|
---|
121 |
|
---|
122 | /* The standard structure describing a builtin command. bash keeps an array
|
---|
123 | of these structures. */
|
---|
124 | struct builtin pathchk_struct = {
|
---|
125 | "pathchk", /* builtin name */
|
---|
126 | pathchk_builtin, /* function implementing the builtin */
|
---|
127 | BUILTIN_ENABLED, /* initial flags for builtin */
|
---|
128 | pathchk_doc, /* array of long documentation strings. */
|
---|
129 | "pathchk [-p] pathname ...", /* usage synopsis */
|
---|
130 | 0 /* reserved for internal use */
|
---|
131 | };
|
---|
132 |
|
---|
133 | /* The remainder of this file is stolen shamelessly from `pathchk.c' in
|
---|
134 | the sh-utils-1.12 distribution, by
|
---|
135 |
|
---|
136 | David MacKenzie <djm@gnu.ai.mit.edu>
|
---|
137 | and Jim Meyering <meyering@cs.utexas.edu> */
|
---|
138 |
|
---|
139 | /* Each element is nonzero if the corresponding ASCII character is
|
---|
140 | in the POSIX portable character set, and zero if it is not.
|
---|
141 | In addition, the entry for `/' is nonzero to simplify checking. */
|
---|
142 | static char const portable_chars[256] =
|
---|
143 | {
|
---|
144 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
|
---|
145 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
|
---|
146 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
|
---|
147 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
|
---|
148 | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
|
---|
149 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
|
---|
150 | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
|
---|
151 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
|
---|
152 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
---|
153 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
---|
154 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
---|
155 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
---|
156 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
---|
157 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
---|
158 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
---|
159 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
---|
160 | };
|
---|
161 |
|
---|
162 | /* If PATH contains only portable characters, return 1, else 0. */
|
---|
163 |
|
---|
164 | static int
|
---|
165 | portable_chars_only (path)
|
---|
166 | const char *path;
|
---|
167 | {
|
---|
168 | const char *p;
|
---|
169 |
|
---|
170 | for (p = path; *p; ++p)
|
---|
171 | if (portable_chars[(const unsigned char) *p] == 0)
|
---|
172 | {
|
---|
173 | builtin_error ("path `%s' contains nonportable character `%c'", path, *p);
|
---|
174 | return 0;
|
---|
175 | }
|
---|
176 | return 1;
|
---|
177 | }
|
---|
178 |
|
---|
179 | /* On some systems, stat can return EINTR. */
|
---|
180 |
|
---|
181 | #ifndef EINTR
|
---|
182 | # define SAFE_STAT(name, buf) stat (name, buf)
|
---|
183 | #else
|
---|
184 | # define SAFE_STAT(name, buf) safe_stat (name, buf)
|
---|
185 | static inline int
|
---|
186 | safe_stat (name, buf)
|
---|
187 | const char *name;
|
---|
188 | struct stat *buf;
|
---|
189 | {
|
---|
190 | int ret;
|
---|
191 |
|
---|
192 | do
|
---|
193 | ret = stat (name, buf);
|
---|
194 | while (ret < 0 && errno == EINTR);
|
---|
195 |
|
---|
196 | return ret;
|
---|
197 | }
|
---|
198 | #endif
|
---|
199 |
|
---|
200 | /* Return 1 if PATH is a usable leading directory, 0 if not,
|
---|
201 | 2 if it doesn't exist. */
|
---|
202 |
|
---|
203 | static int
|
---|
204 | dir_ok (path)
|
---|
205 | const char *path;
|
---|
206 | {
|
---|
207 | struct stat stats;
|
---|
208 |
|
---|
209 | if (SAFE_STAT (path, &stats))
|
---|
210 | return 2;
|
---|
211 |
|
---|
212 | if (!S_ISDIR (stats.st_mode))
|
---|
213 | {
|
---|
214 | builtin_error ("`%s' is not a directory", path);
|
---|
215 | return 0;
|
---|
216 | }
|
---|
217 |
|
---|
218 | /* Use access to test for search permission because
|
---|
219 | testing permission bits of st_mode can lose with new
|
---|
220 | access control mechanisms. Of course, access loses if you're
|
---|
221 | running setuid. */
|
---|
222 | if (access (path, X_OK) != 0)
|
---|
223 | {
|
---|
224 | if (errno == EACCES)
|
---|
225 | builtin_error ("directory `%s' is not searchable", path);
|
---|
226 | else
|
---|
227 | builtin_error ("%s: %s", path, strerror (errno));
|
---|
228 | return 0;
|
---|
229 | }
|
---|
230 |
|
---|
231 | return 1;
|
---|
232 | }
|
---|
233 |
|
---|
234 | static char *
|
---|
235 | xstrdup (s)
|
---|
236 | char *s;
|
---|
237 | {
|
---|
238 | return (savestring (s));
|
---|
239 | }
|
---|
240 |
|
---|
241 | /* Make sure that
|
---|
242 | strlen (PATH) <= PATH_MAX
|
---|
243 | && strlen (each-existing-directory-in-PATH) <= NAME_MAX
|
---|
244 |
|
---|
245 | If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
|
---|
246 | _POSIX_NAME_MAX instead, and make sure that PATH contains no
|
---|
247 | characters not in the POSIX portable filename character set, which
|
---|
248 | consists of A-Z, a-z, 0-9, ., _, -.
|
---|
249 |
|
---|
250 | Make sure that all leading directories along PATH that exist have
|
---|
251 | `x' permission.
|
---|
252 |
|
---|
253 | Return 0 if all of these tests are successful, 1 if any fail. */
|
---|
254 |
|
---|
255 | static int
|
---|
256 | validate_path (path, portability)
|
---|
257 | char *path;
|
---|
258 | int portability;
|
---|
259 | {
|
---|
260 | int path_max;
|
---|
261 | int last_elem; /* Nonzero if checking last element of path. */
|
---|
262 | int exists; /* 2 if the path element exists. */
|
---|
263 | char *slash;
|
---|
264 | char *parent; /* Last existing leading directory so far. */
|
---|
265 |
|
---|
266 | if (portability && !portable_chars_only (path))
|
---|
267 | return 1;
|
---|
268 |
|
---|
269 | if (*path == '\0')
|
---|
270 | return 0;
|
---|
271 |
|
---|
272 | #ifdef lint
|
---|
273 | /* Suppress `used before initialized' warning. */
|
---|
274 | exists = 0;
|
---|
275 | #endif
|
---|
276 |
|
---|
277 | /* Figure out the parent of the first element in PATH. */
|
---|
278 | parent = xstrdup (*path == '/' ? "/" : ".");
|
---|
279 |
|
---|
280 | slash = path;
|
---|
281 | last_elem = 0;
|
---|
282 | while (1)
|
---|
283 | {
|
---|
284 | int name_max;
|
---|
285 | int length; /* Length of partial path being checked. */
|
---|
286 | char *start; /* Start of path element being checked. */
|
---|
287 |
|
---|
288 | /* Find the end of this element of the path.
|
---|
289 | Then chop off the rest of the path after this element. */
|
---|
290 | while (*slash == '/')
|
---|
291 | slash++;
|
---|
292 | start = slash;
|
---|
293 | slash = strchr (slash, '/');
|
---|
294 | if (slash != NULL)
|
---|
295 | *slash = '\0';
|
---|
296 | else
|
---|
297 | {
|
---|
298 | last_elem = 1;
|
---|
299 | slash = strchr (start, '\0');
|
---|
300 | }
|
---|
301 |
|
---|
302 | if (!last_elem)
|
---|
303 | {
|
---|
304 | exists = dir_ok (path);
|
---|
305 | if (dir_ok == 0)
|
---|
306 | {
|
---|
307 | free (parent);
|
---|
308 | return 1;
|
---|
309 | }
|
---|
310 | }
|
---|
311 |
|
---|
312 | length = slash - start;
|
---|
313 | /* Since we know that `parent' is a directory, it's ok to call
|
---|
314 | pathconf with it as the argument. (If `parent' isn't a directory
|
---|
315 | or doesn't exist, the behavior of pathconf is undefined.)
|
---|
316 | But if `parent' is a directory and is on a remote file system,
|
---|
317 | it's likely that pathconf can't give us a reasonable value
|
---|
318 | and will return -1. (NFS and tempfs are not POSIX . . .)
|
---|
319 | In that case, we have no choice but to assume the pessimal
|
---|
320 | POSIX minimums. */
|
---|
321 | name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
|
---|
322 | if (name_max < 0)
|
---|
323 | name_max = _POSIX_NAME_MAX;
|
---|
324 | if (length > name_max)
|
---|
325 | {
|
---|
326 | builtin_error ("name `%s' has length %d; exceeds limit of %d",
|
---|
327 | start, length, name_max);
|
---|
328 | free (parent);
|
---|
329 | return 1;
|
---|
330 | }
|
---|
331 |
|
---|
332 | if (last_elem)
|
---|
333 | break;
|
---|
334 |
|
---|
335 | if (exists == 1)
|
---|
336 | {
|
---|
337 | free (parent);
|
---|
338 | parent = xstrdup (path);
|
---|
339 | }
|
---|
340 |
|
---|
341 | *slash++ = '/';
|
---|
342 | }
|
---|
343 |
|
---|
344 | /* `parent' is now the last existing leading directory in the whole path,
|
---|
345 | so it's ok to call pathconf with it as the argument. */
|
---|
346 | path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
|
---|
347 | if (path_max < 0)
|
---|
348 | path_max = _POSIX_PATH_MAX;
|
---|
349 | free (parent);
|
---|
350 | if (strlen (path) > path_max)
|
---|
351 | {
|
---|
352 | builtin_error ("path `%s' has length %d; exceeds limit of %d",
|
---|
353 | path, strlen (path), path_max);
|
---|
354 | return 1;
|
---|
355 | }
|
---|
356 |
|
---|
357 | return 0;
|
---|
358 | }
|
---|