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 char * _findmail(char const *user
, bool_t force
);
62 /* Perform shell meta character expansion TODO obsolete (INSECURE!) */
63 static char * _globname(char const *name
, enum fexp_mode fexpm
);
65 /* Perform shell variable expansion */
66 static char * _sh_exp_var(struct shvar_stack
*shsp
);
69 _findmail(char const *user
, bool_t force
)
75 if (force
|| (cp
= ok_vlook(MAIL
)) == NULL
) {
76 size_t ul
= strlen(user
), i
= sizeof(MAILSPOOL
) -1 + 1 + ul
+1;
79 memcpy(rv
, MAILSPOOL
, i
= sizeof(MAILSPOOL
));
81 memcpy(&rv
[++i
], user
, ul
+1);
82 } else if ((rv
= fexpand(cp
, FEXP_NSHELL
)) == NULL
)
89 _globname(char const *name
, enum fexp_mode fexpm
)
98 /* Mac OS X Snow Leopard and Linux don't init fields on error, causing
99 * SIGSEGV in wordfree(3); so let's just always zero it ourselfs */
100 memset(&we
, 0, sizeof we
);
102 /* Some systems (notably Open UNIX 8.0.0) fork a shell for wordexp()
103 * and wait, which will fail if our SIGCHLD handler is active */
105 sigaddset(&nset
, SIGCHLD
);
106 sigprocmask(SIG_BLOCK
, &nset
, NULL
);
108 # define WRDE_NOCMD 0
110 i
= wordexp(name
, &we
, WRDE_NOCMD
);
111 sigprocmask(SIG_UNBLOCK
, &nset
, NULL
);
118 if (!(fexpm
& FEXP_SILENT
))
119 n_err(_("\"%s\": Command substitution not allowed\n"), name
);
123 if (!(fexpm
& FEXP_SILENT
))
124 n_err(_("\"%s\": Expansion buffer overflow\n"), name
);
129 if (!(fexpm
& FEXP_SILENT
))
130 n_err(_("Syntax error in \"%s\"\n"), name
);
134 switch (we
.we_wordc
) {
136 cp
= savestr(we
.we_wordv
[0]);
139 if (!(fexpm
& FEXP_SILENT
))
140 n_err(_("\"%s\": No match\n"), name
);
143 if (fexpm
& FEXP_MULTIOK
) {
146 for (l
= 0, j
= 0; j
< we
.we_wordc
; ++j
)
147 l
+= strlen(we
.we_wordv
[j
]) + 1;
150 for (l
= 0, j
= 0; j
< we
.we_wordc
; ++j
) {
151 size_t x
= strlen(we
.we_wordv
[j
]);
152 memcpy(cp
+ l
, we
.we_wordv
[j
], x
);
157 } else if (!(fexpm
& FEXP_SILENT
))
158 n_err(_("\"%s\": Ambiguous\n"), name
);
166 #else /* HAVE_WORDEXP */
168 char xname
[PATH_MAX
+1], cmdbuf
[PATH_MAX
+1], /* also used for files */
170 int pivec
[2], pid
, l
, waits
;
173 if (pipe(pivec
) < 0) {
174 n_perr(_("pipe"), 0);
177 snprintf(cmdbuf
, sizeof cmdbuf
, "echo %s", name
);
178 pid
= start_command(ok_vlook(SHELL
), 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 /* Check getenv(3) shall no internal variable exist! */
297 if ((rv
= vok_vlook(cp
= savestrbuf(shsp
->shs_dat
, i
))) != NULL
||
298 (rv
= getenv(cp
)) != NULL
)
299 shsp
->shs_len
= strlen(shsp
->shs_dat
= rv
);
301 shsp
->shs_len
= 0, shsp
->shs_dat
= UNCONST("");
306 /* That level made the great and completed encoding. Build result */
308 for (i
= 0, np
= shsp
, shsp
= NULL
; np
!= NULL
;) {
316 cp
= rv
= salloc(i
+1);
317 while (shsp
!= NULL
) {
319 shsp
= shsp
->shs_next
;
320 memcpy(cp
, np
->shs_dat
, np
->shs_len
);
329 memset(&next
, 0, sizeof next
);
330 next
.shs_next
= shsp
;
332 next
.shs_err
= shsp
->shs_err
;
333 next
.shs_bsesc
= shsp
->shs_bsesc
;
334 rv
= _sh_exp_var(&next
);
339 fexpand(char const *name
, enum fexp_mode fexpm
)
342 char const *cp
, *res
;
346 /* The order of evaluation is "%" and "#" expand into constants.
347 * "&" can expand into "+". "+" can expand into shell meta characters.
348 * Shell meta characters expand into constants.
349 * This way, we make no recursive expansion */
350 if ((fexpm
& FEXP_NSHORTCUT
) || (res
= shortcut_expand(name
)) == NULL
)
353 if (fexpm
& FEXP_SHELL
) {
361 if (res
[1] == ':' && res
[2] != '\0') {
365 res
= _findmail((res
[1] != '\0' ? res
+ 1 : myname
), (res
[1] != '\0'));
370 if (prevfile
[0] == '\0') {
371 n_err(_("No previous file\n"));
379 res
= ok_vlook(MBOX
);
383 /* POSIX: if *folder* unset or null, "+" shall be retained */
384 if (*res
== '+' && *(cp
= folder_query()) != '\0') {
385 size_t i
= strlen(cp
);
387 res
= str_concat_csvl(&s
, cp
,
388 ((i
== 0 || cp
[i
-1] == '/') ? "" : "/"), res
+ 1, NULL
)->s
;
391 /* TODO *folder* can't start with %[:], can it!?! */
392 if (res
[0] == '%' && res
[1] == ':') {
398 /* Catch the most common shell meta character */
401 res
= n_shell_expand_tilde(res
, NULL
);
404 if (anyof(res
, "|&;<>{}()[]*?$`'\"\\"))
405 switch (which_protocol(res
)) {
408 res
= (fexpm
& FEXP_NSHELL
) ? n_shell_expand_var(res
, TRU1
, NULL
)
409 : _globname(res
, fexpm
);
416 if (fexpm
& FEXP_LOCAL
)
417 switch (which_protocol(res
)) {
422 n_err(_("Not a local file or directory: \"%s\"\n"), name
);
434 fexpand_nshell_quote(char const *name
)
440 for (i
= j
= 0; (c
= name
[i
]) != '\0'; ++i
)
445 rv
= savestrbuf(name
, i
);
447 rv
= salloc(i
+ j
+1);
448 for (i
= j
= 0; (c
= name
[i
]) != '\0'; ++i
) {
460 n_shell_expand_tilde(char const *s
, bool_t
*err_or_null
)
474 if (*(rp
= s
+ 1) == '/' || *rp
== '\0')
477 if ((rp
= strchr(s
+ 1, '/')) == NULL
)
478 rp
= (np
= UNCONST(s
)) + 1;
480 nl
= PTR2SIZE(rp
- s
);
481 np
= savestrbuf(s
, nl
);
484 if ((pwp
= getpwnam(np
)) == NULL
) {
493 rv
= salloc(nl
+ 1 + rl
+1);
496 memcpy(rv
+ nl
, rp
, rl
);
505 if (err_or_null
!= NULL
)
512 n_shell_expand_var(char const *s
, bool_t bsescape
, bool_t
*err_or_null
)
514 struct shvar_stack top
;
518 memset(&top
, 0, sizeof top
);
521 if ((top
.shs_err
= err_or_null
) != NULL
)
523 top
.shs_bsesc
= bsescape
;
524 rv
= _sh_exp_var(&top
);
530 n_shell_expand_escape(char const **s
, bool_t use_nail_extensions
)
538 if ((c
= *xs
& 0xFF) == '\0')
544 switch ((c
= *xs
& 0xFF)) {
545 case 'a': c
= '\a'; break;
546 case 'b': c
= '\b'; break;
547 case 'c': c
= PROMPT_STOP
; break;
548 case 'f': c
= '\f'; break;
549 case 'n': c
= '\n'; break;
550 case 'r': c
= '\r'; break;
551 case 't': c
= '\t'; break;
552 case 'v': c
= '\v'; break;
554 /* Hexadecimal TODO uses ASCII */
557 static ui8_t
const hexatoi
[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
560 hexatoi[(ui8_t)((n) - ((n) <= '9' ? 48 : ((n) <= 'F' ? 55 : 87)))]
568 if(options
& OPT_D_V
)
569 n_err(_("Invalid \"\\xNUMBER\" notation in \"%s\"\n"), xs
- 1);
583 /* octal, with optional 0 prefix */
593 for (c
= 0, n
= 3; n
-- > 0 && octalchar(*xs
); ++xs
) {
599 /* S-nail extension for nice (get)prompt(()) support */
604 if (use_nail_extensions
) {
606 case '&': c
= ok_blook(bsdcompat
) ? '&' : '?'; break;
607 case '?': c
= (pstate
& PS_EVAL_ERROR
) ? '1' : '0'; break;
608 case '$': c
= PROMPT_DOLLAR
; break;
609 case '@': c
= PROMPT_AT
; break;
616 /* A sole <backslash> at EOS is treated as-is! */