1 /* $OpenBSD: ex_argv.c,v 1.20 2016/05/27 09:18:12 martijn Exp $ */ 2 3 /*- 4 * Copyright (c) 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * Copyright (c) 1993, 1994, 1995, 1996 7 * Keith Bostic. All rights reserved. 8 * 9 * See the LICENSE file for redistribution information. 10 */ 11 12 #include "config.h" 13 14 #include <sys/types.h> 15 #include <sys/queue.h> 16 17 #include <bitstring.h> 18 #include <ctype.h> 19 #include <dirent.h> 20 #include <errno.h> 21 #include <limits.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <unistd.h> 26 27 #include "../common/common.h" 28 29 static int argv_alloc(SCR *, size_t); 30 static int argv_comp(const void *, const void *); 31 static int argv_fexp(SCR *, EXCMD *, 32 char *, size_t, char *, size_t *, char **, size_t *, int); 33 static int argv_lexp(SCR *, EXCMD *, char *); 34 static int argv_sexp(SCR *, char **, size_t *, size_t *); 35 36 /* 37 * argv_init -- 38 * Build a prototype arguments list. 39 * 40 * PUBLIC: int argv_init(SCR *, EXCMD *); 41 */ 42 int 43 argv_init(SCR *sp, EXCMD *excp) 44 { 45 EX_PRIVATE *exp; 46 47 exp = EXP(sp); 48 exp->argsoff = 0; 49 argv_alloc(sp, 1); 50 51 excp->argv = exp->args; 52 excp->argc = exp->argsoff; 53 return (0); 54 } 55 56 /* 57 * argv_exp0 -- 58 * Append a string to the argument list. 59 * 60 * PUBLIC: int argv_exp0(SCR *, EXCMD *, char *, size_t); 61 */ 62 int 63 argv_exp0(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen) 64 { 65 EX_PRIVATE *exp; 66 67 exp = EXP(sp); 68 argv_alloc(sp, cmdlen); 69 memcpy(exp->args[exp->argsoff]->bp, cmd, cmdlen); 70 exp->args[exp->argsoff]->bp[cmdlen] = '\0'; 71 exp->args[exp->argsoff]->len = cmdlen; 72 ++exp->argsoff; 73 excp->argv = exp->args; 74 excp->argc = exp->argsoff; 75 return (0); 76 } 77 78 /* 79 * argv_exp1 -- 80 * Do file name expansion on a string, and append it to the 81 * argument list. 82 * 83 * PUBLIC: int argv_exp1(SCR *, EXCMD *, char *, size_t, int); 84 */ 85 int 86 argv_exp1(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen, int is_bang) 87 { 88 size_t blen, len; 89 char *bp, *p, *t; 90 91 GET_SPACE_RET(sp, bp, blen, 512); 92 93 len = 0; 94 if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) { 95 FREE_SPACE(sp, bp, blen); 96 return (1); 97 } 98 99 /* If it's empty, we're done. */ 100 if (len != 0) { 101 for (p = bp, t = bp + len; p < t; ++p) 102 if (!isblank(*p)) 103 break; 104 if (p == t) 105 goto ret; 106 } else 107 goto ret; 108 109 (void)argv_exp0(sp, excp, bp, len); 110 111 ret: FREE_SPACE(sp, bp, blen); 112 return (0); 113 } 114 115 /* 116 * argv_exp2 -- 117 * Do file name and shell expansion on a string, and append it to 118 * the argument list. 119 * 120 * PUBLIC: int argv_exp2(SCR *, EXCMD *, char *, size_t); 121 */ 122 int 123 argv_exp2(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen) 124 { 125 size_t blen, len, n; 126 int rval; 127 char *bp, *mp, *p; 128 129 GET_SPACE_RET(sp, bp, blen, 512); 130 131 #define SHELLECHO "echo " 132 #define SHELLOFFSET (sizeof(SHELLECHO) - 1) 133 memcpy(bp, SHELLECHO, SHELLOFFSET); 134 p = bp + SHELLOFFSET; 135 len = SHELLOFFSET; 136 137 #if defined(DEBUG) && 0 138 TRACE(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd); 139 #endif 140 141 if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) { 142 rval = 1; 143 goto err; 144 } 145 146 #if defined(DEBUG) && 0 147 TRACE(sp, "before shell: %d: {%s}\n", len, bp); 148 #endif 149 150 /* 151 * Do shell word expansion -- it's very, very hard to figure out what 152 * magic characters the user's shell expects. Historically, it was a 153 * union of v7 shell and csh meta characters. We match that practice 154 * by default, so ":read \%" tries to read a file named '%'. It would 155 * make more sense to pass any special characters through the shell, 156 * but then, if your shell was csh, the above example will behave 157 * differently in nvi than in vi. If you want to get other characters 158 * passed through to your shell, change the "meta" option. 159 * 160 * To avoid a function call per character, we do a first pass through 161 * the meta characters looking for characters that aren't expected 162 * to be there, and then we can ignore them in the user's argument. 163 */ 164 if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1)) 165 n = 0; 166 else { 167 for (p = mp = O_STR(sp, O_SHELLMETA); *p != '\0'; ++p) 168 if (isblank(*p) || isalnum(*p)) 169 break; 170 p = bp + SHELLOFFSET; 171 n = len - SHELLOFFSET; 172 if (*p != '\0') { 173 for (; n > 0; --n, ++p) 174 if (strchr(mp, *p) != NULL) 175 break; 176 } else 177 for (; n > 0; --n, ++p) 178 if (!isblank(*p) && 179 !isalnum(*p) && strchr(mp, *p) != NULL) 180 break; 181 } 182 183 /* 184 * If we found a meta character in the string, fork a shell to expand 185 * it. Unfortunately, this is comparatively slow. Historically, it 186 * didn't matter much, since users don't enter meta characters as part 187 * of pathnames that frequently. The addition of filename completion 188 * broke that assumption because it's easy to use. As a result, lots 189 * folks have complained that the expansion code is too slow. So, we 190 * detect filename completion as a special case, and do it internally. 191 * Note that this code assumes that the <asterisk> character is the 192 * match-anything meta character. That feels safe -- if anyone writes 193 * a shell that doesn't follow that convention, I'd suggest giving them 194 * a festive hot-lead enema. 195 */ 196 switch (n) { 197 case 0: 198 p = bp + SHELLOFFSET; 199 len -= SHELLOFFSET; 200 rval = argv_exp3(sp, excp, p, len); 201 break; 202 case 1: 203 if (*p == '*') { 204 *p = '\0'; 205 rval = argv_lexp(sp, excp, bp + SHELLOFFSET); 206 break; 207 } 208 /* FALLTHROUGH */ 209 default: 210 if (argv_sexp(sp, &bp, &blen, &len)) { 211 rval = 1; 212 goto err; 213 } 214 p = bp; 215 rval = argv_exp3(sp, excp, p, len); 216 break; 217 } 218 219 err: FREE_SPACE(sp, bp, blen); 220 return (rval); 221 } 222 223 /* 224 * argv_exp3 -- 225 * Take a string and break it up into an argv, which is appended 226 * to the argument list. 227 * 228 * PUBLIC: int argv_exp3(SCR *, EXCMD *, char *, size_t); 229 */ 230 int 231 argv_exp3(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen) 232 { 233 EX_PRIVATE *exp; 234 size_t len; 235 int ch, off; 236 char *ap, *p; 237 238 for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) { 239 /* Skip any leading whitespace. */ 240 for (; cmdlen > 0; --cmdlen, ++cmd) { 241 ch = *cmd; 242 if (!isblank(ch)) 243 break; 244 } 245 if (cmdlen == 0) 246 break; 247 248 /* 249 * Determine the length of this whitespace delimited 250 * argument. 251 * 252 * QUOTING NOTE: 253 * 254 * Skip any character preceded by the user's quoting 255 * character. 256 */ 257 for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) { 258 ch = *cmd; 259 if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) { 260 ++cmd; 261 --cmdlen; 262 } else if (isblank(ch)) 263 break; 264 } 265 266 /* 267 * Copy the argument into place. 268 * 269 * QUOTING NOTE: 270 * 271 * Lose quote chars. 272 */ 273 argv_alloc(sp, len); 274 off = exp->argsoff; 275 exp->args[off]->len = len; 276 for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++) 277 if (IS_ESCAPE(sp, excp, *ap)) 278 ++ap; 279 *p = '\0'; 280 } 281 excp->argv = exp->args; 282 excp->argc = exp->argsoff; 283 284 #if defined(DEBUG) && 0 285 for (cnt = 0; cnt < exp->argsoff; ++cnt) 286 TRACE(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]); 287 #endif 288 return (0); 289 } 290 291 /* 292 * argv_fexp -- 293 * Do file name and bang command expansion. 294 */ 295 static int 296 argv_fexp(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen, char *p, 297 size_t *lenp, char **bpp, size_t *blenp, int is_bang) 298 { 299 EX_PRIVATE *exp; 300 char *bp, *t; 301 size_t blen, len, off, tlen; 302 303 /* Replace file name characters. */ 304 for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd) 305 switch (*cmd) { 306 case '!': 307 if (!is_bang) 308 goto ins_ch; 309 exp = EXP(sp); 310 if (exp->lastbcomm == NULL) { 311 msgq(sp, M_ERR, 312 "No previous command to replace \"!\""); 313 return (1); 314 } 315 len += tlen = strlen(exp->lastbcomm); 316 off = p - bp; 317 ADD_SPACE_RET(sp, bp, blen, len); 318 p = bp + off; 319 memcpy(p, exp->lastbcomm, tlen); 320 p += tlen; 321 F_SET(excp, E_MODIFY); 322 break; 323 case '%': 324 if ((t = sp->frp->name) == NULL) { 325 msgq(sp, M_ERR, 326 "No filename to substitute for %%"); 327 return (1); 328 } 329 tlen = strlen(t); 330 len += tlen; 331 off = p - bp; 332 ADD_SPACE_RET(sp, bp, blen, len); 333 p = bp + off; 334 memcpy(p, t, tlen); 335 p += tlen; 336 F_SET(excp, E_MODIFY); 337 break; 338 case '#': 339 if ((t = sp->alt_name) == NULL) { 340 msgq(sp, M_ERR, 341 "No filename to substitute for #"); 342 return (1); 343 } 344 len += tlen = strlen(t); 345 off = p - bp; 346 ADD_SPACE_RET(sp, bp, blen, len); 347 p = bp + off; 348 memcpy(p, t, tlen); 349 p += tlen; 350 F_SET(excp, E_MODIFY); 351 break; 352 case '\\': 353 /* 354 * QUOTING NOTE: 355 * 356 * Strip any backslashes that protected the file 357 * expansion characters. 358 */ 359 if (cmdlen > 1 && 360 (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) { 361 ++cmd; 362 --cmdlen; 363 } 364 /* FALLTHROUGH */ 365 default: 366 ins_ch: ++len; 367 off = p - bp; 368 ADD_SPACE_RET(sp, bp, blen, len); 369 p = bp + off; 370 *p++ = *cmd; 371 } 372 373 /* Nul termination. */ 374 ++len; 375 off = p - bp; 376 ADD_SPACE_RET(sp, bp, blen, len); 377 p = bp + off; 378 *p = '\0'; 379 380 /* Return the new string length, buffer, buffer length. */ 381 *lenp = len - 1; 382 *bpp = bp; 383 *blenp = blen; 384 return (0); 385 } 386 387 /* 388 * argv_alloc -- 389 * Make more space for arguments. 390 */ 391 static int 392 argv_alloc(SCR *sp, size_t len) 393 { 394 ARGS *ap; 395 EX_PRIVATE *exp; 396 int cnt, off; 397 398 /* 399 * Allocate room for another argument, always leaving 400 * enough room for an ARGS structure with a length of 0. 401 */ 402 #define INCREMENT 20 403 exp = EXP(sp); 404 off = exp->argsoff; 405 if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) { 406 cnt = exp->argscnt + INCREMENT; 407 REALLOCARRAY(sp, exp->args, cnt, sizeof(ARGS *)); 408 if (exp->args == NULL) { 409 (void)argv_free(sp); 410 goto mem; 411 } 412 memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *)); 413 exp->argscnt = cnt; 414 } 415 416 /* First argument. */ 417 if (exp->args[off] == NULL) { 418 CALLOC(sp, exp->args[off], 1, sizeof(ARGS)); 419 if (exp->args[off] == NULL) 420 goto mem; 421 } 422 423 /* First argument buffer. */ 424 ap = exp->args[off]; 425 ap->len = 0; 426 if (ap->blen < len + 1) { 427 ap->blen = len + 1; 428 REALLOCARRAY(sp, ap->bp, ap->blen, sizeof(CHAR_T)); 429 if (ap->bp == NULL) { 430 ap->bp = NULL; 431 ap->blen = 0; 432 F_CLR(ap, A_ALLOCATED); 433 mem: msgq(sp, M_SYSERR, NULL); 434 return (1); 435 } 436 F_SET(ap, A_ALLOCATED); 437 } 438 439 /* Second argument. */ 440 if (exp->args[++off] == NULL) { 441 CALLOC(sp, exp->args[off], 1, sizeof(ARGS)); 442 if (exp->args[off] == NULL) 443 goto mem; 444 } 445 /* 0 length serves as end-of-argument marker. */ 446 exp->args[off]->len = 0; 447 return (0); 448 } 449 450 /* 451 * argv_free -- 452 * Free up argument structures. 453 * 454 * PUBLIC: int argv_free(SCR *); 455 */ 456 int 457 argv_free(SCR *sp) 458 { 459 EX_PRIVATE *exp; 460 int off; 461 462 exp = EXP(sp); 463 if (exp->args != NULL) { 464 for (off = 0; off < exp->argscnt; ++off) { 465 if (exp->args[off] == NULL) 466 continue; 467 if (F_ISSET(exp->args[off], A_ALLOCATED)) 468 free(exp->args[off]->bp); 469 free(exp->args[off]); 470 } 471 free(exp->args); 472 } 473 exp->args = NULL; 474 exp->argscnt = 0; 475 exp->argsoff = 0; 476 return (0); 477 } 478 479 /* 480 * argv_lexp -- 481 * Find all file names matching the prefix and append them to the 482 * buffer. 483 */ 484 static int 485 argv_lexp(SCR *sp, EXCMD *excp, char *path) 486 { 487 struct dirent *dp; 488 DIR *dirp; 489 EX_PRIVATE *exp; 490 int off; 491 size_t dlen, nlen; 492 char *dname, *name, *p; 493 494 exp = EXP(sp); 495 496 /* Set up the name and length for comparison. */ 497 if ((p = strrchr(path, '/')) == NULL) { 498 dname = "."; 499 dlen = 0; 500 name = path; 501 } else { 502 if (p == path) { 503 dname = "/"; 504 dlen = 1; 505 } else { 506 *p = '\0'; 507 dname = path; 508 dlen = strlen(path); 509 } 510 name = p + 1; 511 } 512 nlen = strlen(name); 513 514 if ((dirp = opendir(dname)) == NULL) { 515 msgq_str(sp, M_SYSERR, dname, "%s"); 516 return (1); 517 } 518 for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) { 519 if (nlen == 0) { 520 if (dp->d_name[0] == '.') 521 continue; 522 } else { 523 if (dp->d_namlen < nlen || 524 memcmp(dp->d_name, name, nlen)) 525 continue; 526 } 527 528 /* Directory + name + slash + null. */ 529 argv_alloc(sp, dlen + dp->d_namlen + 2); 530 p = exp->args[exp->argsoff]->bp; 531 if (dlen != 0) { 532 memcpy(p, dname, dlen); 533 p += dlen; 534 if (dlen > 1 || dname[0] != '/') 535 *p++ = '/'; 536 } 537 memcpy(p, dp->d_name, dp->d_namlen + 1); 538 exp->args[exp->argsoff]->len = dlen + dp->d_namlen + 1; 539 ++exp->argsoff; 540 excp->argv = exp->args; 541 excp->argc = exp->argsoff; 542 } 543 closedir(dirp); 544 545 if (off == exp->argsoff) { 546 /* 547 * If we didn't find a match, complain that the expansion 548 * failed. We can't know for certain that's the error, but 549 * it's a good guess, and it matches historic practice. 550 */ 551 msgq(sp, M_ERR, "Shell expansion failed"); 552 return (1); 553 } 554 qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp); 555 return (0); 556 } 557 558 /* 559 * argv_comp -- 560 * Alphabetic comparison. 561 */ 562 static int 563 argv_comp(const void *a, const void *b) 564 { 565 return (strcmp((char *)(*(ARGS **)a)->bp, (char *)(*(ARGS **)b)->bp)); 566 } 567 568 /* 569 * argv_sexp -- 570 * Fork a shell, pipe a command through it, and read the output into 571 * a buffer. 572 */ 573 static int 574 argv_sexp(SCR *sp, char **bpp, size_t *blenp, size_t *lenp) 575 { 576 enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval; 577 FILE *ifp; 578 pid_t pid; 579 size_t blen, len; 580 int ch, std_output[2]; 581 char *bp, *p, *sh, *sh_path; 582 583 /* Secure means no shell access. */ 584 if (O_ISSET(sp, O_SECURE)) { 585 msgq(sp, M_ERR, 586 "Shell expansions not supported when the secure edit option is set"); 587 return (1); 588 } 589 590 sh_path = O_STR(sp, O_SHELL); 591 if ((sh = strrchr(sh_path, '/')) == NULL) 592 sh = sh_path; 593 else 594 ++sh; 595 596 /* Local copies of the buffer variables. */ 597 bp = *bpp; 598 blen = *blenp; 599 600 /* 601 * There are two different processes running through this code, named 602 * the utility (the shell) and the parent. The utility reads standard 603 * input and writes standard output and standard error output. The 604 * parent writes to the utility, reads its standard output and ignores 605 * its standard error output. Historically, the standard error output 606 * was discarded by vi, as it produces a lot of noise when file patterns 607 * don't match. 608 * 609 * The parent reads std_output[0], and the utility writes std_output[1]. 610 */ 611 ifp = NULL; 612 std_output[0] = std_output[1] = -1; 613 if (pipe(std_output) < 0) { 614 msgq(sp, M_SYSERR, "pipe"); 615 return (1); 616 } 617 if ((ifp = fdopen(std_output[0], "r")) == NULL) { 618 msgq(sp, M_SYSERR, "fdopen"); 619 goto err; 620 } 621 622 /* 623 * Do the minimal amount of work possible, the shell is going to run 624 * briefly and then exit. We sincerely hope. 625 */ 626 switch (pid = vfork()) { 627 case -1: /* Error. */ 628 msgq(sp, M_SYSERR, "vfork"); 629 err: if (ifp != NULL) 630 (void)fclose(ifp); 631 else if (std_output[0] != -1) 632 close(std_output[0]); 633 if (std_output[1] != -1) 634 close(std_output[0]); 635 return (1); 636 case 0: /* Utility. */ 637 /* Redirect stdout to the write end of the pipe. */ 638 (void)dup2(std_output[1], STDOUT_FILENO); 639 640 /* Close the utility's file descriptors. */ 641 (void)close(std_output[0]); 642 (void)close(std_output[1]); 643 (void)close(STDERR_FILENO); 644 645 /* 646 * XXX 647 * Assume that all shells have -c. 648 */ 649 execl(sh_path, sh, "-c", bp, (char *)NULL); 650 msgq_str(sp, M_SYSERR, sh_path, "Error: execl: %s"); 651 _exit(127); 652 default: /* Parent. */ 653 /* Close the pipe ends the parent won't use. */ 654 (void)close(std_output[1]); 655 break; 656 } 657 658 /* 659 * Copy process standard output into a buffer. 660 * 661 * !!! 662 * Historic vi apparently discarded leading \n and \r's from 663 * the shell output stream. We don't on the grounds that any 664 * shell that does that is broken. 665 */ 666 for (p = bp, len = 0, ch = EOF; 667 (ch = getc(ifp)) != EOF; *p++ = ch, --blen, ++len) 668 if (blen < 5) { 669 ADD_SPACE_GOTO(sp, bp, *blenp, *blenp * 2); 670 p = bp + len; 671 blen = *blenp - len; 672 } 673 674 /* Delete the final newline, nul terminate the string. */ 675 if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) { 676 --p; 677 --len; 678 } 679 *p = '\0'; 680 *lenp = len; 681 *bpp = bp; /* *blenp is already updated. */ 682 683 if (ferror(ifp)) 684 goto ioerr; 685 if (fclose(ifp)) { 686 ioerr: msgq_str(sp, M_ERR, sh, "I/O error: %s"); 687 alloc_err: rval = SEXP_ERR; 688 } else 689 rval = SEXP_OK; 690 691 /* 692 * Wait for the process. If the shell process fails (e.g., "echo $q" 693 * where q wasn't a defined variable) or if the returned string has 694 * no characters or only blank characters, (e.g., "echo $5"), complain 695 * that the shell expansion failed. We can't know for certain that's 696 * the error, but it's a good guess, and it matches historic practice. 697 * This won't catch "echo foo_$5", but that's not a common error and 698 * historic vi didn't catch it either. 699 */ 700 if (proc_wait(sp, pid, sh, 1, 0)) 701 rval = SEXP_EXPANSION_ERR; 702 703 for (p = bp; len; ++p, --len) 704 if (!isblank(*p)) 705 break; 706 if (len == 0) 707 rval = SEXP_EXPANSION_ERR; 708 709 if (rval == SEXP_EXPANSION_ERR) 710 msgq(sp, M_ERR, "Shell expansion failed"); 711 712 return (rval == SEXP_OK ? 0 : 1); 713 } 714