1/* vi: set sw=4 ts=4: */ 2/* 3 * test implementation for busybox 4 * 5 * Copyright (c) by a whole pile of folks: 6 * 7 * test(1); version 7-like -- author Erik Baalbergen 8 * modified by Eric Gisin to be used as built-in. 9 * modified by Arnold Robbins to add SVR3 compatibility 10 * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). 11 * modified by J.T. Conklin for NetBSD. 12 * modified by Herbert Xu to be used as built-in in ash. 13 * modified by Erik Andersen <andersen@codepoet.org> to be used 14 * in busybox. 15 * modified by Bernhard Fischer to be useable (i.e. a bit less bloaty). 16 * 17 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. 18 * 19 * Original copyright notice states: 20 * "This program is in the Public Domain." 21 */ 22 23#include "libbb.h" 24#include <setjmp.h> 25 26/* This is a NOEXEC applet. Be very careful! */ 27 28 29/* test(1) accepts the following grammar: 30 oexpr ::= aexpr | aexpr "-o" oexpr ; 31 aexpr ::= nexpr | nexpr "-a" aexpr ; 32 nexpr ::= primary | "!" primary 33 primary ::= unary-operator operand 34 | operand binary-operator operand 35 | operand 36 | "(" oexpr ")" 37 ; 38 unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| 39 "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; 40 41 binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| 42 "-nt"|"-ot"|"-ef"; 43 operand ::= <any legal UNIX file name> 44*/ 45 46enum token { 47 EOI, 48 FILRD, 49 FILWR, 50 FILEX, 51 FILEXIST, 52 FILREG, 53 FILDIR, 54 FILCDEV, 55 FILBDEV, 56 FILFIFO, 57 FILSOCK, 58 FILSYM, 59 FILGZ, 60 FILTT, 61 FILSUID, 62 FILSGID, 63 FILSTCK, 64 FILNT, 65 FILOT, 66 FILEQ, 67 FILUID, 68 FILGID, 69 STREZ, 70 STRNZ, 71 STREQ, 72 STRNE, 73 STRLT, 74 STRGT, 75 INTEQ, 76 INTNE, 77 INTGE, 78 INTGT, 79 INTLE, 80 INTLT, 81 UNOT, 82 BAND, 83 BOR, 84 LPAREN, 85 RPAREN, 86 OPERAND 87}; 88#define is_int_op(a) (((unsigned char)((a) - INTEQ)) <= 5) 89#define is_str_op(a) (((unsigned char)((a) - STREZ)) <= 5) 90#define is_file_op(a) (((unsigned char)((a) - FILNT)) <= 2) 91#define is_file_access(a) (((unsigned char)((a) - FILRD)) <= 2) 92#define is_file_type(a) (((unsigned char)((a) - FILREG)) <= 5) 93#define is_file_bit(a) (((unsigned char)((a) - FILSUID)) <= 2) 94enum token_types { 95 UNOP, 96 BINOP, 97 BUNOP, 98 BBINOP, 99 PAREN 100}; 101 102static const struct t_op { 103 char op_text[4]; 104 unsigned char op_num, op_type; 105} ops[] = { 106 { "-r", FILRD , UNOP }, 107 { "-w", FILWR , UNOP }, 108 { "-x", FILEX , UNOP }, 109 { "-e", FILEXIST, UNOP }, 110 { "-f", FILREG , UNOP }, 111 { "-d", FILDIR , UNOP }, 112 { "-c", FILCDEV , UNOP }, 113 { "-b", FILBDEV , UNOP }, 114 { "-p", FILFIFO , UNOP }, 115 { "-u", FILSUID , UNOP }, 116 { "-g", FILSGID , UNOP }, 117 { "-k", FILSTCK , UNOP }, 118 { "-s", FILGZ , UNOP }, 119 { "-t", FILTT , UNOP }, 120 { "-z", STREZ , UNOP }, 121 { "-n", STRNZ , UNOP }, 122 { "-h", FILSYM , UNOP }, /* for backwards compat */ 123 124 { "-O" , FILUID , UNOP }, 125 { "-G" , FILGID , UNOP }, 126 { "-L" , FILSYM , UNOP }, 127 { "-S" , FILSOCK, UNOP }, 128 { "=" , STREQ , BINOP }, 129 { "==" , STREQ , BINOP }, 130 { "!=" , STRNE , BINOP }, 131 { "<" , STRLT , BINOP }, 132 { ">" , STRGT , BINOP }, 133 { "-eq", INTEQ , BINOP }, 134 { "-ne", INTNE , BINOP }, 135 { "-ge", INTGE , BINOP }, 136 { "-gt", INTGT , BINOP }, 137 { "-le", INTLE , BINOP }, 138 { "-lt", INTLT , BINOP }, 139 { "-nt", FILNT , BINOP }, 140 { "-ot", FILOT , BINOP }, 141 { "-ef", FILEQ , BINOP }, 142 { "!" , UNOT , BUNOP }, 143 { "-a" , BAND , BBINOP }, 144 { "-o" , BOR , BBINOP }, 145 { "(" , LPAREN , PAREN }, 146 { ")" , RPAREN , PAREN }, 147}; 148 149 150#if ENABLE_FEATURE_TEST_64 151typedef int64_t arith_t; 152#else 153typedef int arith_t; 154#endif 155 156/* Cannot eliminate these static data (do the G trick) 157 * because of test_main usage from other applets */ 158static char **t_wp; 159static struct t_op const *t_wp_op; 160static gid_t *group_array; 161static int ngroups; 162static jmp_buf leaving; 163 164static enum token t_lex(char *s); 165static arith_t oexpr(enum token n); 166static arith_t aexpr(enum token n); 167static arith_t nexpr(enum token n); 168static int binop(void); 169static arith_t primary(enum token n); 170static int filstat(char *nm, enum token mode); 171static arith_t getn(const char *s); 172/* UNUSED 173static int newerf(const char *f1, const char *f2); 174static int olderf(const char *f1, const char *f2); 175static int equalf(const char *f1, const char *f2); 176*/ 177static int test_eaccess(char *path, int mode); 178static int is_a_group_member(gid_t gid); 179static void initialize_group_array(void); 180 181int test_main(int argc, char **argv) 182{ 183 int res; 184 const char *arg0; 185 bool _off; 186 187 arg0 = bb_basename(argv[0]); 188 if (arg0[0] == '[') { 189 --argc; 190 if (!arg0[1]) { /* "[" ? */ 191 if (NOT_LONE_CHAR(argv[argc], ']')) { 192 bb_error_msg("missing ]"); 193 return 2; 194 } 195 } else { /* assuming "[[" */ 196 if (strcmp(argv[argc], "]]") != 0) { 197 bb_error_msg("missing ]]"); 198 return 2; 199 } 200 } 201 argv[argc] = NULL; 202 } 203 204 res = setjmp(leaving); 205 if (res) 206 return res; 207 208 /* resetting ngroups is probably unnecessary. it will 209 * force a new call to getgroups(), which prevents using 210 * group data fetched during a previous call. but the 211 * only way the group data could be stale is if there's 212 * been an intervening call to setgroups(), and this 213 * isn't likely in the case of a shell. paranoia 214 * prevails... 215 */ 216 ngroups = 0; 217 218 /* Implement special cases from POSIX.2, section 4.62.4 */ 219 if (argc == 1) 220 return 1; 221 if (argc == 2) 222 return *argv[1] == '\0'; 223//assert(argc); 224 /* remember if we saw argc==4 which wants *no* '!' test */ 225 _off = argc - 4; 226 if (_off ? 227 (LONE_CHAR(argv[1], '!')) 228 : (argv[1][0] != '!' || argv[1][1] != '\0')) 229 { 230 if (argc == 3) 231 return *argv[2] != '\0'; 232 233 t_lex(argv[2 + _off]); 234 if (t_wp_op && t_wp_op->op_type == BINOP) { 235 t_wp = &argv[1 + _off]; 236 return binop() == _off; 237 } 238 } 239 t_wp = &argv[1]; 240 res = !oexpr(t_lex(*t_wp)); 241 242 if (*t_wp != NULL && *++t_wp != NULL) { 243 bb_error_msg("%s: unknown operand", *t_wp); 244 return 2; 245 } 246 return res; 247} 248 249static void syntax(const char *op, const char *msg) ATTRIBUTE_NORETURN; 250static void syntax(const char *op, const char *msg) 251{ 252 if (op && *op) { 253 bb_error_msg("%s: %s", op, msg); 254 } else { 255 bb_error_msg("%s: %s"+4, msg); 256 } 257 longjmp(leaving, 2); 258} 259 260static arith_t oexpr(enum token n) 261{ 262 arith_t res; 263 264 res = aexpr(n); 265 if (t_lex(*++t_wp) == BOR) { 266 return oexpr(t_lex(*++t_wp)) || res; 267 } 268 t_wp--; 269 return res; 270} 271 272static arith_t aexpr(enum token n) 273{ 274 arith_t res; 275 276 res = nexpr(n); 277 if (t_lex(*++t_wp) == BAND) 278 return aexpr(t_lex(*++t_wp)) && res; 279 t_wp--; 280 return res; 281} 282 283static arith_t nexpr(enum token n) 284{ 285 if (n == UNOT) 286 return !nexpr(t_lex(*++t_wp)); 287 return primary(n); 288} 289 290static arith_t primary(enum token n) 291{ 292 arith_t res; 293 294 if (n == EOI) { 295 syntax(NULL, "argument expected"); 296 } 297 if (n == LPAREN) { 298 res = oexpr(t_lex(*++t_wp)); 299 if (t_lex(*++t_wp) != RPAREN) 300 syntax(NULL, "closing paren expected"); 301 return res; 302 } 303 if (t_wp_op && t_wp_op->op_type == UNOP) { 304 /* unary expression */ 305 if (*++t_wp == NULL) 306 syntax(t_wp_op->op_text, "argument expected"); 307 if (n == STREZ) 308 return t_wp[0][0] == '\0'; 309 if (n == STRNZ) 310 return t_wp[0][0] != '\0'; 311 if (n == FILTT) 312 return isatty(getn(*t_wp)); 313 return filstat(*t_wp, n); 314 } 315 316 t_lex(t_wp[1]); 317 if (t_wp_op && t_wp_op->op_type == BINOP) { 318 return binop(); 319 } 320 321 return t_wp[0][0] != '\0'; 322} 323 324static int binop(void) 325{ 326 const char *opnd1, *opnd2; 327 struct t_op const *op; 328 arith_t val1, val2; 329 330 opnd1 = *t_wp; 331 (void) t_lex(*++t_wp); 332 op = t_wp_op; 333 334 opnd2 = *++t_wp; 335 if (opnd2 == NULL) 336 syntax(op->op_text, "argument expected"); 337 338 if (is_int_op(op->op_num)) { 339 val1 = getn(opnd1); 340 val2 = getn(opnd2); 341 if (op->op_num == INTEQ) 342 return val1 == val2; 343 if (op->op_num == INTNE) 344 return val1 != val2; 345 if (op->op_num == INTGE) 346 return val1 >= val2; 347 if (op->op_num == INTGT) 348 return val1 > val2; 349 if (op->op_num == INTLE) 350 return val1 <= val2; 351 if (op->op_num == INTLT) 352 return val1 < val2; 353 } 354 if (is_str_op(op->op_num)) { 355 val1 = strcmp(opnd1, opnd2); 356 if (op->op_num == STREQ) 357 return val1 == 0; 358 if (op->op_num == STRNE) 359 return val1 != 0; 360 if (op->op_num == STRLT) 361 return val1 < 0; 362 if (op->op_num == STRGT) 363 return val1 > 0; 364 } 365 /* We are sure that these three are by now the only binops we didn't check 366 * yet, so we do not check if the class is correct: 367 */ 368/* if (is_file_op(op->op_num)) */ 369 { 370 struct stat b1, b2; 371 372 if (stat(opnd1, &b1) || stat(opnd2, &b2)) 373 return 0; /* false, since at least one stat failed */ 374 if (op->op_num == FILNT) 375 return b1.st_mtime > b2.st_mtime; 376 if (op->op_num == FILOT) 377 return b1.st_mtime < b2.st_mtime; 378 if (op->op_num == FILEQ) 379 return b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino; 380 } 381 return 1; /* NOTREACHED */ 382} 383 384static int filstat(char *nm, enum token mode) 385{ 386 struct stat s; 387 int i = i; /* gcc 3.x thinks it can be used uninitialized */ 388 389 if (mode == FILSYM) { 390#ifdef S_IFLNK 391 if (lstat(nm, &s) == 0) { 392 i = S_IFLNK; 393 goto filetype; 394 } 395#endif 396 return 0; 397 } 398 399 if (stat(nm, &s) != 0) 400 return 0; 401 if (mode == FILEXIST) 402 return 1; 403 if (is_file_access(mode)) { 404 if (mode == FILRD) 405 i = R_OK; 406 if (mode == FILWR) 407 i = W_OK; 408 if (mode == FILEX) 409 i = X_OK; 410 return test_eaccess(nm, i) == 0; 411 } 412 if (is_file_type(mode)) { 413 if (mode == FILREG) 414 i = S_IFREG; 415 if (mode == FILDIR) 416 i = S_IFDIR; 417 if (mode == FILCDEV) 418 i = S_IFCHR; 419 if (mode == FILBDEV) 420 i = S_IFBLK; 421 if (mode == FILFIFO) { 422#ifdef S_IFIFO 423 i = S_IFIFO; 424#else 425 return 0; 426#endif 427 } 428 if (mode == FILSOCK) { 429#ifdef S_IFSOCK 430 i = S_IFSOCK; 431#else 432 return 0; 433#endif 434 } 435 filetype: 436 return ((s.st_mode & S_IFMT) == i); 437 } 438 if (is_file_bit(mode)) { 439 if (mode == FILSUID) 440 i = S_ISUID; 441 if (mode == FILSGID) 442 i = S_ISGID; 443 if (mode == FILSTCK) 444 i = S_ISVTX; 445 return ((s.st_mode & i) != 0); 446 } 447 if (mode == FILGZ) 448 return s.st_size > 0L; 449 if (mode == FILUID) 450 return s.st_uid == geteuid(); 451 if (mode == FILGID) 452 return s.st_gid == getegid(); 453 return 1; /* NOTREACHED */ 454} 455 456static enum token t_lex(char *s) 457{ 458 const struct t_op *op; 459 460 t_wp_op = NULL; 461 if (s == NULL) { 462 return EOI; 463 } 464 465 op = ops; 466 do { 467 if (strcmp(s, op->op_text) == 0) { 468 t_wp_op = op; 469 return op->op_num; 470 } 471 op++; 472 } while (op < ops + ARRAY_SIZE(ops)); 473 474 return OPERAND; 475} 476 477/* atoi with error detection */ 478static arith_t getn(const char *s) 479{ 480 char *p; 481#if ENABLE_FEATURE_TEST_64 482 long long r; 483#else 484 long r; 485#endif 486 487 errno = 0; 488#if ENABLE_FEATURE_TEST_64 489 r = strtoll(s, &p, 10); 490#else 491 r = strtol(s, &p, 10); 492#endif 493 494 if (errno != 0) 495 syntax(s, "out of range"); 496 497 if (*(skip_whitespace(p))) 498 syntax(s, "bad number"); 499 500 return r; 501} 502 503/* UNUSED 504static int newerf(const char *f1, const char *f2) 505{ 506 struct stat b1, b2; 507 508 return (stat(f1, &b1) == 0 && 509 stat(f2, &b2) == 0 && b1.st_mtime > b2.st_mtime); 510} 511 512static int olderf(const char *f1, const char *f2) 513{ 514 struct stat b1, b2; 515 516 return (stat(f1, &b1) == 0 && 517 stat(f2, &b2) == 0 && b1.st_mtime < b2.st_mtime); 518} 519 520static int equalf(const char *f1, const char *f2) 521{ 522 struct stat b1, b2; 523 524 return (stat(f1, &b1) == 0 && 525 stat(f2, &b2) == 0 && 526 b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino); 527} 528*/ 529 530/* Do the same thing access(2) does, but use the effective uid and gid, 531 and don't make the mistake of telling root that any file is 532 executable. */ 533static int test_eaccess(char *path, int mode) 534{ 535 struct stat st; 536 unsigned int euid = geteuid(); 537 538 if (stat(path, &st) < 0) 539 return -1; 540 541 if (euid == 0) { 542 /* Root can read or write any file. */ 543 if (mode != X_OK) 544 return 0; 545 546 /* Root can execute any file that has any one of the execute 547 bits set. */ 548 if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) 549 return 0; 550 } 551 552 if (st.st_uid == euid) /* owner */ 553 mode <<= 6; 554 else if (is_a_group_member(st.st_gid)) 555 mode <<= 3; 556 557 if (st.st_mode & mode) 558 return 0; 559 560 return -1; 561} 562 563static void initialize_group_array(void) 564{ 565 ngroups = getgroups(0, NULL); 566 if (ngroups > 0) { 567 /* We realloc, because test_main can be entered repeatedly by shell. 568 * Testcase (ash): 'while true; do test -x some_file; done' 569 * and watch top. (some_file must have owner != you) */ 570 group_array = xrealloc(group_array, ngroups * sizeof(gid_t)); 571 getgroups(ngroups, group_array); 572 } 573} 574 575/* Return non-zero if GID is one that we have in our groups list. */ 576// see toplevel TODO file: 577// possible code duplication ingroup() and is_a_group_member() 578static int is_a_group_member(gid_t gid) 579{ 580 int i; 581 582 /* Short-circuit if possible, maybe saving a call to getgroups(). */ 583 if (gid == getgid() || gid == getegid()) 584 return 1; 585 586 if (ngroups == 0) 587 initialize_group_array(); 588 589 /* Search through the list looking for GID. */ 590 for (i = 0; i < ngroups; i++) 591 if (gid == group_array[i]) 592 return 1; 593 594 return 0; 595} 596