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