xref: /openbsd-src/usr.bin/rdist/expand.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: expand.c,v 1.15 2015/01/20 09:00:16 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 #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 
45 static char	shchars[] = "${[*?";
46 
47 int		which;		/* bit mask of types to expand */
48 int		eargc;		/* expanded arg count */
49 char	      **eargv;		/* expanded arg vectors */
50 char	       *path;
51 char	       *pathp;
52 char	       *lastpathp;
53 char	       *tilde;		/* "~user" if not expanding tilde, else "" */
54 char	       *tpathp;
55 
56 int		expany;		/* any expansions done? */
57 char	       *entp;
58 char	      **sortbase;
59 char 	       *argvbuf[MAXEARGS];
60 
61 #define sort()	qsort((char *)sortbase, &eargv[eargc] - sortbase, \
62 		      sizeof(*sortbase), argcmp), sortbase = &eargv[eargc]
63 
64 static void Cat(u_char *, u_char *);
65 static void addpath(int);
66 static int argcmp(const void *, const void *);
67 
68 static void
69 Cat(u_char *s1, u_char *s2)		/* quote in s1 and s2 */
70 {
71 	char *cp;
72 	int len = strlen((char *)s1) + strlen((char *)s2) + 2;
73 
74 	if ((eargc + 1) >= MAXEARGS) {
75 		yyerror("Too many names");
76 		return;
77 	}
78 
79 	eargv[++eargc] = NULL;
80 	eargv[eargc - 1] = cp = xmalloc(len);
81 
82 	do {
83 		if (*s1 == QUOTECHAR)
84 			s1++;
85 	} while ((*cp++ = *s1++) != '\0');
86 	cp--;
87 	do {
88 		if (*s2 == QUOTECHAR)
89 			s2++;
90 	} while ((*cp++ = *s2++) != '\0');
91 }
92 
93 static void
94 addpath(int c)
95 {
96 	if (pathp >= lastpathp) {
97 		yyerror("Pathname too long");
98 		return;
99 	} else {
100 		*pathp++ = c;
101 		*pathp = CNULL;
102 	}
103 }
104 
105 /*
106  * Take a list of names and expand any macros, etc.
107  * wh = E_VARS if expanding variables.
108  * wh = E_SHELL if expanding shell characters.
109  * wh = E_TILDE if expanding `~'.
110  * or any of these or'ed together.
111  *
112  * Major portions of this were snarfed from csh/sh.glob.c.
113  */
114 struct namelist *
115 expand(struct namelist *list, int wh)		/* quote in list->n_name */
116 {
117 	struct namelist *nl, *prev;
118 	int n;
119 	char pathbuf[BUFSIZ];
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  */
164 u_char *
165 xstrchr(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 
176 void
177 expstr(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 		cp = ++s;
264 		if (*cp == CNULL || *cp == '/') {
265 			tilde = "~";
266 			cp1 = (u_char *)homedir;
267 		} else {
268 			tilde = (char *)(cp1 = ebuf);
269 			*cp1++ = '~';
270 			do
271 				*cp1++ = *cp++;
272 			while (*cp && *cp != '/');
273 			*cp1 = CNULL;
274 			if (pw == NULL || strcmp(pw->pw_name,
275 						 (char *)ebuf+1) != 0) {
276 				if ((pw = getpwnam((char *)ebuf+1)) == NULL) {
277 					strlcat((char *)ebuf,
278 					        ": unknown user name",
279 					        sizeof(ebuf));
280 					yyerror((char *)ebuf+1);
281 					return;
282 				}
283 			}
284 			cp1 = (u_char *)pw->pw_dir;
285 			s = cp;
286 		}
287 		for (cp = (u_char *)path; (*cp++ = *cp1++) != '\0'; )
288 			continue;
289 		tpathp = pathp = (char *)cp - 1;
290 	} else {
291 		tpathp = pathp = path;
292 		tilde = "";
293 	}
294 	*pathp = CNULL;
295 	if (!(which & E_SHELL)) {
296 		if (which & E_TILDE)
297 			Cat((u_char *)path, s);
298 		else
299 			Cat((u_char *)tilde, s);
300 		sort();
301 		return;
302 	}
303 	oeargc = eargc;
304 	expany = 0;
305 	expsh(s);
306 	if (eargc == oeargc)
307 		Cat(s, (u_char *)"");		/* "nonomatch" is set */
308 	sort();
309 }
310 
311 static int
312 argcmp(const void *v1, const void *v2)
313 {
314 	const char *const *a1 = v1, *const *a2 = v2;
315 
316 	return (strcmp(*a1, *a2));
317 }
318 
319 /*
320  * If there are any Shell meta characters in the name,
321  * expand into a list, after searching directory
322  */
323 void
324 expsh(u_char *s)			/* quote in s */
325 {
326 	u_char *cp, *oldcp;
327 	char *spathp;
328 	struct stat stb;
329 
330 	spathp = pathp;
331 	cp = s;
332 	while (!any(*cp, shchars)) {
333 		if (*cp == CNULL) {
334 			if (!expany || stat(path, &stb) >= 0) {
335 				if (which & E_TILDE)
336 					Cat((u_char *)path, (u_char *)"");
337 				else
338 					Cat((u_char *)tilde, (u_char *)tpathp);
339 			}
340 			goto endit;
341 		}
342 		if (*cp == QUOTECHAR) cp++;
343 		addpath(*cp++);
344 	}
345 	oldcp = cp;
346 	while (cp > s && *cp != '/')
347 		cp--, pathp--;
348 	if (*cp == '/')
349 		cp++, pathp++;
350 	*pathp = CNULL;
351 	if (*oldcp == '{') {
352 		(void) execbrc(cp, NULL);
353 		return;
354 	}
355 	matchdir((char *)cp);
356 endit:
357 	pathp = spathp;
358 	*pathp = CNULL;
359 }
360 
361 void
362 matchdir(char *pattern)			/* quote in pattern */
363 {
364 	struct stat stb;
365 	struct dirent *dp;
366 	DIR *dirp;
367 
368 	dirp = opendir(path);
369 	if (dirp == NULL) {
370 		if (expany)
371 			return;
372 		goto patherr2;
373 	}
374 	if (fstat(dirfd(dirp), &stb) < 0)
375 		goto patherr1;
376 	if (!S_ISDIR(stb.st_mode)) {
377 		errno = ENOTDIR;
378 		goto patherr1;
379 	}
380 	while ((dp = readdir(dirp)) != NULL)
381 		if (match(dp->d_name, pattern)) {
382 			if (which & E_TILDE)
383 				Cat((u_char *)path, (u_char *)dp->d_name);
384 			else {
385 				(void) strlcpy(pathp, dp->d_name,
386 				    lastpathp - pathp + 2);
387 				Cat((u_char *)tilde, (u_char *)tpathp);
388 				*pathp = CNULL;
389 			}
390 		}
391 	closedir(dirp);
392 	return;
393 
394 patherr1:
395 	closedir(dirp);
396 patherr2:
397 	(void) strlcat(path, ": ", lastpathp - path + 2);
398 	(void) strlcat(path, SYSERR, lastpathp - path + 2);
399 	yyerror(path);
400 }
401 
402 int
403 execbrc(u_char *p, u_char *s)		/* quote in p */
404 {
405 	u_char restbuf[BUFSIZ + 2];
406 	u_char *pe, *pm, *pl;
407 	int brclev = 0;
408 	u_char *lm, savec;
409 	char *spathp;
410 
411 	for (lm = restbuf; *p != '{'; *lm++ = *p++)
412 		if (*p == QUOTECHAR) *lm++ = *p++;
413 
414 	for (pe = ++p; *pe; pe++)
415 		switch (*pe) {
416 
417 		case '{':
418 			brclev++;
419 			continue;
420 
421 		case '}':
422 			if (brclev == 0)
423 				goto pend;
424 			brclev--;
425 			continue;
426 
427 		case '[':
428 			for (pe++; *pe && *pe != ']'; pe++)
429 				if (*p == QUOTECHAR) pe++;
430 			if (!*pe)
431 				yyerror("Missing ']'");
432 			continue;
433 
434 		case QUOTECHAR:		/* skip this character */
435 			pe++;
436 			continue;
437 		}
438 pend:
439 	if (brclev || !*pe) {
440 		yyerror("Missing '}'");
441 		return (0);
442 	}
443 	for (pl = pm = p; pm <= pe; pm++)
444 		/* the strip code was a noop */
445 		switch (*pm) {
446 
447 		case '{':
448 			brclev++;
449 			continue;
450 
451 		case '}':
452 			if (brclev) {
453 				brclev--;
454 				continue;
455 			}
456 			goto doit;
457 
458 		case ',':
459 			if (brclev)
460 				continue;
461 doit:
462 			savec = *pm;
463 			*pm = 0;
464 			*lm = 0;
465 			(void) strlcat((char *)restbuf, (char *)pl,
466 			    sizeof(restbuf));
467 			(void) strlcat((char *)restbuf, (char *)pe + 1,
468 			    sizeof(restbuf));
469 			*pm = savec;
470 			if (s == 0) {
471 				spathp = pathp;
472 				expsh(restbuf);
473 				pathp = spathp;
474 				*pathp = 0;
475 			} else if (amatch((char *)s, restbuf))
476 				return (1);
477 			sort();
478 			pl = pm + 1;
479 			continue;
480 
481 		case '[':
482 			for (pm++; *pm && *pm != ']'; pm++)
483 				if (*pm == QUOTECHAR) pm++;
484 			if (!*pm)
485 				yyerror("Missing ']'");
486 			continue;
487 
488 		case QUOTECHAR:			/* skip one character */
489 			pm++;
490 			continue;
491 		}
492 	return (0);
493 }
494 
495 int
496 match(char *s, char *p)				/* quote in p */
497 {
498 	int c;
499 	char *sentp;
500 	char sexpany = expany;
501 
502 	if (*s == '.' && *p != '.')
503 		return (0);
504 	sentp = entp;
505 	entp = s;
506 	c = amatch(s, p);
507 	entp = sentp;
508 	expany = sexpany;
509 	return (c);
510 }
511 
512 int
513 amatch(char *s, u_char *p)			/* quote in p */
514 {
515 	int scc;
516 	int ok, lc;
517 	char *spathp;
518 	struct stat stb;
519 	int c, cc;
520 
521 	expany = 1;
522 	for (;;) {
523 		scc = *s++;
524 		switch (c = *p++) {
525 
526 		case '{':
527 			return (execbrc((u_char *)p - 1, (u_char *)s - 1));
528 
529 		case '[':
530 			ok = 0;
531 			lc = 077777;
532 			while ((cc = *p++) != '\0') {
533 				if (cc == ']') {
534 					if (ok)
535 						break;
536 					return (0);
537 				}
538 				if (cc == QUOTECHAR) cc = *p++;
539 				if (cc == '-') {
540 					if (lc <= scc && scc <= (int)*p++)
541 						ok++;
542 				} else
543 					if (scc == (lc = cc))
544 						ok++;
545 			}
546 			if (cc == 0) {
547 				yyerror("Missing ']'");
548 				return (0);
549 			}
550 			continue;
551 
552 		case '*':
553 			if (!*p)
554 				return (1);
555 			if (*p == '/') {
556 				p++;
557 				goto slash;
558 			}
559 			for (s--; *s; s++)
560 				if (amatch(s, p))
561 					return (1);
562 			return (0);
563 
564 		case CNULL:
565 			return (scc == CNULL);
566 
567 		default:
568 			if (c != scc)
569 				return (0);
570 			continue;
571 
572 		case '?':
573 			if (scc == CNULL)
574 				return (0);
575 			continue;
576 
577 		case '/':
578 			if (scc)
579 				return (0);
580 slash:
581 			s = entp;
582 			spathp = pathp;
583 			while (*s)
584 				addpath(*s++);
585 			addpath('/');
586 			if (stat(path, &stb) == 0 && S_ISDIR(stb.st_mode)) {
587 				if (*p == CNULL) {
588 					if (which & E_TILDE) {
589 						Cat((u_char *)path,
590 						    (u_char *)"");
591 					} else {
592 						Cat((u_char *)tilde,
593 						    (u_char *)tpathp);
594 					}
595 				} else
596 					expsh(p);
597 			}
598 			pathp = spathp;
599 			*pathp = CNULL;
600 			return (0);
601 		}
602 	}
603 }
604