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