1/*	$OpenBSD: expand.c,v 1.18 2019/06/28 13:35:03 deraadt Exp $	*/
2
3/*
4 * Copyright (c) 1983 Regents of the University of California.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <dirent.h>
33#include <errno.h>
34#include <fcntl.h>
35#include <stdlib.h>
36#include <string.h>
37#include <unistd.h>
38
39#include "client.h"
40
41#define	MAXEARGS	2048
42#define LC 		'{'
43#define RC 		'}'
44
45static char	shchars[] = "${[*?";
46
47int		which;		/* bit mask of types to expand */
48int		eargc;		/* expanded arg count */
49char	      **eargv;		/* expanded arg vectors */
50char	       *path;
51char	       *pathp;
52char	       *lastpathp;
53char	       *tilde;		/* "~user" if not expanding tilde, else "" */
54char	       *tpathp;
55char		pathbuf[BUFSIZ];
56
57int		expany;		/* any expansions done? */
58char	       *entp;
59char	      **sortbase;
60char 	       *argvbuf[MAXEARGS];
61
62#define sort()	qsort((char *)sortbase, &eargv[eargc] - sortbase, \
63		      sizeof(*sortbase), argcmp), sortbase = &eargv[eargc]
64
65static void Cat(u_char *, u_char *);
66static void addpath(int);
67static int argcmp(const void *, const void *);
68
69static void
70Cat(u_char *s1, u_char *s2)		/* quote in s1 and s2 */
71{
72	char *cp;
73	int len = strlen((char *)s1) + strlen((char *)s2) + 2;
74
75	if ((eargc + 1) >= MAXEARGS) {
76		yyerror("Too many names");
77		return;
78	}
79
80	eargv[++eargc] = NULL;
81	eargv[eargc - 1] = cp = xmalloc(len);
82
83	do {
84		if (*s1 == QUOTECHAR)
85			s1++;
86	} while ((*cp++ = *s1++) != '\0');
87	cp--;
88	do {
89		if (*s2 == QUOTECHAR)
90			s2++;
91	} while ((*cp++ = *s2++) != '\0');
92}
93
94static void
95addpath(int c)
96{
97	if (pathp >= lastpathp) {
98		yyerror("Pathname too long");
99		return;
100	} else {
101		*pathp++ = c;
102		*pathp = CNULL;
103	}
104}
105
106/*
107 * Take a list of names and expand any macros, etc.
108 * wh = E_VARS if expanding variables.
109 * wh = E_SHELL if expanding shell characters.
110 * wh = E_TILDE if expanding `~'.
111 * or any of these or'ed together.
112 *
113 * Major portions of this were snarfed from csh/sh.glob.c.
114 */
115struct namelist *
116expand(struct namelist *list, int wh)		/* quote in list->n_name */
117{
118	struct namelist *nl, *prev;
119	int n;
120
121	if (debug)
122		debugmsg(DM_CALL, "expand(%p, %d) start, list = %s",
123			 list, wh, getnlstr(list));
124
125	if (wh == 0)
126		fatalerr("expand() contains invalid 'wh' argument.");
127
128	which = wh;
129	path = tpathp = pathp = pathbuf;
130	*pathp = CNULL;
131	lastpathp = &pathbuf[sizeof pathbuf - 2];
132	tilde = "";
133	eargc = 0;
134	eargv = sortbase = argvbuf;
135	*eargv = NULL;
136
137	/*
138	 * Walk the name list and expand names into eargv[];
139	 */
140	for (nl = list; nl != NULL; nl = nl->n_next)
141		expstr((u_char *)nl->n_name);
142	/*
143	 * Take expanded list of names from eargv[] and build a new list.
144	 */
145	list = prev = NULL;
146	for (n = 0; n < eargc; n++) {
147		nl = makenl(NULL);
148		nl->n_name = eargv[n];
149		if (prev == NULL)
150			list = prev = nl;
151		else {
152			prev->n_next = nl;
153			prev = nl;
154		}
155	}
156
157	return(list);
158}
159
160/*
161 * xstrchr() is a version of strchr() that
162 * handles u_char buffers.
163 */
164u_char *
165xstrchr(u_char *str, int ch)
166{
167	u_char *cp;
168
169	for (cp = str; cp && *cp != CNULL; ++cp)
170		if (ch == *cp)
171			return(cp);
172
173	return(NULL);
174}
175
176void
177expstr(u_char *s)
178{
179	u_char *cp, *cp1;
180	struct namelist *tp;
181	u_char *tail;
182	u_char ebuf[BUFSIZ];
183	u_char varbuff[BUFSIZ];
184	int savec, oeargc;
185
186	if (s == NULL || *s == CNULL)
187		return;
188
189	/*
190	 * Remove quoted characters
191	 */
192	if (IS_ON(which, E_VARS)) {
193		if (strlen((char *)s) > sizeof(varbuff)) {
194			yyerror("Variable is too large.");
195			return;
196		}
197		for (cp = s, cp1 = varbuff; cp && *cp; ++cp) {
198			/*
199			 * remove quoted character if the next
200			 * character is not $
201			 */
202			if (*cp == QUOTECHAR && *(cp+1) != '$')
203				++cp;
204			else
205				*cp1++ = *cp;
206		}
207		*cp1 = CNULL;
208		s = varbuff;
209	}
210
211	/*
212	 * Consider string 's' a variable that should be expanded if
213	 * there is a '$' in 's' that is not quoted.
214	 */
215	if (IS_ON(which, E_VARS) &&
216	    ((cp = xstrchr(s, '$')) && !(cp > s && *(cp-1) == QUOTECHAR))) {
217		*cp++ = CNULL;
218		if (*cp == CNULL) {
219			yyerror("no variable name after '$'");
220			return;
221		}
222		if (*cp == LC) {
223			cp++;
224			for (cp1 = cp; ; cp1 = tail + 1) {
225				if ((tail = xstrchr(cp1, RC)) == NULL) {
226					yyerror("unmatched '{'");
227					return;
228				}
229				if (tail[-1] != QUOTECHAR) break;
230			}
231			*tail++ = savec = CNULL;
232			if (*cp == CNULL) {
233				yyerror("no variable name after '$'");
234				return;
235			}
236		} else {
237			tail = cp + 1;
238			savec = *tail;
239			*tail = CNULL;
240		}
241		tp = lookup((char *)cp, LOOKUP, NULL);
242		if (savec != CNULL)
243			*tail = savec;
244		if (tp != NULL) {
245			for (; tp != NULL; tp = tp->n_next) {
246				(void) snprintf((char *)ebuf, sizeof(ebuf),
247					        "%s%s%s", s, tp->n_name, tail);
248				expstr(ebuf);
249			}
250			return;
251		}
252		(void) snprintf((char *)ebuf, sizeof(ebuf), "%s%s", s, tail);
253		expstr(ebuf);
254		return;
255	}
256	if ((which & ~E_VARS) == 0 || !strcmp((char *)s, "{") ||
257	    !strcmp((char *)s, "{}")) {
258		Cat(s, (u_char *)"");
259		sort();
260		return;
261	}
262	if (*s == '~') {
263		if ((cp = strchr(s, '/')) == NULL) {
264			tilde = "~";
265			s++;
266		} else {
267			tilde = memcpy(ebuf, s, (cp - s));
268			ebuf[cp - s] = '\0';
269			s = cp;
270		}
271		cp = exptilde(path, tilde, sizeof(pathbuf));
272		tpathp = pathp = (char *)cp;
273	} else {
274		tpathp = pathp = path;
275		tilde = "";
276	}
277	*pathp = CNULL;
278	if (!(which & E_SHELL)) {
279		if (which & E_TILDE)
280			Cat((u_char *)path, s);
281		else
282			Cat((u_char *)tilde, s);
283		sort();
284		return;
285	}
286	oeargc = eargc;
287	expany = 0;
288	expsh(s);
289	if (eargc == oeargc)
290		Cat(s, (u_char *)"");		/* "nonomatch" is set */
291	sort();
292}
293
294static int
295argcmp(const void *v1, const void *v2)
296{
297	const char *const *a1 = v1, *const *a2 = v2;
298
299	return (strcmp(*a1, *a2));
300}
301
302/*
303 * If there are any Shell meta characters in the name,
304 * expand into a list, after searching directory
305 */
306void
307expsh(u_char *s)			/* quote in s */
308{
309	u_char *cp, *oldcp;
310	char *spathp;
311	struct stat stb;
312
313	spathp = pathp;
314	cp = s;
315	while (!any(*cp, shchars)) {
316		if (*cp == CNULL) {
317			if (!expany || stat(path, &stb) >= 0) {
318				if (which & E_TILDE)
319					Cat((u_char *)path, (u_char *)"");
320				else
321					Cat((u_char *)tilde, (u_char *)tpathp);
322			}
323			goto endit;
324		}
325		if (*cp == QUOTECHAR) cp++;
326		addpath(*cp++);
327	}
328	oldcp = cp;
329	while (cp > s && *cp != '/')
330		cp--, pathp--;
331	if (*cp == '/')
332		cp++, pathp++;
333	*pathp = CNULL;
334	if (*oldcp == '{') {
335		(void) execbrc(cp, NULL);
336		return;
337	}
338	matchdir((char *)cp);
339endit:
340	pathp = spathp;
341	*pathp = CNULL;
342}
343
344void
345matchdir(char *pattern)			/* quote in pattern */
346{
347	struct stat stb;
348	struct dirent *dp;
349	DIR *dirp;
350
351	dirp = opendir(path);
352	if (dirp == NULL) {
353		if (expany)
354			return;
355		goto patherr2;
356	}
357	if (fstat(dirfd(dirp), &stb) == -1)
358		goto patherr1;
359	if (!S_ISDIR(stb.st_mode)) {
360		errno = ENOTDIR;
361		goto patherr1;
362	}
363	while ((dp = readdir(dirp)) != NULL)
364		if (match(dp->d_name, pattern)) {
365			if (which & E_TILDE)
366				Cat((u_char *)path, (u_char *)dp->d_name);
367			else {
368				(void) strlcpy(pathp, dp->d_name,
369				    lastpathp - pathp + 2);
370				Cat((u_char *)tilde, (u_char *)tpathp);
371				*pathp = CNULL;
372			}
373		}
374	closedir(dirp);
375	return;
376
377patherr1:
378	closedir(dirp);
379patherr2:
380	(void) strlcat(path, ": ", lastpathp - path + 2);
381	(void) strlcat(path, SYSERR, lastpathp - path + 2);
382	yyerror(path);
383}
384
385int
386execbrc(u_char *p, u_char *s)		/* quote in p */
387{
388	u_char restbuf[BUFSIZ + 2];
389	u_char *pe, *pm, *pl;
390	int brclev = 0;
391	u_char *lm, savec;
392	char *spathp;
393
394	for (lm = restbuf; *p != '{'; *lm++ = *p++)
395		if (*p == QUOTECHAR) *lm++ = *p++;
396
397	for (pe = ++p; *pe; pe++)
398		switch (*pe) {
399
400		case '{':
401			brclev++;
402			continue;
403
404		case '}':
405			if (brclev == 0)
406				goto pend;
407			brclev--;
408			continue;
409
410		case '[':
411			for (pe++; *pe && *pe != ']'; pe++)
412				if (*p == QUOTECHAR) pe++;
413			if (!*pe)
414				yyerror("Missing ']'");
415			continue;
416
417		case QUOTECHAR:		/* skip this character */
418			pe++;
419			continue;
420		}
421pend:
422	if (brclev || !*pe) {
423		yyerror("Missing '}'");
424		return (0);
425	}
426	for (pl = pm = p; pm <= pe; pm++)
427		/* the strip code was a noop */
428		switch (*pm) {
429
430		case '{':
431			brclev++;
432			continue;
433
434		case '}':
435			if (brclev) {
436				brclev--;
437				continue;
438			}
439			goto doit;
440
441		case ',':
442			if (brclev)
443				continue;
444doit:
445			savec = *pm;
446			*pm = 0;
447			*lm = 0;
448			(void) strlcat((char *)restbuf, (char *)pl,
449			    sizeof(restbuf));
450			(void) strlcat((char *)restbuf, (char *)pe + 1,
451			    sizeof(restbuf));
452			*pm = savec;
453			if (s == 0) {
454				spathp = pathp;
455				expsh(restbuf);
456				pathp = spathp;
457				*pathp = 0;
458			} else if (amatch((char *)s, restbuf))
459				return (1);
460			sort();
461			pl = pm + 1;
462			continue;
463
464		case '[':
465			for (pm++; *pm && *pm != ']'; pm++)
466				if (*pm == QUOTECHAR) pm++;
467			if (!*pm)
468				yyerror("Missing ']'");
469			continue;
470
471		case QUOTECHAR:			/* skip one character */
472			pm++;
473			continue;
474		}
475	return (0);
476}
477
478int
479match(char *s, char *p)				/* quote in p */
480{
481	int c;
482	char *sentp;
483	char sexpany = expany;
484
485	if (*s == '.' && *p != '.')
486		return (0);
487	sentp = entp;
488	entp = s;
489	c = amatch(s, p);
490	entp = sentp;
491	expany = sexpany;
492	return (c);
493}
494
495int
496amatch(char *s, u_char *p)			/* quote in p */
497{
498	int scc;
499	int ok, lc;
500	char *spathp;
501	struct stat stb;
502	int c, cc;
503
504	expany = 1;
505	for (;;) {
506		scc = *s++;
507		switch (c = *p++) {
508
509		case '{':
510			return (execbrc((u_char *)p - 1, (u_char *)s - 1));
511
512		case '[':
513			ok = 0;
514			lc = 077777;
515			while ((cc = *p++) != '\0') {
516				if (cc == ']') {
517					if (ok)
518						break;
519					return (0);
520				}
521				if (cc == QUOTECHAR) cc = *p++;
522				if (cc == '-') {
523					if (lc <= scc && scc <= (int)*p++)
524						ok++;
525				} else
526					if (scc == (lc = cc))
527						ok++;
528			}
529			if (cc == 0) {
530				yyerror("Missing ']'");
531				return (0);
532			}
533			continue;
534
535		case '*':
536			if (!*p)
537				return (1);
538			if (*p == '/') {
539				p++;
540				goto slash;
541			}
542			for (s--; *s; s++)
543				if (amatch(s, p))
544					return (1);
545			return (0);
546
547		case CNULL:
548			return (scc == CNULL);
549
550		default:
551			if (c != scc)
552				return (0);
553			continue;
554
555		case '?':
556			if (scc == CNULL)
557				return (0);
558			continue;
559
560		case '/':
561			if (scc)
562				return (0);
563slash:
564			s = entp;
565			spathp = pathp;
566			while (*s)
567				addpath(*s++);
568			addpath('/');
569			if (stat(path, &stb) == 0 && S_ISDIR(stb.st_mode)) {
570				if (*p == CNULL) {
571					if (which & E_TILDE) {
572						Cat((u_char *)path,
573						    (u_char *)"");
574					} else {
575						Cat((u_char *)tilde,
576						    (u_char *)tpathp);
577					}
578				} else
579					expsh(p);
580			}
581			pathp = spathp;
582			*pathp = CNULL;
583			return (0);
584		}
585	}
586}
587