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