]>
Commit | Line | Data |
---|---|---|
ccc6cda3 JA |
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 | { | |
b72432fd | 173 | builtin_error ("path `%s' contains nonportable character `%c'", path, *p); |
ccc6cda3 JA |
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 | { | |
b72432fd | 214 | builtin_error ("`%s' is not a directory", path); |
ccc6cda3 JA |
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 | { | |
b72432fd | 326 | builtin_error ("name `%s' has length %d; exceeds limit of %d", |
ccc6cda3 JA |
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 | { | |
b72432fd | 352 | builtin_error ("path `%s' has length %d; exceeds limit of %d", |
ccc6cda3 JA |
353 | path, strlen (path), path_max); |
354 | return 1; | |
355 | } | |
356 | ||
357 | return 0; | |
358 | } |