1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
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
44 FILE *s_file
; /* File we were in. */
45 void *s_cond
; /* Saved state of conditional stack */
46 ui32_t s_pstate
; /* Copy of ::pstate */
50 static struct fio_stack _fio_stack
[FIO_STACK_SIZE
];
51 static size_t _fio_stack_size
;
52 static FILE * _fio_input
;
54 /* line is a buffer with the result of fgets(). Returns the first newline or
55 * the last character read */
56 static size_t _length_of_line(char const *line
, size_t linesize
);
58 /* Read a line, one character at a time */
59 static char * _fgetline_byone(char **line
, size_t *linesize
, size_t *llen
,
60 FILE *fp
, int appendnl
, size_t n SMALLOC_DEBUG_ARGS
);
63 static bool_t
a_file_lock(int fd
, enum n_file_lock_type ft
, off_t off
, off_t len
);
65 /* `source' and `source_if' (if silent_error: no pipes allowed, then) */
66 static bool_t
_source_file(char const *file
, bool_t silent_error
);
69 _length_of_line(char const *line
, size_t linesize
)
74 /* Last character is always '\0' and was added by fgets() */
75 for (--linesize
, i
= 0; i
< linesize
; i
++)
78 i
= (i
< linesize
) ? i
+ 1 : linesize
;
84 _fgetline_byone(char **line
, size_t *linesize
, size_t *llen
, FILE *fp
,
85 int appendnl
, size_t n SMALLOC_DEBUG_ARGS
)
91 assert(*linesize
== 0 || *line
!= NULL
);
93 if (*linesize
<= LINESIZE
|| n
>= *linesize
- 128) {
94 *linesize
+= ((rv
== NULL
) ? LINESIZE
+ n
+ 1 : 256);
95 *line
= rv
= (srealloc
)(rv
, *linesize SMALLOC_DEBUG_ARGSCALL
);
124 a_file_lock(int fd
, enum n_file_lock_type flt
, off_t off
, off_t len
)
130 memset(&flp
, 0, sizeof flp
);
134 case FLT_READ
: rv
= F_RDLCK
; break;
135 case FLT_WRITE
: rv
= F_WRLCK
; break;
139 flp
.l_whence
= SEEK_SET
;
142 rv
= (fcntl(fd
, F_SETLK
, &flp
) != -1);
148 _source_file(char const *file
, bool_t silent_error
)
157 if ((ispipe
= !silent_error
)) {
158 size_t i
= strlen(file
);
160 while (i
> 0 && spacechar(file
[i
- 1]))
162 if (i
> 0 && file
[i
- 1] == '|')
163 cp
= savestrbuf(file
, --i
);
171 if ((sh
= ok_vlook(SHELL
)) == NULL
)
173 if ((fi
= Popen(cp
, "r", sh
, NULL
, COMMAND_FD_NULL
)) == NULL
) {
177 } else if ((cp
= fexpand(file
, FEXP_LOCAL
)) == NULL
)
179 else if ((fi
= Fopen(cp
, "r")) == NULL
) {
180 if (!silent_error
|| (options
& OPT_D_V
))
185 if (temporary_localopts_store
!= NULL
) {
186 n_err(_("Before v15 you cannot `source' from within macros, sorry\n"));
189 if (_fio_stack_size
>= NELEM(_fio_stack
)) {
190 n_err(_("Too many `source' recursions\n"));
200 _fio_stack
[_fio_stack_size
].s_file
= _fio_input
;
201 _fio_stack
[_fio_stack_size
].s_cond
= condstack_release();
202 _fio_stack
[_fio_stack_size
].s_pstate
= pstate
;
204 pstate
&= ~(PS_LOADING
| PS_PIPING
);
205 pstate
|= PS_SOURCING
| (ispipe
? PS_PIPING
: PS_NONE
);
213 (fgetline
)(char **line
, size_t *linesize
, size_t *cnt
, size_t *llen
, FILE *fp
,
214 int appendnl SMALLOC_DEBUG_ARGS
)
221 /* Without count, we can't determine where the chars returned by fgets()
222 * end if there's no newline. We have to read one character by one */
223 rv
= _fgetline_byone(line
, linesize
, llen
, fp
, appendnl
, 0
224 SMALLOC_DEBUG_ARGSCALL
);
228 if ((rv
= *line
) == NULL
|| *linesize
< LINESIZE
)
229 *line
= rv
= (srealloc
)(rv
, *linesize
= LINESIZE SMALLOC_DEBUG_ARGSCALL
);
230 sz
= (*linesize
<= *cnt
) ? *linesize
: *cnt
+ 1;
231 if (sz
<= 1 || fgets(rv
, sz
, fp
) == NULL
) {
232 /* Leave llen untouched; it is used to determine whether the last line
233 * was \n-terminated in some callers */
238 i_llen
= _length_of_line(rv
, sz
);
240 while (rv
[i_llen
- 1] != '\n') {
241 *line
= rv
= (srealloc
)(rv
, *linesize
+= 256 SMALLOC_DEBUG_ARGSCALL
);
242 sz
= *linesize
- i_llen
;
243 sz
= (sz
<= *cnt
) ? sz
: *cnt
+ 1;
244 if (sz
<= 1 || fgets(rv
+ i_llen
, sz
, fp
) == NULL
) {
251 sz
= _length_of_line(rv
+ i_llen
, sz
);
263 (readline_restart
)(FILE *ibuf
, char **linebuf
, size_t *linesize
, size_t n
266 /* TODO readline_restart(): always *appends* LF just to strip it again;
267 * TODO should be configurable just as for fgetline(); ..or whatever.. */
274 /* Interrupts will cause trouble if we are inside a stdio call. As this is
275 * only relevant if input is from tty, bypass it by read(), then */
276 if (fileno(ibuf
) == 0 && (options
& OPT_TTYIN
)) {
277 assert(*linesize
== 0 || *linebuf
!= NULL
);
279 if (*linesize
<= LINESIZE
|| n
>= *linesize
- 128) {
280 *linesize
+= ((*linebuf
== NULL
) ? LINESIZE
+ n
+ 1 : 256);
281 *linebuf
= (srealloc
)(*linebuf
, *linesize SMALLOC_DEBUG_ARGSCALL
);
284 sz
= read(0, *linebuf
+ n
, *linesize
- n
- 1);
287 (*linebuf
)[n
] = '\0';
288 if (n
> 0 && (*linebuf
)[n
- 1] == '\n')
291 if (sz
< 0 && errno
== EINTR
)
294 if ((*linebuf
)[n
- 1] != '\n') {
295 (*linebuf
)[n
++] = '\n';
296 (*linebuf
)[n
] = '\0';
304 /* Not reading from standard input or standard input not a terminal. We
305 * read one char at a time as it is the only way to get lines with
306 * embedded NUL characters in standard stdio */
307 if (_fgetline_byone(linebuf
, linesize
, &n
, ibuf
, 1, n
308 SMALLOC_DEBUG_ARGSCALL
) == NULL
)
311 if (n
> 0 && (*linebuf
)[n
- 1] == '\n')
312 (*linebuf
)[--n
] = '\0';
320 (readline_input
)(char const *prompt
, bool_t nl_escape
, char **linebuf
,
321 size_t *linesize
, char const *string SMALLOC_DEBUG_ARGS
)
323 /* TODO readline: linebuf pool! */
324 FILE *ifile
= (_fio_input
!= NULL
) ? _fio_input
: stdin
;
325 bool_t doprompt
, dotty
;
329 doprompt
= (!(pstate
& PS_SOURCING
) && (options
& OPT_INTERACTIVE
));
330 dotty
= (doprompt
&& !ok_blook(line_editor_disable
));
333 else if (prompt
== NULL
)
334 prompt
= getprompt();
336 /* Ensure stdout is flushed first anyway */
337 if (!dotty
&& prompt
== NULL
)
340 for (nold
= n
= 0;;) {
342 assert(ifile
== stdin
);
343 if (string
!= NULL
&& (n
= (int)strlen(string
)) > 0) {
347 *linesize
= (size_t)n
+ LINESIZE
+1;
348 *linebuf
= (srealloc
)(*linebuf
, *linesize SMALLOC_DEBUG_ARGSCALL
);
349 memcpy(*linebuf
, string
, (size_t)n
+1);
352 /* TODO if nold>0, don't redisplay the entire line!
353 * TODO needs complete redesign ... */
354 n
= (n_tty_readline
)(prompt
, linebuf
, linesize
, n
355 SMALLOC_DEBUG_ARGSCALL
);
357 if (prompt
!= NULL
) {
359 fputs(prompt
, stdout
);
362 n
= (readline_restart
)(ifile
, linebuf
, linesize
, n
363 SMALLOC_DEBUG_ARGSCALL
);
365 if (n
> 0 && nold
> 0) {
367 char const *cp
= *linebuf
+ nold
;
369 while (blankspacechar(*cp
) && nold
+ i
< n
)
372 memmove(*linebuf
+ nold
, cp
, n
- nold
- i
);
374 (*linebuf
)[n
] = '\0';
382 * An unquoted <backslash> at the end of a command line shall
383 * be discarded and the next line shall continue the command */
384 if (!nl_escape
|| n
== 0 || (*linebuf
)[n
- 1] != '\\')
386 (*linebuf
)[nold
= --n
] = '\0';
387 if (prompt
!= NULL
&& *prompt
!= '\0')
388 prompt
= ".. "; /* XXX PS2 .. */
391 if (n
>= 0 && (options
& OPT_D_VV
))
392 n_err(_("%s %d bytes <%.*s>\n"),
393 ((pstate
& PS_LOADING
) ? "LOAD"
394 : (pstate
& PS_SOURCING
) ? "SOURCE" : "READ"),
401 n_input_cp_addhist(char const *prompt
, char const *string
, bool_t isgabby
)
403 /* FIXME n_input_cp_addhist(): leaks on sigjmp without linepool */
405 char *linebuf
= NULL
, *rv
= NULL
;
409 n
= readline_input(prompt
, FAL0
, &linebuf
, &linesize
, string
);
410 if (n
> 0 && *(rv
= savestrbuf(linebuf
, (size_t)n
)) != '\0' &&
411 (options
& OPT_INTERACTIVE
))
412 n_tty_addhist(rv
, isgabby
);
421 setptr(FILE *ibuf
, off_t offset
)
424 char *cp
, *linebuf
= NULL
;
426 int c
, maybe
= 1, inhead
= 0, selfcnt
= 0;
427 size_t linesize
= 0, filesize
, cnt
;
430 memset(&self
, 0, sizeof self
);
431 self
.m_flag
= MUSED
| MNEW
| MNEWEST
;
432 filesize
= mailsize
- offset
;
433 offset
= ftell(mb
.mb_otf
);
436 if (fgetline(&linebuf
, &linesize
, &filesize
, &cnt
, ibuf
, 0) == NULL
) {
437 self
.m_xsize
= self
.m_size
;
438 self
.m_xlines
= self
.m_lines
;
439 self
.m_have
= HAVE_HEADER
| HAVE_BODY
;
441 message_append(&self
);
442 message_append_null();
449 if (linebuf
[0] == '\0')
452 /* XXX Convert CRLF to LF; this should be rethought in that
453 * XXX CRLF input should possibly end as CRLF output? */
454 if (cnt
>= 2 && linebuf
[cnt
- 1] == '\n' && linebuf
[cnt
- 2] == '\r')
455 linebuf
[--cnt
- 1] = '\n';
456 fwrite(linebuf
, sizeof *linebuf
, cnt
, mb
.mb_otf
);
457 if (ferror(mb
.mb_otf
)) {
458 n_perr(_("/tmp"), 0);
461 if (linebuf
[cnt
- 1] == '\n')
462 linebuf
[cnt
- 1] = '\0';
463 if (maybe
&& linebuf
[0] == 'F' && is_head(linebuf
, cnt
, FAL0
)) {
464 /* TODO char date[FROM_DATEBUF];
465 * TODO extract_date_from_from_(linebuf, cnt, date);
466 * TODO self.m_time = 10000; */
467 self
.m_xsize
= self
.m_size
;
468 self
.m_xlines
= self
.m_lines
;
469 self
.m_have
= HAVE_HEADER
| HAVE_BODY
;
471 message_append(&self
);
473 self
.m_flag
= MUSED
| MNEW
| MNEWEST
;
476 self
.m_block
= mailx_blockof(offset
);
477 self
.m_offset
= mailx_offsetof(offset
);
479 } else if (linebuf
[0] == 0) {
482 for (cp
= linebuf
, cp2
= "status";; ++cp
) {
483 if ((c
= *cp2
++) == 0) {
484 while (c
= *cp
++, whitechar(c
))
488 while ((c
= *cp
++) != '\0')
490 self
.m_flag
|= MREAD
;
492 self
.m_flag
&= ~MNEW
;
495 if (*cp
!= c
&& *cp
!= upperconv(c
))
498 for (cp
= linebuf
, cp2
= "x-status";; ++cp
) {
499 if ((c
= *cp2
++) == 0) {
500 while ((c
= *cp
++, whitechar(c
)))
504 while ((c
= *cp
++) != '\0')
506 self
.m_flag
|= MFLAGGED
;
508 self
.m_flag
|= MANSWERED
;
510 self
.m_flag
|= MDRAFTED
;
513 if (*cp
!= c
&& *cp
!= upperconv(c
))
520 maybe
= linebuf
[0] == 0;
526 putline(FILE *obuf
, char *linebuf
, size_t cnt
)
531 fwrite(linebuf
, sizeof *linebuf
, cnt
, obuf
);
546 rv
= (fstat(fileno(iob
), &sbuf
) == -1) ? 0 : sbuf
.st_size
;
552 var_folder_updated(char const *name
, char **store
)
555 char *folder
, *unres
= NULL
, *res
= NULL
;
558 if ((folder
= UNCONST(name
)) == NULL
)
561 /* Expand the *folder*; skip %: prefix for simplicity of use */
562 /* XXX This *only* works because we do NOT
563 * XXX update environment variables via the "set" mechanism */
564 if (folder
[0] == '%' && folder
[1] == ':')
566 if ((folder
= fexpand(folder
, FEXP_FULL
)) == NULL
) /* XXX error? */
569 switch (which_protocol(folder
)) {
571 n_err(_("*folder* cannot be set to a flat, readonly POP3 account\n"));
575 /* Further expansion desired */
579 /* All non-absolute paths are relative to our home directory */
580 if (*folder
!= '/') {
581 size_t l1
= strlen(homedir
), l2
= strlen(folder
);
582 unres
= ac_alloc(l1
+ l2
+ 1 +1);
583 memcpy(unres
, homedir
, l1
);
585 memcpy(unres
+ l1
+ 1, folder
, l2
);
586 unres
[l1
+ 1 + l2
] = '\0';
590 /* Since lex.c:_update_mailname() uses realpath(3) if available to
591 * avoid that we loose track of our currently open folder in case we
592 * chdir away, but still checks the leading path portion against
593 * getfold() to be able to abbreviate to the +FOLDER syntax if
594 * possible, we need to realpath(3) the folder, too */
596 res
= ac_alloc(PATH_MAX
+1);
597 if (realpath(folder
, res
) == NULL
)
598 n_err(_("Can't canonicalize \"%s\"\n"), folder
);
603 *store
= sstrdup(folder
);
615 getdeadletter(void) /* XXX should that be in auxlily.c? */
620 if ((cp
= ok_vlook(DEAD
)) == NULL
|| (cp
= fexpand(cp
, FEXP_LOCAL
)) == NULL
)
621 cp
= fexpand("~/dead.letter", FEXP_LOCAL
| FEXP_SHELL
);
622 else if (*cp
!= '/') {
623 size_t sz
= strlen(cp
) + 2 +1;
624 char *buf
= ac_alloc(sz
);
626 snprintf(buf
, sz
, "~/%s", cp
);
627 cp
= fexpand(buf
, FEXP_LOCAL
| FEXP_SHELL
);
632 cp
= "dead.letter"; /* XXX magic -> nail.h (POSIX thing though) */
638 n_file_lock(int fd
, enum n_file_lock_type flt
, off_t off
, off_t len
,
645 for (tries
= 0; tries
<= FILE_LOCK_TRIES
; ++tries
)
646 if ((rv
= a_file_lock(fd
, flt
, off
, len
)) || pollmsecs
== 0)
649 n_msleep(pollmsecs
, FAL0
);
655 load(char const *name
)
662 if (name
== NULL
|| *name
== '\0' || (in
= Fopen(name
, "r")) == NULL
)
667 pstate
|= PS_IN_LOAD
;
668 /* commands() may sreset(), copy over file name */
670 n
.s
= ac_alloc(n
.l
+1);
671 memcpy(n
.s
, name
, n
.l
+1);
673 cond
= condstack_release();
675 n_err(_("Stopped loading \"%s\" due to errors "
676 "(enable *debug* for trace)\n"), n
.s
);
677 condstack_take(cond
);
680 pstate
&= ~PS_IN_LOAD
;
693 rv
= _source_file(*(char**)v
, FAL0
) ? 0 : 1;
699 c_source_if(void *v
) /* XXX obsolete?, support file tests in `if' etc.! */
704 rv
= _source_file(*(char**)v
, TRU1
) ? 0 : 1;
716 if (_fio_stack_size
== 0) {
717 n_err(_("`source' stack over-pop\n"));
718 pstate
&= ~PS_SOURCING
;
722 if (pstate
& PS_PIPING
)
723 Pclose(_fio_input
, TRU1
);
728 if (!condstack_take(_fio_stack
[_fio_stack_size
].s_cond
))
729 n_err(_("Unmatched \"if\"\n"));
730 pstate
&= ~(PS_LOADING
| PS_PIPING
);
731 pstate
|= _fio_stack
[_fio_stack_size
].s_pstate
& (PS_LOADING
| PS_PIPING
);
732 _fio_input
= _fio_stack
[_fio_stack_size
].s_file
;
733 if (_fio_stack_size
== 0) {
734 if (pstate
& PS_LOADING
)
735 pstate
|= PS_SOURCING
;
737 pstate
&= ~PS_SOURCING
;