nail.h: use MAXPATHLEN as fallback for struct cw
[s-mailx.git] / fio.c
blob16a3ea5555fc39aacb8ad1ee4a582482b9a2dfbd
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ File I/O.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
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
13 * are met:
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. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #include "nail.h"
42 #include <sys/wait.h>
44 #include <fcntl.h>
46 #ifdef HAVE_WORDEXP
47 # include <wordexp.h>
48 #endif
50 #ifdef HAVE_SOCKETS
51 # include <sys/socket.h>
53 # include <netdb.h>
55 # include <netinet/in.h>
57 # ifdef HAVE_ARPA_INET_H
58 # include <arpa/inet.h>
59 # endif
60 #endif
62 #ifdef HAVE_OPENSSL
63 # include <openssl/err.h>
64 # include <openssl/rand.h>
65 # include <openssl/ssl.h>
66 # include <openssl/x509v3.h>
67 # include <openssl/x509.h>
68 #endif
70 struct {
71 FILE *s_file; /* File we were in. */
72 enum condition s_cond; /* Saved state of conditionals */
73 int s_loading; /* Loading .mailrc, etc. */
74 #define SSTACK 20
75 } _sstack[SSTACK];
76 static size_t _ssp; /* Top of file stack */
77 static FILE * _input;
79 /* Locate the user's mailbox file (where new, unread mail is queued) */
80 static void _findmail(char *buf, size_t bufsize, char const *user,
81 bool_t force);
83 /* Perform shell meta character expansion */
84 static char * _globname(char const *name, enum fexp_mode fexpm);
86 /* *line* is a buffer with the result of fgets().
87 * Returns the first newline or the last character read */
88 static size_t _length_of_line(const char *line, size_t linesize);
90 /* Read a line, one character at a time */
91 static char * _fgetline_byone(char **line, size_t *linesize, size_t *llen,
92 FILE *fp, int appendnl, size_t n SMALLOC_DEBUG_ARGS);
94 static void makemessage(void);
95 static void append(struct message *mp);
96 static enum okay get_header(struct message *mp);
98 static void
99 _findmail(char *buf, size_t bufsize, char const *user, bool_t force)
101 char *cp;
103 if (strcmp(user, myname) == 0 && ! force &&
104 (cp = value("folder")) != NULL) {
105 switch (which_protocol(cp)) {
106 case PROTO_IMAP:
107 if (strcmp(cp, protbase(cp)) != 0)
108 goto jcopy;
109 snprintf(buf, bufsize, "%s/INBOX", cp);
110 goto jleave;
111 default:
112 break;
116 if (force || (cp = value("MAIL")) == NULL)
117 snprintf(buf, bufsize, "%s/%s", MAILSPOOL, user);
118 else {
119 jcopy:
120 n_strlcpy(buf, cp, bufsize);
122 jleave:
126 static char *
127 _globname(char const *name, enum fexp_mode fexpm)
129 #ifdef HAVE_WORDEXP
130 wordexp_t we;
131 char *cp = NULL;
132 sigset_t nset;
133 int i;
136 * Some systems (notably Open UNIX 8.0.0) fork a shell for
137 * wordexp() and wait for it; waiting will fail if our SIGCHLD
138 * handler is active.
140 sigemptyset(&nset);
141 sigaddset(&nset, SIGCHLD);
142 sigprocmask(SIG_BLOCK, &nset, NULL);
143 /* Mac OS X Snow Leopard doesn't init fields on error, causing SIGSEGV
144 * in wordfree(3) */
145 # ifdef __APPLE__
146 memset(&we, 0, sizeof we);
147 # endif
148 i = wordexp(name, &we, 0);
149 sigprocmask(SIG_UNBLOCK, &nset, NULL);
151 switch (i) {
152 case 0:
153 break;
154 case WRDE_NOSPACE:
155 if (! (fexpm & FEXP_SILENT))
156 fprintf(stderr,
157 tr(83, "\"%s\": Expansion buffer overflow.\n"),
158 name);
159 goto jleave;
160 case WRDE_BADCHAR:
161 case WRDE_SYNTAX:
162 default:
163 if (! (fexpm & FEXP_SILENT))
164 fprintf(stderr, tr(242, "Syntax error in \"%s\"\n"),
165 name);
166 goto jleave;
169 switch (we.we_wordc) {
170 case 1:
171 cp = savestr(we.we_wordv[0]);
172 break;
173 case 0:
174 if (! (fexpm & FEXP_SILENT))
175 fprintf(stderr, tr(82, "\"%s\": No match.\n"), name);
176 break;
177 default:
178 if (fexpm & FEXP_MULTIOK) {
179 size_t j, l;
181 for (l = 0, j = 0; j < we.we_wordc; ++j)
182 l += strlen(we.we_wordv[j]) + 1;
183 ++l;
184 cp = salloc(l);
185 for (l = 0, j = 0; j < we.we_wordc; ++j) {
186 size_t x = strlen(we.we_wordv[j]);
187 memcpy(cp + l, we.we_wordv[j], x);
188 l += x;
189 cp[l++] = ' ';
191 cp[l] = '\0';
192 } else if (! (fexpm & FEXP_SILENT))
193 fprintf(stderr, tr(84, "\"%s\": Ambiguous.\n"), name);
194 break;
196 jleave:
197 wordfree(&we);
198 return cp;
200 #else /* !HAVE_WORDEXP */
201 extern int wait_status;
203 struct stat sbuf;
204 char xname[MAXPATHLEN], cmdbuf[MAXPATHLEN], /* also used for files */
205 *cp, *shell;
206 int pid, l, pivec[2];
208 if (pipe(pivec) < 0) {
209 perror("pipe");
210 return NULL;
212 snprintf(cmdbuf, sizeof cmdbuf, "echo %s", name);
213 if ((shell = value("SHELL")) == NULL)
214 shell = UNCONST(SHELL);
215 pid = start_command(shell, 0, -1, pivec[1], "-c", cmdbuf, NULL);
216 if (pid < 0) {
217 close(pivec[0]);
218 close(pivec[1]);
219 return NULL;
221 close(pivec[1]);
223 again:
224 l = read(pivec[0], xname, sizeof xname);
225 if (l < 0) {
226 if (errno == EINTR)
227 goto again;
228 perror("read");
229 close(pivec[0]);
230 return NULL;
232 close(pivec[0]);
233 if (wait_child(pid) < 0 && WTERMSIG(wait_status) != SIGPIPE) {
234 if (! (fexpm & FEXP_SILENT))
235 fprintf(stderr, tr(81, "\"%s\": Expansion failed.\n"),
236 name);
237 return NULL;
239 if (l == 0) {
240 if (! (fexpm & FEXP_SILENT))
241 fprintf(stderr, tr(82, "\"%s\": No match.\n"), name);
242 return NULL;
244 if (l == sizeof xname) {
245 if (! (fexpm & FEXP_SILENT))
246 fprintf(stderr,
247 tr(83, "\"%s\": Expansion buffer overflow.\n"),
248 name);
249 return NULL;
251 xname[l] = 0;
252 for (cp = &xname[l-1]; *cp == '\n' && cp > xname; cp--)
254 cp[1] = '\0';
255 if (! (fexpm & FEXP_MULTIOK) && strchr(xname, ' ') &&
256 stat(xname, &sbuf) < 0) {
257 if (! (fexpm & FEXP_SILENT))
258 fprintf(stderr, tr(84, "\"%s\": Ambiguous.\n"), name);
259 return NULL;
261 return savestr(xname);
262 #endif /* !HAVE_WORDEXP */
266 * line is a buffer with the result of fgets(). Returns the first
267 * newline or the last character read.
269 static size_t
270 _length_of_line(const char *line, size_t linesize)
272 size_t i;
274 /* Last character is always '\0' and was added by fgets() */
275 for (--linesize, i = 0; i < linesize; i++)
276 if (line[i] == '\n')
277 break;
278 return (i < linesize) ? i + 1 : linesize;
281 static char *
282 _fgetline_byone(char **line, size_t *linesize, size_t *llen,
283 FILE *fp, int appendnl, size_t n SMALLOC_DEBUG_ARGS)
285 int c;
287 assert(*linesize == 0 || *line != NULL);
288 for (;;) {
289 if (*linesize <= LINESIZE || n >= *linesize - 128) {
290 *linesize += ((*line == NULL)
291 ? LINESIZE + n + 1 : 256);
292 *line = (srealloc_safe)(*line, *linesize
293 SMALLOC_DEBUG_ARGSCALL);
295 c = getc(fp);
296 if (c != EOF) {
297 (*line)[n++] = c;
298 (*line)[n] = '\0';
299 if (c == '\n')
300 break;
301 } else {
302 if (n > 0) {
303 if (appendnl) {
304 (*line)[n++] = '\n';
305 (*line)[n] = '\0';
307 break;
308 } else
309 return NULL;
312 if (llen)
313 *llen = n;
314 return *line;
317 char *
318 (fgetline)(char **line, size_t *linesize, size_t *cnt, size_t *llen,
319 FILE *fp, int appendnl SMALLOC_DEBUG_ARGS)
321 size_t i_llen, sz;
323 if (cnt == NULL)
325 * If we have no count, we cannot determine where the
326 * characters returned by fgets() end if there was no
327 * newline. We have to read one character at one.
329 return _fgetline_byone(line, linesize, llen, fp, appendnl, 0
330 SMALLOC_DEBUG_ARGSCALL);
331 if (*line == NULL || *linesize < LINESIZE)
332 *line = (srealloc_safe)(*line, *linesize = LINESIZE
333 SMALLOC_DEBUG_ARGSCALL);
334 sz = *linesize <= *cnt ? *linesize : *cnt + 1;
335 if (sz <= 1 || fgets(*line, sz, fp) == NULL)
337 * Leave llen untouched; it is used to determine whether
338 * the last line was \n-terminated in some callers.
340 return NULL;
341 i_llen = _length_of_line(*line, sz);
342 *cnt -= i_llen;
343 while ((*line)[i_llen - 1] != '\n') {
344 *line = (srealloc_safe)(*line, *linesize += 256
345 SMALLOC_DEBUG_ARGSCALL);
346 sz = *linesize - i_llen;
347 sz = (sz <= *cnt ? sz : *cnt + 1);
348 if (sz <= 1 || fgets(&(*line)[i_llen], sz, fp) == NULL) {
349 if (appendnl) {
350 (*line)[i_llen++] = '\n';
351 (*line)[i_llen] = '\0';
353 break;
355 sz = _length_of_line(&(*line)[i_llen], sz);
356 i_llen += sz;
357 *cnt -= sz;
359 if (llen)
360 *llen = i_llen;
361 return *line;
365 (readline_restart)(FILE *ibuf, char **linebuf, size_t *linesize, size_t n
366 SMALLOC_DEBUG_ARGS)
368 /* TODO readline_restart(): always *appends* LF just to strip it again;
369 * TODO should be configurable just as for fgetline(); ..or whatevr.. */
370 long sz;
372 clearerr(ibuf);
374 * Interrupts will cause trouble if we are inside a stdio call. As
375 * this is only relevant if input comes from a terminal, we can simply
376 * bypass it by read() then.
378 if (fileno(ibuf) == 0 && (options & OPT_TTYIN)) {
379 assert(*linesize == 0 || *linebuf != NULL);
380 for (;;) {
381 if (*linesize <= LINESIZE || n >= *linesize - 128) {
382 *linesize += ((*linebuf == NULL)
383 ? LINESIZE + n + 1 : 256);
384 *linebuf = (srealloc_safe)(*linebuf, *linesize
385 SMALLOC_DEBUG_ARGSCALL);
387 again:
388 sz = read(0, *linebuf + n, *linesize - n - 1);
389 if (sz > 0) {
390 n += sz;
391 (*linebuf)[n] = '\0';
392 if (n > 0 && (*linebuf)[n - 1] == '\n')
393 break;
394 } else {
395 if (sz < 0 && errno == EINTR)
396 goto again;
397 if (n > 0) {
398 if ((*linebuf)[n - 1] != '\n') {
399 (*linebuf)[n++] = '\n';
400 (*linebuf)[n] = '\0';
402 break;
403 } else
404 return -1;
407 } else {
409 * Not reading from standard input or standard input not
410 * a terminal. We read one char at a time as it is the
411 * only way to get lines with embedded NUL characters in
412 * standard stdio.
414 if (_fgetline_byone(linebuf, linesize, &n, ibuf, 1, n
415 SMALLOC_DEBUG_ARGSCALL) == NULL)
416 return -1;
418 if (n > 0 && (*linebuf)[n - 1] == '\n')
419 (*linebuf)[--n] = '\0';
420 return n;
424 (readline_input)(enum lned_mode lned, char const *prompt, char **linebuf,
425 size_t *linesize SMALLOC_DEBUG_ARGS)
427 FILE *ifile = (_input != NULL) ? _input : stdin;
428 bool_t doprompt, dotty;
429 int n;
431 doprompt = (! sourcing && (options & OPT_INTERACTIVE));
432 dotty = (doprompt && ! boption("line-editor-disable"));
433 if (prompt == NULL)
434 prompt = doprompt ? getprompt() : "";
436 for (n = 0;;) {
437 if (dotty) {
438 assert(ifile == stdin);
439 n = (tty_readline)(prompt, linebuf, linesize, n
440 SMALLOC_DEBUG_ARGSCALL);
441 } else {
442 if (doprompt && *prompt) {
443 fputs(prompt, stdout);
444 fflush(stdout);
446 n = (readline_restart)(ifile, linebuf, linesize, n
447 SMALLOC_DEBUG_ARGSCALL);
449 if (n <= 0)
450 break;
452 * POSIX says:
453 * An unquoted <backslash> at the end of a command line
454 * shall be discarded and the next line shall continue the
455 * command.
457 if ((lned & LNED_LF_ESC) && (*linebuf)[n - 1] == '\\') {
458 (*linebuf)[--n] = '\0';
459 if (*prompt)
460 prompt = ".. "; /* XXX PS2 .. */
461 continue;
463 if (dotty && (lned & LNED_HIST_ADD))
464 tty_addhist(*linebuf);
465 break;
467 return n;
470 char *
471 readstr_input(char const *prompt, char const *string) /* FIXME SIGS<->leaks */
473 /* TODO readstr_input(): linebuf pool */
474 size_t linesize = 0, slen;
475 char *linebuf = NULL, *rv = NULL;
476 bool_t doprompt, dotty;
478 doprompt = (! sourcing && (options & OPT_INTERACTIVE));
479 dotty = (doprompt && ! boption("line-editor-disable"));
480 if (prompt == NULL)
481 prompt = doprompt ? getprompt() : "";
483 /* If STDIN is not a terminal, simply read from it */
484 if (dotty) {
485 slen = (string != NULL) ? strlen(string) : 0;
486 if (slen) {
487 linesize = slen + LINESIZE + 1;
488 linebuf = smalloc_safe(linesize);
489 if (slen)
490 memcpy(linebuf, string, slen + 1);
492 if (tty_readline(prompt, &linebuf, &linesize, slen) >= 0)
493 rv = linebuf;
494 } else {
495 if (doprompt && *prompt) {
496 fputs(prompt, stdout);
497 fflush(stdout);
499 linesize = slen = 0;
500 linebuf = NULL;
501 if (readline_restart(stdin, &linebuf, &linesize, slen) >= 0)
502 rv = linebuf;
505 if (rv != NULL)
506 rv = (*rv == '\0') ? NULL : savestr(rv);
507 if (linebuf != NULL)
508 free(linebuf);
509 return rv;
512 * Set up the input pointers while copying the mail file into /tmp.
514 void
515 setptr(FILE *ibuf, off_t offset)
517 int c;
518 char *cp, *linebuf = NULL;
519 char const *cp2;
520 struct message this;
521 int maybe, inhead, thiscnt;
522 size_t linesize = 0, filesize, cnt;
524 maybe = 1;
525 inhead = 0;
526 thiscnt = 0;
527 memset(&this, 0, sizeof this);
528 this.m_flag = MUSED|MNEW|MNEWEST;
529 filesize = mailsize - offset;
530 offset = ftell(mb.mb_otf);
531 for (;;) {
532 if (fgetline(&linebuf, &linesize, &filesize, &cnt, ibuf, 0)
533 == NULL) {
534 this.m_xsize = this.m_size;
535 this.m_xlines = this.m_lines;
536 this.m_have = HAVE_HEADER|HAVE_BODY;
537 if (thiscnt > 0)
538 append(&this);
539 makemessage();
540 if (linebuf)
541 free(linebuf);
542 return;
544 #ifdef notdef
545 if (linebuf[0] == '\0')
546 linebuf[0] = '.';
547 #endif
548 /* XXX Convert CRLF to LF; this should be rethought in that
549 * XXX CRLF input should possibly end as CRLF output? */
550 if (cnt >= 2 && linebuf[cnt - 1] == '\n' &&
551 linebuf[cnt - 2] == '\r')
552 linebuf[--cnt - 1] = '\n';
553 fwrite(linebuf, sizeof *linebuf, cnt, mb.mb_otf);
554 if (ferror(mb.mb_otf)) {
555 perror("/tmp");
556 exit(1);
558 if (linebuf[cnt - 1] == '\n')
559 linebuf[cnt - 1] = '\0';
560 if (maybe && linebuf[0] == 'F' && is_head(linebuf, cnt)) {
561 /* TODO
562 * TODO char date[FROM_DATEBUF];
563 * TODO extract_date_from_from_(linebuf, cnt, date);
564 * TODO this.m_time = 10000;
566 this.m_xsize = this.m_size;
567 this.m_xlines = this.m_lines;
568 this.m_have = HAVE_HEADER|HAVE_BODY;
569 if (thiscnt++ > 0)
570 append(&this);
571 msgCount++;
572 this.m_flag = MUSED|MNEW|MNEWEST;
573 this.m_size = 0;
574 this.m_lines = 0;
575 this.m_block = mailx_blockof(offset);
576 this.m_offset = mailx_offsetof(offset);
577 inhead = 1;
578 } else if (linebuf[0] == 0) {
579 inhead = 0;
580 } else if (inhead) {
581 for (cp = linebuf, cp2 = "status";; cp++) {
582 if ((c = *cp2++) == 0) {
583 while (c = *cp++, whitechar(c));
584 if (cp[-1] != ':')
585 break;
586 while ((c = *cp++) != '\0')
587 if (c == 'R')
588 this.m_flag |= MREAD;
589 else if (c == 'O')
590 this.m_flag &= ~MNEW;
591 break;
593 if (*cp != c && *cp != upperconv(c))
594 break;
596 for (cp = linebuf, cp2 = "x-status";; cp++) {
597 if ((c = *cp2++) == 0) {
598 while (c = *cp++, whitechar(c));
599 if (cp[-1] != ':')
600 break;
601 while ((c = *cp++) != '\0')
602 if (c == 'F')
603 this.m_flag |= MFLAGGED;
604 else if (c == 'A')
605 this.m_flag|=MANSWERED;
606 else if (c == 'T')
607 this.m_flag|=MDRAFTED;
608 break;
610 if (*cp != c && *cp != upperconv(c))
611 break;
614 offset += cnt;
615 this.m_size += cnt;
616 this.m_lines++;
617 maybe = linebuf[0] == 0;
619 /*NOTREACHED*/
623 * Drop the passed line onto the passed output buffer.
624 * If a write error occurs, return -1, else the count of
625 * characters written, including the newline.
628 putline(FILE *obuf, char *linebuf, size_t cnt)
630 fwrite(linebuf, sizeof *linebuf, cnt, obuf);
631 putc('\n', obuf);
632 if (ferror(obuf))
633 return (-1);
634 return (cnt + 1);
638 * Return a file buffer all ready to read up the
639 * passed message pointer.
641 FILE *
642 setinput(struct mailbox *mp, struct message *m, enum needspec need)
644 enum okay ok = STOP;
646 switch (need) {
647 case NEED_HEADER:
648 if (m->m_have & HAVE_HEADER)
649 ok = OKAY;
650 else
651 ok = get_header(m);
652 break;
653 case NEED_BODY:
654 if (m->m_have & HAVE_BODY)
655 ok = OKAY;
656 else
657 ok = get_body(m);
658 break;
659 case NEED_UNSPEC:
660 ok = OKAY;
661 break;
663 if (ok != OKAY)
664 return NULL;
665 fflush(mp->mb_otf);
666 if (fseek(mp->mb_itf, (long)mailx_positionof(m->m_block,
667 m->m_offset), SEEK_SET) < 0) {
668 perror("fseek");
669 panic(tr(77, "temporary file seek"));
671 return (mp->mb_itf);
674 struct message *
675 setdot(struct message *mp)
677 if (dot != mp) {
678 prevdot = dot;
679 did_print_dot = FAL0;
681 dot = mp;
682 uncollapse1(dot, 0);
683 return dot;
687 * Take the data out of the passed ghost file and toss it into
688 * a dynamically allocated message structure.
690 static void
691 makemessage(void)
693 if (msgCount == 0)
694 append(NULL);
695 setdot(message);
696 message[msgCount].m_size = 0;
697 message[msgCount].m_lines = 0;
701 * Append the passed message descriptor onto the message structure.
703 static void
704 append(struct message *mp)
706 if (msgCount + 1 >= msgspace)
707 message = srealloc(message, (msgspace += 64) * sizeof *message);
708 if (msgCount > 0)
709 message[msgCount - 1] = *mp;
713 * Delete a file, but only if the file is a plain file.
716 rm(char *name) /* TODO TOCTOU; but i'm out of ideas today */
718 struct stat sb;
719 int ret = -1;
721 if (stat(name, &sb) < 0)
723 else if (! S_ISREG(sb.st_mode))
724 errno = EISDIR;
725 else
726 ret = unlink(name);
727 return ret;
730 static int sigdepth; /* depth of holdsigs() */
731 static sigset_t nset, oset;
733 * Hold signals SIGHUP, SIGINT, and SIGQUIT.
735 void
736 holdsigs(void)
739 if (sigdepth++ == 0) {
740 sigemptyset(&nset);
741 sigaddset(&nset, SIGHUP);
742 sigaddset(&nset, SIGINT);
743 sigaddset(&nset, SIGQUIT);
744 sigprocmask(SIG_BLOCK, &nset, &oset);
749 * Release signals SIGHUP, SIGINT, and SIGQUIT.
751 void
752 relsesigs(void)
754 if (--sigdepth == 0)
755 sigprocmask(SIG_SETMASK, &oset, (sigset_t *)NULL);
759 * Determine the size of the file possessed by
760 * the passed buffer.
762 off_t
763 fsize(FILE *iob)
765 struct stat sbuf;
767 if (fstat(fileno(iob), &sbuf) < 0)
768 return 0;
769 return sbuf.st_size;
772 char *
773 fexpand(char const *name, enum fexp_mode fexpm)
775 char cbuf[MAXPATHLEN], *res;
776 struct str s;
777 struct shortcut *sh;
778 bool_t dyn;
781 * The order of evaluation is "%" and "#" expand into constants.
782 * "&" can expand into "+". "+" can expand into shell meta characters.
783 * Shell meta characters expand into constants.
784 * This way, we make no recursive expansion.
786 res = UNCONST(name);
787 if (! (fexpm & FEXP_NSHORTCUT) && (sh = get_shortcut(res)) != NULL)
788 res = sh->sh_long;
790 if (fexpm & FEXP_SHELL) {
791 dyn = FAL0;
792 goto jshell;
794 jnext:
795 dyn = FAL0;
796 switch (*res) {
797 case '%':
798 if (res[1] == ':' && res[2] != '\0') {
799 res = &res[2];
800 goto jnext;
802 _findmail(cbuf, sizeof cbuf,
803 (res[1] != '\0') ? res + 1 : myname,
804 (res[1] != '\0' || option_u_arg != NULL));
805 res = cbuf;
806 goto jislocal;
807 case '#':
808 if (res[1] != '\0')
809 break;
810 if (prevfile[0] == '\0') {
811 fprintf(stderr, tr(80, "No previous file\n"));
812 res = NULL;
813 goto jleave;
815 res = prevfile;
816 goto jislocal;
817 case '&':
818 if (res[1] == '\0') {
819 if ((res = value("MBOX")) == NULL)
820 res = UNCONST("~/mbox");
821 else if (res[0] != '&' || res[1] != '\0')
822 goto jnext;
824 break;
827 if (res[0] == '@' && which_protocol(mailname) == PROTO_IMAP) {
828 res = str_concat_csvl(&s,
829 protbase(mailname), "/", res + 1, NULL)->s;
830 dyn = TRU1;
833 if (res[0] == '+' && getfold(cbuf, sizeof cbuf)) {
834 size_t i = strlen(cbuf);
836 res = str_concat_csvl(&s, cbuf,
837 (i > 0 && cbuf[i - 1] == '/') ? "" : "/",
838 res + 1, NULL)->s;
839 dyn = TRU1;
841 if (res[0] == '%' && res[1] == ':') {
842 res += 2;
843 goto jnext;
847 /* Catch the most common shell meta character */
848 jshell:
849 if (res[0] == '~' && (res[1] == '/' || res[1] == '\0')) {
850 res = str_concat_csvl(&s, homedir, res + 1, NULL)->s;
851 dyn = TRU1;
854 if (anyof(res, "|&;<>~{}()[]*?$`'\"\\") &&
855 which_protocol(res) == PROTO_FILE) {
856 res = _globname(res, fexpm);
857 dyn = TRU1;
858 goto jleave;
861 jislocal:
862 if (fexpm & FEXP_LOCAL)
863 switch (which_protocol(res)) {
864 case PROTO_FILE:
865 case PROTO_MAILDIR: /* XXX Really? ok MAILDIR for local? */
866 break;
867 default:
868 fprintf(stderr, tr(280,
869 "`%s': only a local file or directory may "
870 "be used\n"), name);
871 res = NULL;
872 break;
874 jleave:
875 if (res && ! dyn)
876 res = savestr(res);
877 return res;
880 void
881 demail(void)
884 if (value("keep") != NULL || rm(mailname) < 0) {
885 int fd = open(mailname, O_WRONLY|O_CREAT|O_TRUNC, 0600);
886 if (fd >= 0)
887 close(fd);
891 bool_t
892 var_folder_updated(char const *name, char **store)
894 char rv = TRU1;
895 char *folder, *unres = NULL, *res = NULL;
897 if ((folder = UNCONST(name)) == NULL)
898 goto jleave;
900 /* Expand the *folder*; skip `%:' prefix for simplicity of use */
901 /* XXX This *only* works because we do NOT
902 * XXX update environment variables via the "set" mechanism */
903 if (folder[0] == '%' && folder[1] == ':')
904 folder += 2;
905 if ((folder = fexpand(folder, FEXP_FULL)) == NULL) /* XXX error? */
906 goto jleave;
908 switch (which_protocol(folder)) {
909 case PROTO_POP3:
910 /* Ooops. This won't work */
911 fprintf(stderr, tr(501, "`folder' cannot be set to a flat, "
912 "readonly POP3 account\n"));
913 rv = FAL0;
914 goto jleave;
915 case PROTO_IMAP:
916 /* Simply assign what we have, even including `%:' prefix */
917 if (folder != name)
918 goto jvcopy;
919 goto jleave;
920 default:
921 /* Further expansion desired */
922 break;
925 /* All non-absolute paths are relative to our home directory */
926 if (*folder != '/') {
927 size_t l1 = strlen(homedir), l2 = strlen(folder);
928 unres = ac_alloc(l1 + l2 + 2);
929 memcpy(unres, homedir, l1);
930 unres[l1] = '/';
931 memcpy(unres + l1 + 1, folder, l2);
932 unres[l1 + 1 + l2] = '\0';
933 folder = unres;
936 /* Since lex.c:_update_mailname() uses realpath(3) if available to
937 * avoid that we loose track of our currently open folder in case we
938 * chdir away, but still checks the leading path portion against
939 * getfold() to be able to abbreviate to the +FOLDER syntax if
940 * possible, we need to realpath(3) the folder, too */
941 #ifdef HAVE_REALPATH
942 res = ac_alloc(MAXPATHLEN);
943 if (realpath(folder, res) == NULL)
944 fprintf(stderr, tr(151, "Can't canonicalize `%s'\n"), folder);
945 else
946 folder = res;
947 #endif
949 jvcopy:
950 *store = sstrdup(folder);
952 if (res != NULL)
953 ac_free(res);
954 if (unres != NULL)
955 ac_free(unres);
956 jleave:
957 return rv;
960 bool_t
961 getfold(char *name, size_t size)
963 char const *folder;
965 if ((folder = value("folder")) != NULL)
966 (void)n_strlcpy(name, folder, size);
967 return (folder != NULL);
971 * Return the name of the dead.letter file.
973 char const *
974 getdeadletter(void)
976 char const *cp;
978 if ((cp = value("DEAD")) == NULL ||
979 (cp = fexpand(cp, FEXP_LOCAL)) == NULL)
980 cp = fexpand("~/dead.letter", FEXP_LOCAL|FEXP_SHELL);
981 else if (*cp != '/') {
982 size_t sz = strlen(cp) + 3;
983 char *buf = ac_alloc(sz);
985 snprintf(buf, sz, "~/%s", cp);
986 cp = fexpand(buf, FEXP_LOCAL|FEXP_SHELL);
987 ac_free(buf);
989 if (cp == NULL)
990 cp = "dead.letter";
991 return cp;
994 static enum okay
995 get_header(struct message *mp)
997 (void)mp;
998 switch (mb.mb_type) {
999 case MB_FILE:
1000 case MB_MAILDIR:
1001 return (OKAY);
1002 #ifdef HAVE_POP3
1003 case MB_POP3:
1004 return (pop3_header(mp));
1005 #endif
1006 #ifdef HAVE_IMAP
1007 case MB_IMAP:
1008 case MB_CACHE:
1009 return imap_header(mp);
1010 #endif
1011 case MB_VOID:
1012 default:
1013 return (STOP);
1017 enum okay
1018 get_body(struct message *mp)
1020 (void)mp;
1021 switch (mb.mb_type) {
1022 case MB_FILE:
1023 case MB_MAILDIR:
1024 return (OKAY);
1025 #ifdef HAVE_POP3
1026 case MB_POP3:
1027 return (pop3_body(mp));
1028 #endif
1029 #ifdef HAVE_IMAP
1030 case MB_IMAP:
1031 case MB_CACHE:
1032 return imap_body(mp);
1033 #endif
1034 case MB_VOID:
1035 default:
1036 return (STOP);
1040 #ifdef HAVE_SOCKETS
1041 static long xwrite(int fd, const char *data, size_t sz);
1043 static long
1044 xwrite(int fd, const char *data, size_t sz)
1046 long wo;
1047 size_t wt = 0;
1049 do {
1050 if ((wo = write(fd, data + wt, sz - wt)) < 0) {
1051 if (errno == EINTR)
1052 continue;
1053 else
1054 return -1;
1056 wt += wo;
1057 } while (wt < sz);
1058 return sz;
1062 sclose(struct sock *sp)
1064 int i;
1066 if (sp->s_fd > 0) {
1067 if (sp->s_onclose != NULL)
1068 (*sp->s_onclose)();
1069 #ifdef HAVE_OPENSSL
1070 if (sp->s_use_ssl) {
1071 sp->s_use_ssl = 0;
1072 SSL_shutdown(sp->s_ssl);
1073 SSL_free(sp->s_ssl);
1074 sp->s_ssl = NULL;
1075 SSL_CTX_free(sp->s_ctx);
1076 sp->s_ctx = NULL;
1078 #endif
1080 i = close(sp->s_fd);
1082 sp->s_fd = -1;
1083 return i;
1085 sp->s_fd = -1;
1086 return 0;
1089 enum okay
1090 swrite(struct sock *sp, const char *data)
1092 return swrite1(sp, data, strlen(data), 0);
1095 enum okay
1096 swrite1(struct sock *sp, const char *data, int sz, int use_buffer)
1098 int x;
1100 if (use_buffer > 0) {
1101 int di;
1102 enum okay ok;
1104 if (sp->s_wbuf == NULL) {
1105 sp->s_wbufsize = 4096;
1106 sp->s_wbuf = smalloc(sp->s_wbufsize);
1107 sp->s_wbufpos = 0;
1109 while (sp->s_wbufpos + sz > sp->s_wbufsize) {
1110 di = sp->s_wbufsize - sp->s_wbufpos;
1111 sz -= di;
1112 if (sp->s_wbufpos > 0) {
1113 memcpy(&sp->s_wbuf[sp->s_wbufpos], data, di);
1114 ok = swrite1(sp, sp->s_wbuf,
1115 sp->s_wbufsize, -1);
1116 } else
1117 ok = swrite1(sp, data,
1118 sp->s_wbufsize, -1);
1119 if (ok != OKAY)
1120 return STOP;
1121 data += di;
1122 sp->s_wbufpos = 0;
1124 if (sz == sp->s_wbufsize) {
1125 ok = swrite1(sp, data, sp->s_wbufsize, -1);
1126 if (ok != OKAY)
1127 return STOP;
1128 } else if (sz) {
1129 memcpy(&sp->s_wbuf[sp->s_wbufpos], data, sz);
1130 sp->s_wbufpos += sz;
1132 return OKAY;
1133 } else if (use_buffer == 0 && sp->s_wbuf != NULL &&
1134 sp->s_wbufpos > 0) {
1135 x = sp->s_wbufpos;
1136 sp->s_wbufpos = 0;
1137 if (swrite1(sp, sp->s_wbuf, x, -1) != OKAY)
1138 return STOP;
1140 if (sz == 0)
1141 return OKAY;
1142 #ifdef HAVE_OPENSSL
1143 if (sp->s_use_ssl) {
1144 ssl_retry: x = SSL_write(sp->s_ssl, data, sz);
1145 if (x < 0) {
1146 switch (SSL_get_error(sp->s_ssl, x)) {
1147 case SSL_ERROR_WANT_READ:
1148 case SSL_ERROR_WANT_WRITE:
1149 goto ssl_retry;
1152 } else
1153 #endif
1155 x = xwrite(sp->s_fd, data, sz);
1157 if (x != sz) {
1158 char o[512];
1159 snprintf(o, sizeof o, "%s write error",
1160 sp->s_desc ? sp->s_desc : "socket");
1161 #ifdef HAVE_OPENSSL
1162 sp->s_use_ssl ? ssl_gen_err("%s", o) : perror(o);
1163 #else
1164 perror(o);
1165 #endif
1166 if (x < 0)
1167 sclose(sp);
1168 return STOP;
1170 return OKAY;
1173 enum okay
1174 sopen(const char *xserver, struct sock *sp, int use_ssl,
1175 const char *uhp, const char *portstr, int verbose)
1177 #ifdef HAVE_SO_SNDTIMEO
1178 struct timeval tv;
1179 #endif
1180 #ifdef HAVE_SO_LINGER
1181 struct linger li;
1182 #endif
1183 #ifdef HAVE_IPV6
1184 char hbuf[NI_MAXHOST];
1185 struct addrinfo hints, *res0, *res;
1186 #else
1187 struct sockaddr_in servaddr;
1188 struct in_addr **pptr;
1189 struct hostent *hp;
1190 struct servent *ep;
1191 unsigned short port = 0;
1192 #endif
1193 int sockfd;
1194 char *cp;
1195 char *server = UNCONST(xserver);
1196 (void)use_ssl;
1197 (void)uhp;
1199 if ((cp = strchr(server, ':')) != NULL) { /* TODO URI parse! IPv6! */
1200 portstr = &cp[1];
1201 #ifndef HAVE_IPV6
1202 port = strtol(portstr, NULL, 10);
1203 #endif
1204 server = salloc(cp - xserver + 1);
1205 memcpy(server, xserver, cp - xserver);
1206 server[cp - xserver] = '\0';
1209 /* Connect timeouts after 30 seconds */
1210 #ifdef HAVE_SO_SNDTIMEO
1211 tv.tv_sec = 30;
1212 tv.tv_usec = 0;
1213 #endif
1215 #ifdef HAVE_IPV6
1216 if (verbose)
1217 fprintf(stderr, "Resolving host %s . . .", server);
1218 memset(&hints, 0, sizeof hints);
1219 hints.ai_socktype = SOCK_STREAM;
1220 if (getaddrinfo(server, portstr, &hints, &res0) != 0) {
1221 fprintf(stderr, tr(252, " lookup of `%s' failed.\n"), server);
1222 return STOP;
1223 } else if (verbose)
1224 fprintf(stderr, tr(500, " done.\n"));
1226 sockfd = -1;
1227 for (res = res0; res != NULL && sockfd < 0; res = res->ai_next) {
1228 if (verbose) {
1229 if (getnameinfo(res->ai_addr, res->ai_addrlen,
1230 hbuf, sizeof hbuf, NULL, 0,
1231 NI_NUMERICHOST) != 0)
1232 strcpy(hbuf, "unknown host");
1233 fprintf(stderr, tr(192,
1234 "%sConnecting to %s:%s . . ."),
1235 (res == res0) ? "" : "\n",
1236 hbuf, portstr);
1238 if ((sockfd = socket(res->ai_family, res->ai_socktype,
1239 res->ai_protocol)) >= 0) {
1240 # ifdef HAVE_SO_SNDTIMEO
1241 setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO,
1242 &tv, sizeof tv);
1243 # endif
1244 if (connect(sockfd, res->ai_addr, res->ai_addrlen)!=0) {
1245 close(sockfd);
1246 sockfd = -1;
1250 if (sockfd < 0) {
1251 perror(tr(254, " could not connect"));
1252 freeaddrinfo(res0);
1253 return STOP;
1255 freeaddrinfo(res0);
1257 #else /* HAVE_IPV6 */
1258 if (port == 0) {
1259 if (strcmp(portstr, "smtp") == 0)
1260 port = htons(25);
1261 else if (strcmp(portstr, "smtps") == 0)
1262 port = htons(465);
1263 # ifdef HAVE_IMAP
1264 else if (strcmp(portstr, "imap") == 0)
1265 port = htons(143);
1266 else if (strcmp(portstr, "imaps") == 0)
1267 port = htons(993);
1268 # endif
1269 # ifdef HAVE_POP3
1270 else if (strcmp(portstr, "pop3") == 0)
1271 port = htons(110);
1272 else if (strcmp(portstr, "pop3s") == 0)
1273 port = htons(995);
1274 # endif
1275 else if ((ep = getservbyname(UNCONST(portstr), "tcp")) != NULL)
1276 port = ep->s_port;
1277 else {
1278 fprintf(stderr, tr(251, "Unknown service: %s\n"),
1279 portstr);
1280 return (STOP);
1282 } else
1283 port = htons(port);
1285 if (verbose)
1286 fprintf(stderr, "Resolving host %s . . .", server);
1287 if ((hp = gethostbyname(server)) == NULL) {
1288 fprintf(stderr, tr(252, " lookup of `%s' failed.\n"), server);
1289 return STOP;
1290 } else if (verbose)
1291 fprintf(stderr, tr(500, " done.\n"));
1293 pptr = (struct in_addr **)hp->h_addr_list;
1294 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
1295 perror(tr(253, "could not create socket"));
1296 return STOP;
1298 memset(&servaddr, 0, sizeof servaddr);
1299 servaddr.sin_family = AF_INET;
1300 servaddr.sin_port = port;
1301 memcpy(&servaddr.sin_addr, *pptr, sizeof(struct in_addr));
1302 if (verbose)
1303 fprintf(stderr, tr(192, "%sConnecting to %s:%d . . ."),
1304 "", inet_ntoa(**pptr), ntohs(port));
1306 # ifdef HAVE_SO_SNDTIMEO
1307 setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
1308 # endif
1309 if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof servaddr)
1310 != 0) {
1311 perror(tr(254, " could not connect"));
1312 return STOP;
1314 #endif /* HAVE_IPV6 */
1315 if (verbose)
1316 fputs(tr(193, " connected.\n"), stderr);
1318 /* And the regular timeouts */
1319 #ifdef HAVE_SO_SNDTIMEO
1320 tv.tv_sec = 42;
1321 tv.tv_usec = 0;
1322 setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
1323 setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
1324 #endif
1325 #ifdef HAVE_SO_LINGER
1326 li.l_onoff = 1;
1327 li.l_linger = 42;
1328 setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &li, sizeof li);
1329 #endif
1331 memset(sp, 0, sizeof *sp);
1332 sp->s_fd = sockfd;
1333 #ifdef HAVE_SSL
1334 if (use_ssl && ssl_open(server, sp, uhp) != OKAY) {
1335 sclose(sp);
1336 return STOP;
1338 #endif
1339 return OKAY;
1343 (sgetline)(char **line, size_t *linesize, size_t *linelen, struct sock *sp
1344 SMALLOC_DEBUG_ARGS)
1346 char *lp = *line;
1348 if (sp->s_rsz < 0) {
1349 sclose(sp);
1350 return sp->s_rsz;
1352 do {
1353 if (*line == NULL || lp > &(*line)[*linesize - 128]) {
1354 size_t diff = lp - *line;
1355 *line = (srealloc)(*line, *linesize += 256
1356 SMALLOC_DEBUG_ARGSCALL);
1357 lp = &(*line)[diff];
1359 if (sp->s_rbufptr == NULL ||
1360 sp->s_rbufptr >= &sp->s_rbuf[sp->s_rsz]) {
1361 #ifdef HAVE_OPENSSL
1362 if (sp->s_use_ssl) {
1363 ssl_retry: if ((sp->s_rsz = SSL_read(sp->s_ssl,
1364 sp->s_rbuf,
1365 sizeof sp->s_rbuf)) <= 0) {
1366 if (sp->s_rsz < 0) {
1367 char o[512];
1368 switch(SSL_get_error(sp->s_ssl,
1369 sp->s_rsz)) {
1370 case SSL_ERROR_WANT_READ:
1371 case SSL_ERROR_WANT_WRITE:
1372 goto ssl_retry;
1374 snprintf(o, sizeof o, "%s",
1375 sp->s_desc ?
1376 sp->s_desc :
1377 "socket");
1378 ssl_gen_err("%s", o);
1381 break;
1383 } else
1384 #endif
1386 again: if ((sp->s_rsz = read(sp->s_fd, sp->s_rbuf,
1387 sizeof sp->s_rbuf)) <= 0) {
1388 if (sp->s_rsz < 0) {
1389 char o[512];
1390 if (errno == EINTR)
1391 goto again;
1392 snprintf(o, sizeof o, "%s",
1393 sp->s_desc ?
1394 sp->s_desc :
1395 "socket");
1396 perror(o);
1398 break;
1401 sp->s_rbufptr = sp->s_rbuf;
1403 } while ((*lp++ = *sp->s_rbufptr++) != '\n');
1404 *lp = '\0';
1405 if (linelen)
1406 *linelen = lp - *line;
1407 return lp - *line;
1409 #endif /* HAVE_SOCKETS */
1411 void
1412 load(char const *name)
1414 FILE *in, *oldin;
1416 if (name == NULL || (in = Fopen(name, "r")) == NULL)
1417 return;
1418 oldin = _input;
1419 _input = in;
1420 loading = TRU1;
1421 sourcing = TRU1;
1422 commands();
1423 loading = FAL0;
1424 sourcing = FAL0;
1425 _input = oldin;
1426 Fclose(in);
1430 csource(void *v)
1432 int rv = 1;
1433 char **arglist = v;
1434 FILE *fi;
1435 char *cp;
1437 if ((cp = fexpand(*arglist, FEXP_LOCAL)) == NULL)
1438 goto jleave;
1439 if ((fi = Fopen(cp, "r")) == NULL) {
1440 perror(cp);
1441 goto jleave;
1443 if (_ssp >= SSTACK - 1) {
1444 fprintf(stderr, tr(3, "Too much \"sourcing\" going on.\n"));
1445 Fclose(fi);
1446 goto jleave;
1449 _sstack[_ssp].s_file = _input;
1450 _sstack[_ssp].s_cond = cond;
1451 _sstack[_ssp].s_loading = loading;
1452 ++_ssp;
1453 loading = FAL0;
1454 cond = CANY;
1455 _input = fi;
1456 sourcing = TRU1;
1457 rv = 0;
1458 jleave:
1459 return rv;
1463 unstack(void)
1465 int rv = 1;
1467 if (_ssp <= 0) {
1468 fprintf(stderr, tr(4, "\"Source\" stack over-pop.\n"));
1469 sourcing = FAL0;
1470 goto jleave;
1473 Fclose(_input);
1474 if (cond != CANY)
1475 fprintf(stderr, tr(5, "Unmatched \"if\"\n"));
1476 --_ssp;
1477 cond = _sstack[_ssp].s_cond;
1478 loading = _sstack[_ssp].s_loading;
1479 _input = _sstack[_ssp].s_file;
1480 if (_ssp == 0)
1481 sourcing = loading;
1482 rv = 0;
1483 jleave:
1484 return rv;