1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Shell "word", file- and other name expansions.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 #ifndef HAVE_AMALGAMATION
51 struct shvar_stack
*shs_next
; /* Outer stack frame */
52 char const *shs_value
; /* Remaining value to expand */
53 size_t shs_len
; /* gth of .shs_dat this level */
54 char const *shs_dat
; /* Result data of this level */
55 bool_t
*shs_err
; /* Or NULL */
56 bool_t shs_bsesc
; /* Shall backslash escaping be performed */
59 /* Locate the user's mailbox file (where new, unread mail is queued) */
60 static void _findmail(char *buf
, size_t bufsize
, char const *user
,
63 /* Perform shell meta character expansion TODO obsolete (INSECURE!) */
64 static char * _globname(char const *name
, enum fexp_mode fexpm
);
66 /* Perform shell variable expansion */
67 static char * _sh_exp_var(struct shvar_stack
*shsp
);
70 _findmail(char *buf
, size_t bufsize
, char const *user
, bool_t force
)
75 if (!strcmp(user
, myname
) && !force
&& (cp
= ok_vlook(folder
)) != NULL
) {
79 if (force
|| (cp
= ok_vlook(MAIL
)) == NULL
)
80 snprintf(buf
, bufsize
, "%s/%s", MAILSPOOL
, user
); /* TODO */
82 n_strscpy(buf
, cp
, bufsize
);
87 _globname(char const *name
, enum fexp_mode fexpm
)
96 /* Mac OS X Snow Leopard and Linux don't init fields on error, causing
97 * SIGSEGV in wordfree(3); so let's just always zero it ourselfs */
98 memset(&we
, 0, sizeof we
);
100 /* Some systems (notably Open UNIX 8.0.0) fork a shell for wordexp()
101 * and wait, which will fail if our SIGCHLD handler is active */
103 sigaddset(&nset
, SIGCHLD
);
104 sigprocmask(SIG_BLOCK
, &nset
, NULL
);
106 # define WRDE_NOCMD 0
108 i
= wordexp(name
, &we
, WRDE_NOCMD
);
109 sigprocmask(SIG_UNBLOCK
, &nset
, NULL
);
116 if (!(fexpm
& FEXP_SILENT
))
117 n_err(_("\"%s\": Command substitution not allowed\n"), name
);
121 if (!(fexpm
& FEXP_SILENT
))
122 n_err(_("\"%s\": Expansion buffer overflow\n"), name
);
127 if (!(fexpm
& FEXP_SILENT
))
128 n_err(_("Syntax error in \"%s\"\n"), name
);
132 switch (we
.we_wordc
) {
134 cp
= savestr(we
.we_wordv
[0]);
137 if (!(fexpm
& FEXP_SILENT
))
138 n_err(_("\"%s\": No match\n"), name
);
141 if (fexpm
& FEXP_MULTIOK
) {
144 for (l
= 0, j
= 0; j
< we
.we_wordc
; ++j
)
145 l
+= strlen(we
.we_wordv
[j
]) + 1;
148 for (l
= 0, j
= 0; j
< we
.we_wordc
; ++j
) {
149 size_t x
= strlen(we
.we_wordv
[j
]);
150 memcpy(cp
+ l
, we
.we_wordv
[j
], x
);
155 } else if (!(fexpm
& FEXP_SILENT
))
156 n_err(_("\"%s\": Ambiguous\n"), name
);
164 #else /* HAVE_WORDEXP */
166 char xname
[PATH_MAX
+1], cmdbuf
[PATH_MAX
+1], /* also used for files */
168 int pivec
[2], pid
, l
, waits
;
171 if (pipe(pivec
) < 0) {
172 n_perr(_("pipe"), 0);
175 snprintf(cmdbuf
, sizeof cmdbuf
, "echo %s", name
);
176 if ((shellp
= ok_vlook(SHELL
)) == NULL
)
177 shellp
= UNCONST(XSHELL
);
178 pid
= start_command(shellp
, NULL
, COMMAND_FD_NULL
, pivec
[1],
179 "-c", cmdbuf
, NULL
, NULL
);
188 l
= read(pivec
[0], xname
, sizeof xname
);
192 n_perr(_("read"), 0);
197 if (!wait_child(pid
, &waits
) && WTERMSIG(waits
) != SIGPIPE
) {
198 if (!(fexpm
& FEXP_SILENT
))
199 n_err(_("\"%s\": Expansion failed\n"), name
);
203 if (!(fexpm
& FEXP_SILENT
))
204 n_err(_("\"%s\": No match\n"), name
);
207 if (l
== sizeof xname
) {
208 if (!(fexpm
& FEXP_SILENT
))
209 n_err(_("\"%s\": Expansion buffer overflow\n"), name
);
213 for (cp
= xname
+ l
- 1; *cp
== '\n' && cp
> xname
; --cp
)
216 if (!(fexpm
& FEXP_MULTIOK
) && strchr(xname
, ' ') != NULL
&&
217 stat(xname
, &sbuf
) < 0) {
218 if (!(fexpm
& FEXP_SILENT
))
219 n_err(_("\"%s\": Ambiguous\n"), name
);
227 #endif /* !HAVE_WORDEXP */
231 _sh_exp_var(struct shvar_stack
*shsp
)
233 struct shvar_stack next
, *np
, *tmp
;
235 char lc
, c
, *cp
, *rv
;
239 if (*(vp
= shsp
->shs_value
) != '$') {
240 bool_t bsesc
= shsp
->shs_bsesc
;
241 union {bool_t hadbs
; char c
;} u
= {FAL0
};
244 for (lc
= '\0', i
= 0; ((c
= *vp
) != '\0'); ++i
, ++vp
) {
245 if (c
== '$' && lc
!= '\\')
249 lc
= (lc
== '\\') ? (u
.hadbs
= TRU1
, '\0') : c
;
254 shsp
->shs_dat
= cp
= savestrbuf(shsp
->shs_dat
, i
);
256 for (lc
= '\0', rv
= cp
; (u
.c
= *cp
++) != '\0';) {
257 if (u
.c
!= '\\' || lc
== '\\')
259 lc
= (lc
== '\\') ? '\0' : u
.c
;
263 shsp
->shs_len
= PTR2SIZE(rv
- shsp
->shs_dat
);
266 if ((lc
= (*++vp
== '{')))
270 * Environment variable names used by the utilities in the Shell and
271 * Utilities volume of POSIX.1-2008 consist solely of uppercase
272 * letters, digits, and the <underscore> ('_') from the characters
273 * defined in Portable Character Set and do not begin with a digit.
274 * Other characters may be permitted by an implementation;
275 * applications shall tolerate the presence of such names.
276 * We do support the hyphen "-" because it is common for mailx. */
278 for (i
= 0; (c
= *vp
) != '\0'; ++i
, ++vp
)
279 if (!alnumchar(c
) && c
!= '_' && c
!= '-')
284 n_err(_("Variable name misses closing \"}\": \"%s\"\n"),
286 shsp
->shs_len
= strlen(shsp
->shs_value
);
287 shsp
->shs_dat
= shsp
->shs_value
;
288 if (shsp
->shs_err
!= NULL
)
289 *shsp
->shs_err
= TRU1
;
296 if ((cp
= vok_vlook(savestrbuf(shsp
->shs_dat
, i
))) != NULL
)
297 shsp
->shs_len
= strlen(shsp
->shs_dat
= cp
);
302 /* That level made the great and completed encoding. Build result */
304 for (i
= 0, np
= shsp
, shsp
= NULL
; np
!= NULL
;) {
312 cp
= rv
= salloc(i
+1);
313 while (shsp
!= NULL
) {
315 shsp
= shsp
->shs_next
;
316 memcpy(cp
, np
->shs_dat
, np
->shs_len
);
325 memset(&next
, 0, sizeof next
);
326 next
.shs_next
= shsp
;
328 next
.shs_err
= shsp
->shs_err
;
329 next
.shs_bsesc
= shsp
->shs_bsesc
;
330 rv
= _sh_exp_var(&next
);
335 fexpand(char const *name
, enum fexp_mode fexpm
)
337 char cbuf
[PATH_MAX
+1];
343 /* The order of evaluation is "%" and "#" expand into constants.
344 * "&" can expand into "+". "+" can expand into shell meta characters.
345 * Shell meta characters expand into constants.
346 * This way, we make no recursive expansion */
347 if ((fexpm
& FEXP_NSHORTCUT
) || (res
= shortcut_expand(name
)) == NULL
)
350 if (fexpm
& FEXP_SHELL
) {
358 if (res
[1] == ':' && res
[2] != '\0') {
362 _findmail(cbuf
, sizeof cbuf
, (res
[1] != '\0' ? res
+ 1 : myname
),
363 (res
[1] != '\0' || (options
& OPT_u_FLAG
)));
369 if (prevfile
[0] == '\0') {
370 n_err(_("No previous file\n"));
377 if (res
[1] == '\0') {
378 if ((res
= ok_vlook(MBOX
)) == NULL
)
379 res
= UNCONST("~/mbox"); /* XXX no magics (POSIX though) */
380 else if (res
[0] != '&' || res
[1] != '\0')
386 if (res
[0] == '+' && getfold(cbuf
, sizeof cbuf
)) {
387 size_t i
= strlen(cbuf
);
389 res
= str_concat_csvl(&s
, cbuf
,
390 ((i
> 0 && cbuf
[i
- 1] == '/') ? "" : "/"), res
+ 1, NULL
)->s
;
393 if (res
[0] == '%' && res
[1] == ':') {
399 /* Catch the most common shell meta character */
402 res
= n_shell_expand_tilde(res
, NULL
);
405 if (anyof(res
, "|&;<>{}()[]*?$`'\"\\"))
406 switch (which_protocol(res
)) {
409 res
= (fexpm
& FEXP_NSHELL
) ? n_shell_expand_var(res
, TRU1
, NULL
)
410 : _globname(res
, fexpm
);
417 if (fexpm
& FEXP_LOCAL
)
418 switch (which_protocol(res
)) {
423 n_err(_("Not a local file or directory: \"%s\"\n"), name
);
435 fexpand_nshell_quote(char const *name
)
441 for (i
= j
= 0; (c
= name
[i
]) != '\0'; ++i
)
446 rv
= savestrbuf(name
, i
);
448 rv
= salloc(i
+ j
+1);
449 for (i
= j
= 0; (c
= name
[i
]) != '\0'; ++i
) {
461 n_shell_expand_tilde(char const *s
, bool_t
*err_or_null
)
475 if (*(rp
= s
+ 1) == '/' || *rp
== '\0')
478 if ((rp
= strchr(s
+ 1, '/')) == NULL
)
479 rp
= (np
= UNCONST(s
)) + 1;
481 nl
= PTR2SIZE(rp
- s
);
482 np
= savestrbuf(s
, nl
);
485 if ((pwp
= getpwnam(np
)) == NULL
) {
494 rv
= salloc(nl
+ 1 + rl
+1);
497 memcpy(rv
+ nl
, rp
, rl
);
506 if (err_or_null
!= NULL
)
513 n_shell_expand_var(char const *s
, bool_t bsescape
, bool_t
*err_or_null
)
515 struct shvar_stack top
;
519 memset(&top
, 0, sizeof top
);
522 if ((top
.shs_err
= err_or_null
) != NULL
)
524 top
.shs_bsesc
= bsescape
;
525 rv
= _sh_exp_var(&top
);
531 n_shell_expand_escape(char const **s
, bool_t use_nail_extensions
)
539 if ((c
= *xs
& 0xFF) == '\0')
545 switch ((c
= *xs
& 0xFF)) {
546 case 'a': c
= '\a'; break;
547 case 'b': c
= '\b'; break;
548 case 'c': c
= PROMPT_STOP
; break;
549 case 'f': c
= '\f'; break;
550 case 'n': c
= '\n'; break;
551 case 'r': c
= '\r'; break;
552 case 't': c
= '\t'; break;
553 case 'v': c
= '\v'; break;
555 /* Hexadecimal TODO uses ASCII */
558 static ui8_t
const hexatoi
[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
561 hexatoi[(ui8_t)((n) - ((n) <= '9' ? 48 : ((n) <= 'F' ? 55 : 87)))]
569 if(options
& OPT_D_V
)
570 n_err(_("Invalid \"\\xNUMBER\" notation in \"%s\"\n"), xs
- 1);
584 /* octal, with optional 0 prefix */
594 for (c
= 0, n
= 3; n
-- > 0 && octalchar(*xs
); ++xs
) {
600 /* S-nail extension for nice (get)prompt(()) support */
605 if (use_nail_extensions
) {
607 case '&': c
= ok_blook(bsdcompat
) ? '&' : '?'; break;
608 case '?': c
= (pstate
& PS_EVAL_ERROR
) ? '1' : '0'; break;
609 case '$': c
= PROMPT_DOLLAR
; break;
610 case '@': c
= PROMPT_AT
; break;
617 /* A sole <backslash> at EOS is treated as-is! */