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