0,0 → 1,583 |
/* $NetBSD: test.c,v 1.22 2000/04/09 23:24:59 christos Exp $ */ |
|
/* |
* test(1); version 7-like -- author Erik Baalbergen |
* modified by Eric Gisin to be used as built-in. |
* modified by Arnold Robbins to add SVR3 compatibility |
* (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). |
* modified by J.T. Conklin for NetBSD. |
* |
* This program is in the Public Domain. |
*/ |
|
#include <sys/cdefs.h> |
#ifndef lint |
__RCSID("$NetBSD: test.c,v 1.22 2000/04/09 23:24:59 christos Exp $"); |
#endif |
|
#include <sys/types.h> |
#include <sys/stat.h> |
#include <unistd.h> |
#include <ctype.h> |
#include <errno.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <string.h> |
#include <err.h> |
#ifdef __STDC__ |
#include <stdarg.h> |
#else |
#include <varargs.h> |
#endif |
|
/* test(1) accepts the following grammar: |
oexpr ::= aexpr | aexpr "-o" oexpr ; |
aexpr ::= nexpr | nexpr "-a" aexpr ; |
nexpr ::= primary | "!" primary |
primary ::= unary-operator operand |
| operand binary-operator operand |
| operand |
| "(" oexpr ")" |
; |
unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| |
"-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; |
|
binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| |
"-nt"|"-ot"|"-ef"; |
operand ::= <any legal UNIX file name> |
*/ |
|
enum token { |
EOI, |
FILRD, |
FILWR, |
FILEX, |
FILEXIST, |
FILREG, |
FILDIR, |
FILCDEV, |
FILBDEV, |
FILFIFO, |
FILSOCK, |
FILSYM, |
FILGZ, |
FILTT, |
FILSUID, |
FILSGID, |
FILSTCK, |
FILNT, |
FILOT, |
FILEQ, |
FILUID, |
FILGID, |
STREZ, |
STRNZ, |
STREQ, |
STRNE, |
STRLT, |
STRGT, |
INTEQ, |
INTNE, |
INTGE, |
INTGT, |
INTLE, |
INTLT, |
UNOT, |
BAND, |
BOR, |
LPAREN, |
RPAREN, |
OPERAND |
}; |
|
enum token_types { |
UNOP, |
BINOP, |
BUNOP, |
BBINOP, |
PAREN |
}; |
|
static struct t_op { |
const char *op_text; |
short op_num, op_type; |
} const ops [] = { |
{"-r", FILRD, UNOP}, |
{"-w", FILWR, UNOP}, |
{"-x", FILEX, UNOP}, |
{"-e", FILEXIST,UNOP}, |
{"-f", FILREG, UNOP}, |
{"-d", FILDIR, UNOP}, |
{"-c", FILCDEV,UNOP}, |
{"-b", FILBDEV,UNOP}, |
{"-p", FILFIFO,UNOP}, |
{"-u", FILSUID,UNOP}, |
{"-g", FILSGID,UNOP}, |
{"-k", FILSTCK,UNOP}, |
{"-s", FILGZ, UNOP}, |
{"-t", FILTT, UNOP}, |
{"-z", STREZ, UNOP}, |
{"-n", STRNZ, UNOP}, |
{"-h", FILSYM, UNOP}, /* for backwards compat */ |
{"-O", FILUID, UNOP}, |
{"-G", FILGID, UNOP}, |
{"-L", FILSYM, UNOP}, |
{"-S", FILSOCK,UNOP}, |
{"=", STREQ, BINOP}, |
{"!=", STRNE, BINOP}, |
{"<", STRLT, BINOP}, |
{">", STRGT, BINOP}, |
{"-eq", INTEQ, BINOP}, |
{"-ne", INTNE, BINOP}, |
{"-ge", INTGE, BINOP}, |
{"-gt", INTGT, BINOP}, |
{"-le", INTLE, BINOP}, |
{"-lt", INTLT, BINOP}, |
{"-nt", FILNT, BINOP}, |
{"-ot", FILOT, BINOP}, |
{"-ef", FILEQ, BINOP}, |
{"!", UNOT, BUNOP}, |
{"-a", BAND, BBINOP}, |
{"-o", BOR, BBINOP}, |
{"(", LPAREN, PAREN}, |
{")", RPAREN, PAREN}, |
{0, 0, 0} |
}; |
|
static char **t_wp; |
static struct t_op const *t_wp_op; |
static gid_t *group_array = NULL; |
static int ngroups; |
|
static void syntax __P((const char *, const char *)); |
static int oexpr __P((enum token)); |
static int aexpr __P((enum token)); |
static int nexpr __P((enum token)); |
static int primary __P((enum token)); |
static int binop __P((void)); |
static int filstat __P((char *, enum token)); |
static enum token t_lex __P((char *)); |
static int isoperand __P((void)); |
static int getn __P((const char *)); |
static int newerf __P((const char *, const char *)); |
static int olderf __P((const char *, const char *)); |
static int equalf __P((const char *, const char *)); |
static int test_eaccess(); |
static int bash_group_member(); |
static void initialize_group_array(); |
|
#if defined(SHELL) |
extern void error __P((const char *, ...)) __attribute__((__noreturn__)); |
#else |
static void error __P((const char *, ...)) __attribute__((__noreturn__)); |
|
static void |
#ifdef __STDC__ |
error(const char *msg, ...) |
#else |
error(va_alist) |
va_dcl |
#endif |
{ |
va_list ap; |
#ifndef __STDC__ |
const char *msg; |
|
va_start(ap); |
msg = va_arg(ap, const char *); |
#else |
va_start(ap, msg); |
#endif |
verrx(2, msg, ap); |
/*NOTREACHED*/ |
va_end(ap); |
} |
#endif |
|
#ifdef SHELL |
int testcmd __P((int, char **)); |
|
int |
testcmd(argc, argv) |
int argc; |
char **argv; |
#else |
int main __P((int, char **)); |
|
int |
main(argc, argv) |
int argc; |
char **argv; |
#endif |
{ |
int res; |
|
|
if (strcmp(argv[0], "[") == 0) { |
if (strcmp(argv[--argc], "]")) |
error("missing ]"); |
argv[argc] = NULL; |
} |
|
if (argc < 2) |
return 1; |
|
t_wp = &argv[1]; |
res = !oexpr(t_lex(*t_wp)); |
|
if (*t_wp != NULL && *++t_wp != NULL) |
syntax(*t_wp, "unexpected operator"); |
|
return res; |
} |
|
static void |
syntax(op, msg) |
const char *op; |
const char *msg; |
{ |
if (op && *op) |
error("%s: %s", op, msg); |
else |
error("%s", msg); |
} |
|
static int |
oexpr(n) |
enum token n; |
{ |
int res; |
|
res = aexpr(n); |
if (t_lex(*++t_wp) == BOR) |
return oexpr(t_lex(*++t_wp)) || res; |
t_wp--; |
return res; |
} |
|
static int |
aexpr(n) |
enum token n; |
{ |
int res; |
|
res = nexpr(n); |
if (t_lex(*++t_wp) == BAND) |
return aexpr(t_lex(*++t_wp)) && res; |
t_wp--; |
return res; |
} |
|
static int |
nexpr(n) |
enum token n; /* token */ |
{ |
if (n == UNOT) |
return !nexpr(t_lex(*++t_wp)); |
return primary(n); |
} |
|
static int |
primary(n) |
enum token n; |
{ |
enum token nn; |
int res; |
|
if (n == EOI) |
return 0; /* missing expression */ |
if (n == LPAREN) { |
if ((nn = t_lex(*++t_wp)) == RPAREN) |
return 0; /* missing expression */ |
res = oexpr(nn); |
if (t_lex(*++t_wp) != RPAREN) |
syntax(NULL, "closing paren expected"); |
return res; |
} |
if (t_wp_op && t_wp_op->op_type == UNOP) { |
/* unary expression */ |
if (*++t_wp == NULL) |
syntax(t_wp_op->op_text, "argument expected"); |
switch (n) { |
case STREZ: |
return strlen(*t_wp) == 0; |
case STRNZ: |
return strlen(*t_wp) != 0; |
case FILTT: |
return isatty(getn(*t_wp)); |
default: |
return filstat(*t_wp, n); |
} |
} |
|
if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { |
return binop(); |
} |
|
return strlen(*t_wp) > 0; |
} |
|
static int |
binop() |
{ |
const char *opnd1, *opnd2; |
struct t_op const *op; |
|
opnd1 = *t_wp; |
(void) t_lex(*++t_wp); |
op = t_wp_op; |
|
if ((opnd2 = *++t_wp) == (char *)0) |
syntax(op->op_text, "argument expected"); |
|
switch (op->op_num) { |
case STREQ: |
return strcmp(opnd1, opnd2) == 0; |
case STRNE: |
return strcmp(opnd1, opnd2) != 0; |
case STRLT: |
return strcmp(opnd1, opnd2) < 0; |
case STRGT: |
return strcmp(opnd1, opnd2) > 0; |
case INTEQ: |
return getn(opnd1) == getn(opnd2); |
case INTNE: |
return getn(opnd1) != getn(opnd2); |
case INTGE: |
return getn(opnd1) >= getn(opnd2); |
case INTGT: |
return getn(opnd1) > getn(opnd2); |
case INTLE: |
return getn(opnd1) <= getn(opnd2); |
case INTLT: |
return getn(opnd1) < getn(opnd2); |
case FILNT: |
return newerf (opnd1, opnd2); |
case FILOT: |
return olderf (opnd1, opnd2); |
case FILEQ: |
return equalf (opnd1, opnd2); |
default: |
abort(); |
/* NOTREACHED */ |
} |
} |
|
static int |
filstat(nm, mode) |
char *nm; |
enum token mode; |
{ |
struct stat s; |
|
if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) |
return 0; |
|
switch (mode) { |
case FILRD: |
return test_eaccess(nm, R_OK) == 0; |
case FILWR: |
return test_eaccess(nm, W_OK) == 0; |
case FILEX: |
return test_eaccess(nm, X_OK) == 0; |
case FILEXIST: |
return 1; |
case FILREG: |
return S_ISREG(s.st_mode); |
case FILDIR: |
return S_ISDIR(s.st_mode); |
case FILCDEV: |
return S_ISCHR(s.st_mode); |
case FILBDEV: |
return S_ISBLK(s.st_mode); |
case FILFIFO: |
return S_ISFIFO(s.st_mode); |
case FILSOCK: |
return S_ISSOCK(s.st_mode); |
case FILSYM: |
return S_ISLNK(s.st_mode); |
case FILSUID: |
return (s.st_mode & S_ISUID) != 0; |
case FILSGID: |
return (s.st_mode & S_ISGID) != 0; |
case FILSTCK: |
return (s.st_mode & S_ISVTX) != 0; |
case FILGZ: |
return s.st_size > (off_t)0; |
case FILUID: |
return s.st_uid == geteuid(); |
case FILGID: |
return s.st_gid == getegid(); |
default: |
return 1; |
} |
} |
|
static enum token |
t_lex(s) |
char *s; |
{ |
struct t_op const *op = ops; |
|
if (s == 0) { |
t_wp_op = (struct t_op *)0; |
return EOI; |
} |
while (op->op_text) { |
if (strcmp(s, op->op_text) == 0) { |
if ((op->op_type == UNOP && isoperand()) || |
(op->op_num == LPAREN && *(t_wp+1) == 0)) |
break; |
t_wp_op = op; |
return op->op_num; |
} |
op++; |
} |
t_wp_op = (struct t_op *)0; |
return OPERAND; |
} |
|
static int |
isoperand() |
{ |
struct t_op const *op = ops; |
char *s; |
char *t; |
|
if ((s = *(t_wp+1)) == 0) |
return 1; |
if ((t = *(t_wp+2)) == 0) |
return 0; |
while (op->op_text) { |
if (strcmp(s, op->op_text) == 0) |
return op->op_type == BINOP && |
(t[0] != ')' || t[1] != '\0'); |
op++; |
} |
return 0; |
} |
|
/* atoi with error detection */ |
static int |
getn(s) |
const char *s; |
{ |
char *p; |
long r; |
|
errno = 0; |
r = strtol(s, &p, 10); |
|
if (errno != 0) |
error("%s: out of range", s); |
|
while (isspace((unsigned char)*p)) |
p++; |
|
if (*p) |
error("%s: bad number", s); |
|
return (int) r; |
} |
|
static int |
newerf (f1, f2) |
const char *f1, *f2; |
{ |
struct stat b1, b2; |
|
return (stat (f1, &b1) == 0 && |
stat (f2, &b2) == 0 && |
b1.st_mtime > b2.st_mtime); |
} |
|
static int |
olderf (f1, f2) |
const char *f1, *f2; |
{ |
struct stat b1, b2; |
|
return (stat (f1, &b1) == 0 && |
stat (f2, &b2) == 0 && |
b1.st_mtime < b2.st_mtime); |
} |
|
static int |
equalf (f1, f2) |
const char *f1, *f2; |
{ |
struct stat b1, b2; |
|
return (stat (f1, &b1) == 0 && |
stat (f2, &b2) == 0 && |
b1.st_dev == b2.st_dev && |
b1.st_ino == b2.st_ino); |
} |
|
/* Do the same thing access(2) does, but use the effective uid and gid, |
and don't make the mistake of telling root that any file is |
executable. */ |
static int |
test_eaccess (path, mode) |
char *path; |
int mode; |
{ |
struct stat st; |
int euid = geteuid(); |
|
if (stat (path, &st) < 0) |
return (-1); |
|
if (euid == 0) { |
/* Root can read or write any file. */ |
if (mode != X_OK) |
return (0); |
|
/* Root can execute any file that has any one of the execute |
bits set. */ |
if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) |
return (0); |
} |
|
if (st.st_uid == euid) /* owner */ |
mode <<= 6; |
else if (bash_group_member (st.st_gid)) |
mode <<= 3; |
|
if (st.st_mode & mode) |
return (0); |
|
return (-1); |
} |
|
static void |
initialize_group_array () |
{ |
ngroups = getgroups(0, NULL); |
group_array = malloc(ngroups * sizeof(gid_t)); |
if (!group_array) |
error(strerror(ENOMEM)); |
getgroups(ngroups, group_array); |
} |
|
/* Return non-zero if GID is one that we have in our groups list. */ |
static int |
bash_group_member (gid) |
gid_t gid; |
{ |
register int i; |
|
/* Short-circuit if possible, maybe saving a call to getgroups(). */ |
if (gid == getgid() || gid == getegid()) |
return (1); |
|
if (ngroups == 0) |
initialize_group_array (); |
|
/* Search through the list looking for GID. */ |
for (i = 0; i < ngroups; i++) |
if (gid == group_array[i]) |
return (1); |
|
return (0); |
} |