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