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
argv_init(sp,excp)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
argv_exp0(sp,excp,cmd,cmdlen)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
argv_exp1(sp,excp,cmd,cmdlen,is_bang)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
argv_exp2(sp,excp,cmd,cmdlen)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
argv_exp3(sp,excp,cmd,cmdlen)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
argv_fexp(sp,excp,cmd,cmdlen,p,lenp,bpp,blenp,is_bang)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
argv_alloc(sp,len)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
argv_free(sp)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
argv_lexp(sp,excp,path)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
argv_comp(a,b)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
argv_sexp(sp,bpp,blenp,lenp)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