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