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