Rev 2714 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
2714 | cejka | 1 | /* $NetBSD: test.c,v 1.22 2000/04/09 23:24:59 christos Exp $ */ |
2 | |||
3 | /* |
||
4 | * test(1); version 7-like -- author Erik Baalbergen |
||
5 | * modified by Eric Gisin to be used as built-in. |
||
6 | * modified by Arnold Robbins to add SVR3 compatibility |
||
7 | * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). |
||
8 | * modified by J.T. Conklin for NetBSD. |
||
9 | * |
||
10 | * This program is in the Public Domain. |
||
11 | */ |
||
12 | |||
13 | #include <sys/cdefs.h> |
||
14 | #ifndef lint |
||
15 | __RCSID("$NetBSD: test.c,v 1.22 2000/04/09 23:24:59 christos Exp $"); |
||
16 | #endif |
||
17 | |||
18 | #include <sys/types.h> |
||
19 | #include <sys/stat.h> |
||
20 | #include <unistd.h> |
||
21 | #include <ctype.h> |
||
22 | #include <errno.h> |
||
23 | #include <stdio.h> |
||
24 | #include <stdlib.h> |
||
25 | #include <string.h> |
||
26 | #include <err.h> |
||
27 | #ifdef __STDC__ |
||
28 | #include <stdarg.h> |
||
29 | #else |
||
30 | #include <varargs.h> |
||
31 | #endif |
||
32 | |||
33 | /* test(1) accepts the following grammar: |
||
34 | oexpr ::= aexpr | aexpr "-o" oexpr ; |
||
35 | aexpr ::= nexpr | nexpr "-a" aexpr ; |
||
36 | nexpr ::= primary | "!" primary |
||
37 | primary ::= unary-operator operand |
||
38 | | operand binary-operator operand |
||
39 | | operand |
||
40 | | "(" oexpr ")" |
||
41 | ; |
||
42 | unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| |
||
43 | "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; |
||
44 | |||
45 | binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| |
||
46 | "-nt"|"-ot"|"-ef"; |
||
47 | operand ::= <any legal UNIX file name> |
||
48 | */ |
||
49 | |||
50 | enum token { |
||
51 | EOI, |
||
52 | FILRD, |
||
53 | FILWR, |
||
54 | FILEX, |
||
55 | FILEXIST, |
||
56 | FILREG, |
||
57 | FILDIR, |
||
58 | FILCDEV, |
||
59 | FILBDEV, |
||
60 | FILFIFO, |
||
61 | FILSOCK, |
||
62 | FILSYM, |
||
63 | FILGZ, |
||
64 | FILTT, |
||
65 | FILSUID, |
||
66 | FILSGID, |
||
67 | FILSTCK, |
||
68 | FILNT, |
||
69 | FILOT, |
||
70 | FILEQ, |
||
71 | FILUID, |
||
72 | FILGID, |
||
73 | STREZ, |
||
74 | STRNZ, |
||
75 | STREQ, |
||
76 | STRNE, |
||
77 | STRLT, |
||
78 | STRGT, |
||
79 | INTEQ, |
||
80 | INTNE, |
||
81 | INTGE, |
||
82 | INTGT, |
||
83 | INTLE, |
||
84 | INTLT, |
||
85 | UNOT, |
||
86 | BAND, |
||
87 | BOR, |
||
88 | LPAREN, |
||
89 | RPAREN, |
||
90 | OPERAND |
||
91 | }; |
||
92 | |||
93 | enum token_types { |
||
94 | UNOP, |
||
95 | BINOP, |
||
96 | BUNOP, |
||
97 | BBINOP, |
||
98 | PAREN |
||
99 | }; |
||
100 | |||
101 | static struct t_op { |
||
102 | const char *op_text; |
||
103 | short op_num, op_type; |
||
104 | } const ops [] = { |
||
105 | {"-r", FILRD, UNOP}, |
||
106 | {"-w", FILWR, UNOP}, |
||
107 | {"-x", FILEX, UNOP}, |
||
108 | {"-e", FILEXIST,UNOP}, |
||
109 | {"-f", FILREG, UNOP}, |
||
110 | {"-d", FILDIR, UNOP}, |
||
111 | {"-c", FILCDEV,UNOP}, |
||
112 | {"-b", FILBDEV,UNOP}, |
||
113 | {"-p", FILFIFO,UNOP}, |
||
114 | {"-u", FILSUID,UNOP}, |
||
115 | {"-g", FILSGID,UNOP}, |
||
116 | {"-k", FILSTCK,UNOP}, |
||
117 | {"-s", FILGZ, UNOP}, |
||
118 | {"-t", FILTT, UNOP}, |
||
119 | {"-z", STREZ, UNOP}, |
||
120 | {"-n", STRNZ, UNOP}, |
||
121 | {"-h", FILSYM, UNOP}, /* for backwards compat */ |
||
122 | {"-O", FILUID, UNOP}, |
||
123 | {"-G", FILGID, UNOP}, |
||
124 | {"-L", FILSYM, UNOP}, |
||
125 | {"-S", FILSOCK,UNOP}, |
||
126 | {"=", STREQ, BINOP}, |
||
127 | {"!=", STRNE, BINOP}, |
||
128 | {"<", STRLT, BINOP}, |
||
129 | {">", STRGT, BINOP}, |
||
130 | {"-eq", INTEQ, BINOP}, |
||
131 | {"-ne", INTNE, BINOP}, |
||
132 | {"-ge", INTGE, BINOP}, |
||
133 | {"-gt", INTGT, BINOP}, |
||
134 | {"-le", INTLE, BINOP}, |
||
135 | {"-lt", INTLT, BINOP}, |
||
136 | {"-nt", FILNT, BINOP}, |
||
137 | {"-ot", FILOT, BINOP}, |
||
138 | {"-ef", FILEQ, BINOP}, |
||
139 | {"!", UNOT, BUNOP}, |
||
140 | {"-a", BAND, BBINOP}, |
||
141 | {"-o", BOR, BBINOP}, |
||
142 | {"(", LPAREN, PAREN}, |
||
143 | {")", RPAREN, PAREN}, |
||
144 | {0, 0, 0} |
||
145 | }; |
||
146 | |||
147 | static char **t_wp; |
||
148 | static struct t_op const *t_wp_op; |
||
149 | static gid_t *group_array = NULL; |
||
150 | static int ngroups; |
||
151 | |||
152 | static void syntax __P((const char *, const char *)); |
||
153 | static int oexpr __P((enum token)); |
||
154 | static int aexpr __P((enum token)); |
||
155 | static int nexpr __P((enum token)); |
||
156 | static int primary __P((enum token)); |
||
157 | static int binop __P((void)); |
||
158 | static int filstat __P((char *, enum token)); |
||
159 | static enum token t_lex __P((char *)); |
||
160 | static int isoperand __P((void)); |
||
161 | static int getn __P((const char *)); |
||
162 | static int newerf __P((const char *, const char *)); |
||
163 | static int olderf __P((const char *, const char *)); |
||
164 | static int equalf __P((const char *, const char *)); |
||
165 | static int test_eaccess(); |
||
166 | static int bash_group_member(); |
||
167 | static void initialize_group_array(); |
||
168 | |||
169 | #if defined(SHELL) |
||
170 | extern void error __P((const char *, ...)) __attribute__((__noreturn__)); |
||
171 | #else |
||
172 | static void error __P((const char *, ...)) __attribute__((__noreturn__)); |
||
173 | |||
174 | static void |
||
175 | #ifdef __STDC__ |
||
176 | error(const char *msg, ...) |
||
177 | #else |
||
178 | error(va_alist) |
||
179 | va_dcl |
||
180 | #endif |
||
181 | { |
||
182 | va_list ap; |
||
183 | #ifndef __STDC__ |
||
184 | const char *msg; |
||
185 | |||
186 | va_start(ap); |
||
187 | msg = va_arg(ap, const char *); |
||
188 | #else |
||
189 | va_start(ap, msg); |
||
190 | #endif |
||
191 | verrx(2, msg, ap); |
||
192 | /*NOTREACHED*/ |
||
193 | va_end(ap); |
||
194 | } |
||
195 | #endif |
||
196 | |||
197 | #ifdef SHELL |
||
198 | int testcmd __P((int, char **)); |
||
199 | |||
200 | int |
||
201 | testcmd(argc, argv) |
||
202 | int argc; |
||
203 | char **argv; |
||
204 | #else |
||
205 | int main __P((int, char **)); |
||
206 | |||
207 | int |
||
208 | main(argc, argv) |
||
209 | int argc; |
||
210 | char **argv; |
||
211 | #endif |
||
212 | { |
||
213 | int res; |
||
214 | |||
215 | |||
216 | if (strcmp(argv[0], "[") == 0) { |
||
217 | if (strcmp(argv[--argc], "]")) |
||
218 | error("missing ]"); |
||
219 | argv[argc] = NULL; |
||
220 | } |
||
221 | |||
222 | if (argc < 2) |
||
223 | return 1; |
||
224 | |||
225 | t_wp = &argv[1]; |
||
226 | res = !oexpr(t_lex(*t_wp)); |
||
227 | |||
228 | if (*t_wp != NULL && *++t_wp != NULL) |
||
229 | syntax(*t_wp, "unexpected operator"); |
||
230 | |||
231 | return res; |
||
232 | } |
||
233 | |||
234 | static void |
||
235 | syntax(op, msg) |
||
236 | const char *op; |
||
237 | const char *msg; |
||
238 | { |
||
239 | if (op && *op) |
||
240 | error("%s: %s", op, msg); |
||
241 | else |
||
242 | error("%s", msg); |
||
243 | } |
||
244 | |||
245 | static int |
||
246 | oexpr(n) |
||
247 | enum token n; |
||
248 | { |
||
249 | int res; |
||
250 | |||
251 | res = aexpr(n); |
||
252 | if (t_lex(*++t_wp) == BOR) |
||
253 | return oexpr(t_lex(*++t_wp)) || res; |
||
254 | t_wp--; |
||
255 | return res; |
||
256 | } |
||
257 | |||
258 | static int |
||
259 | aexpr(n) |
||
260 | enum token n; |
||
261 | { |
||
262 | int res; |
||
263 | |||
264 | res = nexpr(n); |
||
265 | if (t_lex(*++t_wp) == BAND) |
||
266 | return aexpr(t_lex(*++t_wp)) && res; |
||
267 | t_wp--; |
||
268 | return res; |
||
269 | } |
||
270 | |||
271 | static int |
||
272 | nexpr(n) |
||
273 | enum token n; /* token */ |
||
274 | { |
||
275 | if (n == UNOT) |
||
276 | return !nexpr(t_lex(*++t_wp)); |
||
277 | return primary(n); |
||
278 | } |
||
279 | |||
280 | static int |
||
281 | primary(n) |
||
282 | enum token n; |
||
283 | { |
||
284 | enum token nn; |
||
285 | int res; |
||
286 | |||
287 | if (n == EOI) |
||
288 | return 0; /* missing expression */ |
||
289 | if (n == LPAREN) { |
||
290 | if ((nn = t_lex(*++t_wp)) == RPAREN) |
||
291 | return 0; /* missing expression */ |
||
292 | res = oexpr(nn); |
||
293 | if (t_lex(*++t_wp) != RPAREN) |
||
294 | syntax(NULL, "closing paren expected"); |
||
295 | return res; |
||
296 | } |
||
297 | if (t_wp_op && t_wp_op->op_type == UNOP) { |
||
298 | /* unary expression */ |
||
299 | if (*++t_wp == NULL) |
||
300 | syntax(t_wp_op->op_text, "argument expected"); |
||
301 | switch (n) { |
||
302 | case STREZ: |
||
303 | return strlen(*t_wp) == 0; |
||
304 | case STRNZ: |
||
305 | return strlen(*t_wp) != 0; |
||
306 | case FILTT: |
||
307 | return isatty(getn(*t_wp)); |
||
308 | default: |
||
309 | return filstat(*t_wp, n); |
||
310 | } |
||
311 | } |
||
312 | |||
313 | if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { |
||
314 | return binop(); |
||
315 | } |
||
316 | |||
317 | return strlen(*t_wp) > 0; |
||
318 | } |
||
319 | |||
320 | static int |
||
321 | binop() |
||
322 | { |
||
323 | const char *opnd1, *opnd2; |
||
324 | struct t_op const *op; |
||
325 | |||
326 | opnd1 = *t_wp; |
||
327 | (void) t_lex(*++t_wp); |
||
328 | op = t_wp_op; |
||
329 | |||
330 | if ((opnd2 = *++t_wp) == (char *)0) |
||
331 | syntax(op->op_text, "argument expected"); |
||
332 | |||
333 | switch (op->op_num) { |
||
334 | case STREQ: |
||
335 | return strcmp(opnd1, opnd2) == 0; |
||
336 | case STRNE: |
||
337 | return strcmp(opnd1, opnd2) != 0; |
||
338 | case STRLT: |
||
339 | return strcmp(opnd1, opnd2) < 0; |
||
340 | case STRGT: |
||
341 | return strcmp(opnd1, opnd2) > 0; |
||
342 | case INTEQ: |
||
343 | return getn(opnd1) == getn(opnd2); |
||
344 | case INTNE: |
||
345 | return getn(opnd1) != getn(opnd2); |
||
346 | case INTGE: |
||
347 | return getn(opnd1) >= getn(opnd2); |
||
348 | case INTGT: |
||
349 | return getn(opnd1) > getn(opnd2); |
||
350 | case INTLE: |
||
351 | return getn(opnd1) <= getn(opnd2); |
||
352 | case INTLT: |
||
353 | return getn(opnd1) < getn(opnd2); |
||
354 | case FILNT: |
||
355 | return newerf (opnd1, opnd2); |
||
356 | case FILOT: |
||
357 | return olderf (opnd1, opnd2); |
||
358 | case FILEQ: |
||
359 | return equalf (opnd1, opnd2); |
||
360 | default: |
||
361 | abort(); |
||
362 | /* NOTREACHED */ |
||
363 | } |
||
364 | } |
||
365 | |||
366 | static int |
||
367 | filstat(nm, mode) |
||
368 | char *nm; |
||
369 | enum token mode; |
||
370 | { |
||
371 | struct stat s; |
||
372 | |||
373 | if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) |
||
374 | return 0; |
||
375 | |||
376 | switch (mode) { |
||
377 | case FILRD: |
||
378 | return test_eaccess(nm, R_OK) == 0; |
||
379 | case FILWR: |
||
380 | return test_eaccess(nm, W_OK) == 0; |
||
381 | case FILEX: |
||
382 | return test_eaccess(nm, X_OK) == 0; |
||
383 | case FILEXIST: |
||
384 | return 1; |
||
385 | case FILREG: |
||
386 | return S_ISREG(s.st_mode); |
||
387 | case FILDIR: |
||
388 | return S_ISDIR(s.st_mode); |
||
389 | case FILCDEV: |
||
390 | return S_ISCHR(s.st_mode); |
||
391 | case FILBDEV: |
||
392 | return S_ISBLK(s.st_mode); |
||
393 | case FILFIFO: |
||
394 | return S_ISFIFO(s.st_mode); |
||
395 | case FILSOCK: |
||
396 | return S_ISSOCK(s.st_mode); |
||
397 | case FILSYM: |
||
398 | return S_ISLNK(s.st_mode); |
||
399 | case FILSUID: |
||
400 | return (s.st_mode & S_ISUID) != 0; |
||
401 | case FILSGID: |
||
402 | return (s.st_mode & S_ISGID) != 0; |
||
403 | case FILSTCK: |
||
404 | return (s.st_mode & S_ISVTX) != 0; |
||
405 | case FILGZ: |
||
406 | return s.st_size > (off_t)0; |
||
407 | case FILUID: |
||
408 | return s.st_uid == geteuid(); |
||
409 | case FILGID: |
||
410 | return s.st_gid == getegid(); |
||
411 | default: |
||
412 | return 1; |
||
413 | } |
||
414 | } |
||
415 | |||
416 | static enum token |
||
417 | t_lex(s) |
||
418 | char *s; |
||
419 | { |
||
420 | struct t_op const *op = ops; |
||
421 | |||
422 | if (s == 0) { |
||
423 | t_wp_op = (struct t_op *)0; |
||
424 | return EOI; |
||
425 | } |
||
426 | while (op->op_text) { |
||
427 | if (strcmp(s, op->op_text) == 0) { |
||
428 | if ((op->op_type == UNOP && isoperand()) || |
||
429 | (op->op_num == LPAREN && *(t_wp+1) == 0)) |
||
430 | break; |
||
431 | t_wp_op = op; |
||
432 | return op->op_num; |
||
433 | } |
||
434 | op++; |
||
435 | } |
||
436 | t_wp_op = (struct t_op *)0; |
||
437 | return OPERAND; |
||
438 | } |
||
439 | |||
440 | static int |
||
441 | isoperand() |
||
442 | { |
||
443 | struct t_op const *op = ops; |
||
444 | char *s; |
||
445 | char *t; |
||
446 | |||
447 | if ((s = *(t_wp+1)) == 0) |
||
448 | return 1; |
||
449 | if ((t = *(t_wp+2)) == 0) |
||
450 | return 0; |
||
451 | while (op->op_text) { |
||
452 | if (strcmp(s, op->op_text) == 0) |
||
453 | return op->op_type == BINOP && |
||
454 | (t[0] != ')' || t[1] != '\0'); |
||
455 | op++; |
||
456 | } |
||
457 | return 0; |
||
458 | } |
||
459 | |||
460 | /* atoi with error detection */ |
||
461 | static int |
||
462 | getn(s) |
||
463 | const char *s; |
||
464 | { |
||
465 | char *p; |
||
466 | long r; |
||
467 | |||
468 | errno = 0; |
||
469 | r = strtol(s, &p, 10); |
||
470 | |||
471 | if (errno != 0) |
||
472 | error("%s: out of range", s); |
||
473 | |||
474 | while (isspace((unsigned char)*p)) |
||
475 | p++; |
||
476 | |||
477 | if (*p) |
||
478 | error("%s: bad number", s); |
||
479 | |||
480 | return (int) r; |
||
481 | } |
||
482 | |||
483 | static int |
||
484 | newerf (f1, f2) |
||
485 | const char *f1, *f2; |
||
486 | { |
||
487 | struct stat b1, b2; |
||
488 | |||
489 | return (stat (f1, &b1) == 0 && |
||
490 | stat (f2, &b2) == 0 && |
||
491 | b1.st_mtime > b2.st_mtime); |
||
492 | } |
||
493 | |||
494 | static int |
||
495 | olderf (f1, f2) |
||
496 | const char *f1, *f2; |
||
497 | { |
||
498 | struct stat b1, b2; |
||
499 | |||
500 | return (stat (f1, &b1) == 0 && |
||
501 | stat (f2, &b2) == 0 && |
||
502 | b1.st_mtime < b2.st_mtime); |
||
503 | } |
||
504 | |||
505 | static int |
||
506 | equalf (f1, f2) |
||
507 | const char *f1, *f2; |
||
508 | { |
||
509 | struct stat b1, b2; |
||
510 | |||
511 | return (stat (f1, &b1) == 0 && |
||
512 | stat (f2, &b2) == 0 && |
||
513 | b1.st_dev == b2.st_dev && |
||
514 | b1.st_ino == b2.st_ino); |
||
515 | } |
||
516 | |||
517 | /* Do the same thing access(2) does, but use the effective uid and gid, |
||
518 | and don't make the mistake of telling root that any file is |
||
519 | executable. */ |
||
520 | static int |
||
521 | test_eaccess (path, mode) |
||
522 | char *path; |
||
523 | int mode; |
||
524 | { |
||
525 | struct stat st; |
||
526 | int euid = geteuid(); |
||
527 | |||
528 | if (stat (path, &st) < 0) |
||
529 | return (-1); |
||
530 | |||
531 | if (euid == 0) { |
||
532 | /* Root can read or write any file. */ |
||
533 | if (mode != X_OK) |
||
534 | return (0); |
||
535 | |||
536 | /* Root can execute any file that has any one of the execute |
||
537 | bits set. */ |
||
538 | if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) |
||
539 | return (0); |
||
540 | } |
||
541 | |||
542 | if (st.st_uid == euid) /* owner */ |
||
543 | mode <<= 6; |
||
544 | else if (bash_group_member (st.st_gid)) |
||
545 | mode <<= 3; |
||
546 | |||
547 | if (st.st_mode & mode) |
||
548 | return (0); |
||
549 | |||
550 | return (-1); |
||
551 | } |
||
552 | |||
553 | static void |
||
554 | initialize_group_array () |
||
555 | { |
||
556 | ngroups = getgroups(0, NULL); |
||
557 | group_array = malloc(ngroups * sizeof(gid_t)); |
||
558 | if (!group_array) |
||
559 | error(strerror(ENOMEM)); |
||
560 | getgroups(ngroups, group_array); |
||
561 | } |
||
562 | |||
563 | /* Return non-zero if GID is one that we have in our groups list. */ |
||
564 | static int |
||
565 | bash_group_member (gid) |
||
566 | gid_t gid; |
||
567 | { |
||
568 | register int i; |
||
569 | |||
570 | /* Short-circuit if possible, maybe saving a call to getgroups(). */ |
||
571 | if (gid == getgid() || gid == getegid()) |
||
572 | return (1); |
||
573 | |||
574 | if (ngroups == 0) |
||
575 | initialize_group_array (); |
||
576 | |||
577 | /* Search through the list looking for GID. */ |
||
578 | for (i = 0; i < ngroups; i++) |
||
579 | if (gid == group_array[i]) |
||
580 | return (1); |
||
581 | |||
582 | return (0); |
||
583 | } |