popen.c: some cleanup, use other wait_child() from now on
[s-mailx.git] / collect.c
blobd2ff6303f7adf11d011e507cfc7cff5a324e2f9b
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Collect input from standard input, handling ~ escapes.
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 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
45 * The following hokiness with global variables is so that on
46 * receipt of an interrupt signal, the partial message can be salted
47 * away on dead.letter.
50 static sighandler_type _coll_saveint; /* Previous SIGINT value */
51 static sighandler_type _coll_savehup; /* Previous SIGHUP value */
52 static sighandler_type _coll_savetstp; /* Previous SIGTSTP value */
53 static sighandler_type _coll_savettou; /* Previous SIGTTOU value */
54 static sighandler_type _coll_savettin; /* Previous SIGTTIN value */
55 static FILE *_coll_fp; /* File for saving away */
56 static int volatile _coll_hadintr; /* Have seen one SIGINT so far */
57 static sigjmp_buf _coll_jmp; /* To get back to work */
58 static int _coll_jmp_p; /* whether to long jump */
59 static sigjmp_buf _coll_abort; /* To end collection with error */
60 static sigjmp_buf _coll_pipejmp; /* On broken pipe */
62 /* Handle `~:', `~_' */
63 static void _execute_command(struct header *hp, char *linebuf,
64 size_t linesize);
66 /* If *interactive* is set and *doecho* is, too, also dump to *stdout* */
67 static int _include_file(FILE *fbuf, char const *name, int *linecount,
68 int *charcount, bool_t doecho);
70 static void _collect_onpipe(int signo);
71 static void insertcommand(FILE *fp, char const *cmd);
72 static void print_collf(FILE *collf, struct header *hp);
73 static int exwrite(char const *name, FILE *fp, int f);
74 static enum okay makeheader(FILE *fp, struct header *hp);
75 static void mesedit(int c, struct header *hp);
76 static void mespipe(char *cmd);
77 static int forward(char *ms, FILE *fp, int f);
78 static void collstop(int s);
79 static void collint(int s);
80 static void collhup(int s);
81 static int putesc(const char *s, FILE *stream);
83 static void
84 _execute_command(struct header *hp, char *linebuf, size_t linesize)
86 /* The problem arises if there are rfc822 message attachments and the
87 * user uses `~:' to change the current file. TODO Unfortunately we
88 * TODO cannot simply keep a pointer to, or increment a reference count
89 * TODO of the current `file' (mailbox that is) object, because the
90 * TODO codebase doesn't deal with that at all; so, until some far
91 * TODO later time, copy the name of the path, and warn the user if it
92 * TODO changed; we COULD use the AC_TMPFILE attachment type, i.e.,
93 * TODO copy the message attachments over to temporary files, but that
94 * TODO would require more changes so that the user still can recognize
95 * TODO in `~@' etc. that its a rfc822 message attachment; see below */
96 char *mnbuf = NULL;
97 size_t mnlen = 0 /* silence CC */;
98 struct attachment *ap;
100 /* If the above todo is worked, remove or outsource to attachments.c! */
101 if ((ap = hp->h_attach) != NULL) do
102 if (ap->a_msgno) {
103 mnlen = strlen(mailname) + 1;
104 mnbuf = ac_alloc(mnlen);
105 memcpy(mnbuf, mailname, mnlen);
107 while ((ap = ap->a_flink) != NULL);
109 inhook = 0;
110 execute(linebuf, TRU1, linesize);
112 if (mnbuf != NULL && memcmp(mnbuf, mailname, mnlen) != 0)
113 fputs(tr(237, "The mail file has changed, existing rfc822 "
114 "attachments became invalid!\n"), stderr);
117 static int
118 _include_file(FILE *fbuf, char const *name, int *linecount, int *charcount,
119 bool_t doecho)
121 int ret = -1;
122 char *linebuf = NULL;
123 size_t linesize = 0, linelen, cnt;
125 if (fbuf == NULL) {
126 if ((fbuf = Fopen(name, "r")) == NULL) {
127 perror(name);
128 goto jleave;
130 } else
131 fflush_rewind(fbuf);
133 *linecount = *charcount = 0;
134 cnt = fsize(fbuf);
135 while (fgetline(&linebuf, &linesize, &cnt, &linelen, fbuf, 0)
136 != NULL) {
137 if (fwrite(linebuf, sizeof *linebuf, linelen, _coll_fp)
138 != linelen)
139 goto jleave;
140 if ((options & OPT_INTERACTIVE) && doecho)
141 fwrite(linebuf, sizeof *linebuf, linelen, stdout);
142 ++(*linecount);
143 (*charcount) += linelen;
145 if (fflush(_coll_fp))
146 goto jleave;
148 ret = 0;
149 jleave:
150 if (linebuf != NULL)
151 free(linebuf);
152 if (fbuf != NULL)
153 Fclose(fbuf);
154 return (ret);
157 /*ARGSUSED*/
158 static void
159 _collect_onpipe(int signo)
161 UNUSED(signo);
162 siglongjmp(_coll_pipejmp, 1);
166 * Execute cmd and insert its standard output into fp.
168 static void
169 insertcommand(FILE *fp, char const *cmd)
171 FILE *ibuf = NULL;
172 char const *cp;
173 int c;
175 cp = value("SHELL");
176 if (cp == NULL)
177 cp = SHELL;
178 if ((ibuf = Popen(cmd, "r", cp, 0)) != NULL) {
179 while ((c = getc(ibuf)) != EOF) /* XXX bytewise, yuck! */
180 putc(c, fp);
181 Pclose(ibuf, TRU1);
182 } else
183 perror(cmd);
187 * ~p command.
189 static void
190 print_collf(FILE *cf, struct header *hp)
192 char *lbuf = NULL;
193 FILE *volatile obuf = stdout;
194 struct attachment *ap;
195 char const *cp;
196 enum gfield gf;
197 size_t linecnt, maxlines, linesize = 0, linelen, cnt, cnt2;
199 fflush(cf);
200 rewind(cf);
201 cnt = cnt2 = fsize(cf);
203 if (IS_TTY_SESSION() && (cp = voption("crt")) != NULL) {
204 for (linecnt = 0;
205 fgetline(&lbuf, &linesize, &cnt2, NULL, cf, 0);
206 linecnt++);
207 rewind(cf);
208 maxlines = (*cp == '\0' ? screensize() : atoi(cp));
209 maxlines -= 4;
210 if (hp->h_to)
211 maxlines--;
212 if (hp->h_subject)
213 maxlines--;
214 if (hp->h_cc)
215 maxlines--;
216 if (hp->h_bcc)
217 maxlines--;
218 if (hp->h_attach)
219 maxlines--;
220 maxlines -= myaddrs(hp) != NULL || hp->h_from != NULL;
221 maxlines -= value("ORGANIZATION") != NULL ||
222 hp->h_organization != NULL;
223 maxlines -= value("replyto") != NULL || hp->h_replyto != NULL;
224 maxlines -= value("sender") != NULL || hp->h_sender != NULL;
225 if ((long)maxlines < 0 || linecnt > maxlines) {
226 cp = get_pager();
227 if (sigsetjmp(_coll_pipejmp, 1))
228 goto endpipe;
229 obuf = Popen(cp, "w", NULL, 1);
230 if (obuf == NULL) {
231 perror(cp);
232 obuf = stdout;
233 } else
234 safe_signal(SIGPIPE, &_collect_onpipe);
238 fprintf(obuf, tr(62, "-------\nMessage contains:\n"));
239 gf = GIDENT|GTO|GSUBJECT|GCC|GBCC|GNL|GFILES;
240 if (value("fullnames"))
241 gf |= GCOMMA;
242 puthead(hp, obuf, gf, SEND_TODISP, CONV_NONE, NULL, NULL);
243 while (fgetline(&lbuf, &linesize, &cnt, &linelen, cf, 1))
244 prout(lbuf, linelen, obuf);
245 if (hp->h_attach != NULL) {
246 fputs(tr(63, "-------\nAttachments:\n"), obuf);
247 for (ap = hp->h_attach; ap != NULL; ap = ap->a_flink) {
248 if (ap->a_msgno)
249 fprintf(obuf, " - message %u\n", ap->a_msgno);
250 else {
251 /* TODO after MIME/send layer rewrite we *know*
252 * TODO the details of the attachment here,
253 * TODO so adjust this again, then */
254 char const *cs, *csi = "-> ";
256 if ((cs = ap->a_charset) == NULL &&
257 (csi = "<- ",
258 cs = ap->a_input_charset)
259 == NULL)
260 cs = charset_get_lc();
261 if ((cp = ap->a_content_type) == NULL)
262 cp = "?";
263 else if (ascncasecmp(cp, "text/", 5) != 0)
264 csi = "";
266 fprintf(obuf, " - [%s, %s%s] %s\n",
267 cp, csi, cs, ap->a_name);
271 endpipe:
272 if (obuf != stdout) {
273 safe_signal(SIGPIPE, SIG_IGN);
274 Pclose(obuf, TRU1);
275 safe_signal(SIGPIPE, dflpipe);
277 if (lbuf)
278 free(lbuf);
281 FL FILE *
282 collect(struct header *hp, int printheaders, struct message *mp,
283 char *quotefile, int doprefix)
285 FILE *fbuf;
286 struct ignoretab *quoteig;
287 int lc, cc, eofcount, c, t;
288 int volatile escape, getfields;
289 char *linebuf = NULL, *quote = NULL, *tempMail = NULL;
290 char const *cp;
291 size_t linesize = 0;
292 long cnt;
293 enum sendaction action;
294 sigset_t oset, nset;
295 sighandler_type savedtop;
297 _coll_fp = NULL;
299 * Start catching signals from here, but we're still die on interrupts
300 * until we're in the main loop.
302 sigemptyset(&nset);
303 sigaddset(&nset, SIGINT);
304 sigaddset(&nset, SIGHUP);
305 sigprocmask(SIG_BLOCK, &nset, &oset);
306 handlerpush(collint);
307 if ((_coll_saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
308 safe_signal(SIGINT, collint);
309 if ((_coll_savehup = safe_signal(SIGHUP, SIG_IGN)) != SIG_IGN)
310 safe_signal(SIGHUP, collhup);
311 /* TODO We do a lot of redundant signal handling, especially
312 * TODO with the command line editor(s); try to merge this */
313 _coll_savetstp = safe_signal(SIGTSTP, collstop);
314 _coll_savettou = safe_signal(SIGTTOU, collstop);
315 _coll_savettin = safe_signal(SIGTTIN, collstop);
316 if (sigsetjmp(_coll_abort, 1))
317 goto jerr;
318 if (sigsetjmp(_coll_jmp, 1))
319 goto jerr;
320 sigprocmask(SIG_SETMASK, &oset, (sigset_t *)NULL);
322 noreset++;
323 if ((_coll_fp = Ftemp(&tempMail, "Rs", "w+", 0600, 1)) == NULL) {
324 perror(tr(51, "temporary mail file"));
325 goto jerr;
327 unlink(tempMail);
328 Ftfree(&tempMail);
330 if ((cp = value("NAIL_HEAD")) != NULL && putesc(cp, _coll_fp) < 0)
331 goto jerr;
334 * If we are going to prompt for a subject,
335 * refrain from printing a newline after
336 * the headers (since some people mind).
338 getfields = 0;
339 if (! (options & OPT_t_FLAG)) {
340 t = GTO|GSUBJECT|GCC|GNL;
341 if (value("fullnames"))
342 t |= GCOMMA;
343 if (hp->h_subject == NULL && (options & OPT_INTERACTIVE) &&
344 (value("ask") != NULL || value("asksub") != NULL))
345 t &= ~GNL, getfields |= GSUBJECT;
346 if (hp->h_to == NULL && (options & OPT_INTERACTIVE))
347 t &= ~GNL, getfields |= GTO;
348 if (value("bsdcompat") == NULL && value("askatend") == NULL &&
349 (options & OPT_INTERACTIVE)) {
350 if (hp->h_bcc == NULL && value("askbcc"))
351 t &= ~GNL, getfields |= GBCC;
352 if (hp->h_cc == NULL && value("askcc"))
353 t &= ~GNL, getfields |= GCC;
355 if (printheaders) {
356 (void)puthead(hp, stdout, t, SEND_TODISP, CONV_NONE,
357 NULL, NULL);
358 (void)fflush(stdout);
363 * Quote an original message
365 if (mp != NULL && (doprefix || (quote = value("quote")) != NULL)) {
366 quoteig = allignore;
367 action = SEND_QUOTE;
368 if (doprefix) {
369 quoteig = fwdignore;
370 if ((cp = value("fwdheading")) == NULL)
371 cp = "-------- Original Message --------";
372 if (*cp && fprintf(_coll_fp, "%s\n", cp) < 0)
373 goto jerr;
374 } else if (strcmp(quote, "noheading") == 0) {
375 /*EMPTY*/;
376 } else if (strcmp(quote, "headers") == 0) {
377 quoteig = ignore;
378 } else if (strcmp(quote, "allheaders") == 0) {
379 quoteig = NULL;
380 action = SEND_QUOTE_ALL;
381 } else {
382 cp = hfield1("from", mp);
383 if (cp != NULL && (cnt = (long)strlen(cp)) > 0) {
384 if (xmime_write(cp, cnt, _coll_fp,
385 CONV_FROMHDR, TD_NONE,
386 NULL) < 0)
387 goto jerr;
388 if (fprintf(_coll_fp,
389 tr(52, " wrote:\n\n")) < 0)
390 goto jerr;
393 if (fflush(_coll_fp))
394 goto jerr;
395 cp = value("indentprefix");
396 if (cp != NULL && *cp == '\0')
397 cp = "\t";
398 if (sendmp(mp, _coll_fp, quoteig, (doprefix ? NULL : cp),
399 action, NULL) < 0)
400 goto jerr;
403 /* Print what we have sofar also on the terminal */
404 rewind(_coll_fp);
405 while ((c = getc(_coll_fp)) != EOF) /* XXX bytewise, yuck! */
406 (void)putc(c, stdout);
407 if (fseek(_coll_fp, 0, SEEK_END))
408 goto jerr;
410 escape = ((cp = value("escape")) != NULL) ? *cp : ESCAPE;
411 eofcount = 0;
412 _coll_hadintr = 0;
414 if (!sigsetjmp(_coll_jmp, 1)) {
415 if (getfields)
416 grab_headers(hp, getfields, 1);
417 if (quotefile != NULL) {
418 if (_include_file(NULL, quotefile, &lc, &cc, TRU1) != 0)
419 goto jerr;
421 if ((options & OPT_INTERACTIVE) && value("editalong")) {
422 rewind(_coll_fp);
423 mesedit('e', hp);
424 goto jcont;
426 } else {
428 * Come here for printing the after-signal message.
429 * Duplicate messages won't be printed because
430 * the write is aborted if we get a SIGTTOU.
432 jcont:
433 if (_coll_hadintr) {
434 (void)fprintf(stderr, tr(53,
435 "\n(Interrupt -- one more to kill letter)\n"));
436 } else {
437 printf(tr(54, "(continue)\n"));
438 (void)fflush(stdout);
443 * No tilde escapes, interrupts not expected. Simply copy STDIN
445 if (! (options & (OPT_INTERACTIVE | OPT_t_FLAG|OPT_TILDE_FLAG))) {
446 linebuf = srealloc(linebuf, linesize = LINESIZE);
447 while ((cnt = fread(linebuf, sizeof *linebuf,
448 linesize, stdin)) > 0) {
449 if ((size_t)cnt != fwrite(linebuf, sizeof *linebuf,
450 cnt, _coll_fp))
451 goto jerr;
453 if (fflush(_coll_fp))
454 goto jerr;
455 goto jout;
459 * The interactive collect loop
461 for (;;) {
462 _coll_jmp_p = 1;
463 cnt = readline_input(LNED_NONE, "", &linebuf, &linesize);
464 _coll_jmp_p = 0;
466 if (cnt < 0) {
467 if ((options & OPT_INTERACTIVE) &&
468 value("ignoreeof") != NULL && ++eofcount < 25) {
469 printf(tr(55,
470 "Use \".\" to terminate letter\n"));
471 continue;
473 break;
475 if ((options & OPT_t_FLAG) && cnt == 0) {
476 rewind(_coll_fp);
477 if (makeheader(_coll_fp, hp) != OKAY)
478 goto jerr;
479 rewind(_coll_fp);
480 options &= ~OPT_t_FLAG;
481 continue;
484 eofcount = 0;
485 _coll_hadintr = 0;
486 if (linebuf[0] == '.' && linebuf[1] == '\0' &&
487 (options & (OPT_INTERACTIVE|OPT_TILDE_FLAG)) &&
488 (boption("dot") || boption("ignoreeof")))
489 break;
490 if (cnt == 0 || linebuf[0] != escape || ! (options &
491 (OPT_INTERACTIVE | OPT_TILDE_FLAG))) {
492 /* TODO calls putline(), which *always* appends LF;
493 * TODO thus, STDIN with -t will ALWAYS end with LF,
494 * TODO even if no trailing LF and QP CTE */
495 if (putline(_coll_fp, linebuf, cnt) < 0)
496 goto jerr;
497 continue;
500 c = linebuf[1];
501 switch (c) {
502 default:
504 * On double escape, just send the single one.
505 * Otherwise, it's an error.
507 if (c == escape) {
508 if (putline(_coll_fp, &linebuf[1], cnt - 1) < 0)
509 goto jerr;
510 else
511 break;
513 fputs(tr(56, "Unknown tilde escape.\n"), stderr);
514 break;
515 #ifdef HAVE_DEBUG
516 case 'C':
517 /* Dump core */
518 core(NULL);
519 break;
520 #endif
521 case '!':
522 /* Shell escape, send the balance of line to sh -c */
523 shell(&linebuf[2]);
524 break;
525 case ':':
526 /* FALLTHRU */
527 case '_':
528 /* Escape to command mode, but be nice! */
529 _execute_command(hp, linebuf + 2, cnt - 2);
530 goto jcont;
531 case '.':
532 /* Simulate end of file on input */
533 goto jout;
534 case 'x':
535 /* Same as 'q', but no dead.letter saving */
536 /* FALLTHRU */
537 case 'q':
538 /* Force a quit, act like an interrupt had happened */
539 ++_coll_hadintr;
540 collint((c == 'x') ? 0 : SIGINT);
541 exit(1);
542 /*NOTREACHED*/
543 case 'h':
544 /* Grab a bunch of headers */
546 grab_headers(hp, GTO|GSUBJECT|GCC|GBCC,
547 (value("bsdcompat") != NULL &&
548 value("bsdorder") != NULL));
549 while (hp->h_to == NULL);
550 goto jcont;
551 case 'H':
552 /* Grab extra headers */
554 grab_headers(hp, GEXTRA, 0);
555 while (check_from_and_sender(hp->h_from, hp->h_sender));
556 goto jcont;
557 case 't':
558 /* Add to the To list */
559 while ((hp->h_to = cat(hp->h_to, checkaddrs(
560 lextract(&linebuf[2], GTO|GFULL))))
561 == NULL)
563 break;
564 case 's':
565 /* Set the Subject list */
566 cp = &linebuf[2];
567 while (whitechar(*cp))
568 ++cp;
569 hp->h_subject = savestr(cp);
570 break;
571 #ifdef HAVE_DEBUG
572 case 'S':
573 c_sstats(NULL);
574 break;
575 #endif
576 case '@':
577 /* Edit the attachment list */
578 if (linebuf[2] != '\0')
579 hp->h_attach = append_attachments(hp->h_attach,
580 &linebuf[2]);
581 else
582 hp->h_attach = edit_attachments(hp->h_attach);
583 break;
584 case 'c':
585 /* Add to the CC list */
586 hp->h_cc = cat(hp->h_cc, checkaddrs(
587 lextract(&linebuf[2], GCC|GFULL)));
588 break;
589 case 'b':
590 /* Add stuff to blind carbon copies list */
591 hp->h_bcc = cat(hp->h_bcc, checkaddrs(
592 lextract(&linebuf[2], GBCC|GFULL)));
593 break;
594 case 'd':
595 strncpy(linebuf + 2, getdeadletter(), linesize - 2);
596 linebuf[linesize-1]='\0';
597 /*FALLTHRU*/
598 case 'r':
599 case '<':
601 * Invoke a file:
602 * Search for the file name,
603 * then open it and copy the contents to _coll_fp.
605 cp = &linebuf[2];
606 while (whitechar(*cp))
607 cp++;
608 if (*cp == '\0') {
609 fputs(tr(57, "Interpolate what file?\n"),
610 stderr);
611 break;
613 if (*cp == '!') {
614 insertcommand(_coll_fp, cp + 1);
615 break;
617 if ((cp = file_expand(cp)) == NULL)
618 break;
619 if (is_dir(cp)) {
620 fprintf(stderr, tr(58, "%s: Directory\n"), cp);
621 break;
623 if ((fbuf = Fopen(cp, "r")) == NULL) {
624 perror(cp);
625 break;
627 printf(tr(59, "\"%s\" "), cp);
628 fflush(stdout);
629 if (_include_file(fbuf, cp, &lc, &cc, FAL0) != 0)
630 goto jerr;
631 printf(tr(60, "%d/%d\n"), lc, cc);
632 break;
633 case 'i':
634 /* Insert an environment variable into the file */
635 cp = &linebuf[2];
636 while (whitechar(*cp))
637 ++cp;
638 if ((cp = value(cp)) == NULL || *cp == '\0')
639 break;
640 if (putesc(cp, _coll_fp) < 0)
641 goto jerr;
642 if ((options & OPT_INTERACTIVE) &&
643 putesc(cp, stdout) < 0)
644 goto jerr;
645 break;
646 case 'a':
647 case 'A':
648 /* Insert the contents of a signature variable */
649 if ((cp = value(c == 'a' ? "sign" : "Sign")) != NULL &&
650 *cp != '\0') {
651 if (putesc(cp, _coll_fp) < 0)
652 goto jerr;
653 if ((options & OPT_INTERACTIVE) &&
654 putesc(cp, stdout) < 0)
655 goto jerr;
657 break;
658 case 'w':
659 /* Write the message on a file */
660 cp = &linebuf[2];
661 while (blankchar(*cp))
662 ++cp;
663 if (*cp == '\0' || (cp = file_expand(cp)) == NULL) {
664 fputs(tr(61, "Write what file!?\n"), stderr);
665 break;
667 rewind(_coll_fp);
668 if (exwrite(cp, _coll_fp, 1) < 0)
669 goto jerr;
670 break;
671 case 'm':
672 case 'M':
673 case 'f':
674 case 'F':
676 * Interpolate the named messages, if we
677 * are in receiving mail mode. Does the
678 * standard list processing garbage.
679 * If ~f is given, we don't shift over.
681 if (forward(linebuf + 2, _coll_fp, c) < 0)
682 goto jerr;
683 goto jcont;
684 case 'p':
686 * Print out the current state of the
687 * message without altering anything.
689 print_collf(_coll_fp, hp);
690 goto jcont;
691 case '|':
693 * Pipe message through command.
694 * Collect output as new message.
696 rewind(_coll_fp);
697 mespipe(&linebuf[2]);
698 goto jcont;
699 case 'v':
700 case 'e':
702 * Edit the current message.
703 * 'e' means to use EDITOR
704 * 'v' means to use VISUAL
706 rewind(_coll_fp);
707 mesedit(c, value("editheaders") ? hp : NULL);
708 goto jcont;
709 case '?':
711 * Last the lengthy help string.
712 * (Very ugly, but take care for compiler supported
713 * string lengths :()
715 (void)puts(tr(300,
716 "-------------------- ~ ESCAPES ----------------------------\n"
717 "~~ Quote a single tilde\n"
718 "~@ [file ...] Edit attachment list\n"
719 "~b users Add users to \"blind\" cc list\n"
720 "~c users Add users to cc list\n"
721 "~d Read in dead.letter\n"
722 "~e Edit the message buffer\n"
723 "~f messages Read in messages without indenting lines\n"
724 "~F messages Same as ~f, but keep all header lines\n"
725 "~h Prompt for to list, subject, cc, and \"blind\" cc list\n"));
726 (void)puts(tr(301,
727 "~r file Read a file into the message buffer\n"
728 "~p Print the message buffer\n"
729 "~q Abort message composition and save text to dead.letter\n"
730 "~m messages Read in messages with each line indented\n"
731 "~M messages Same as ~m, but keep all header lines\n"
732 "~s subject Set subject\n"
733 "~t users Add users to to list\n"
734 "~v Invoke display editor on message\n"
735 "~w file Write message onto file\n"
736 "~x Abort message composition and discard text written so far\n"));
737 (void)puts(tr(302,
738 "~!command Invoke the shell\n"
739 "~:command Execute a regular command\n"
740 "-----------------------------------------------------------\n"));
741 break;
745 jout:
746 if (_coll_fp != NULL) {
747 if ((cp = value("NAIL_TAIL")) != NULL) {
748 if (putesc(cp, _coll_fp) < 0)
749 goto jerr;
750 if ((options & OPT_INTERACTIVE) &&
751 putesc(cp, stdout) < 0)
752 goto jerr;
754 rewind(_coll_fp);
756 if (linebuf != NULL)
757 free(linebuf);
758 handlerpop();
759 noreset--;
760 sigemptyset(&nset);
761 sigaddset(&nset, SIGINT);
762 sigaddset(&nset, SIGHUP);
763 sigprocmask(SIG_BLOCK, &nset, (sigset_t*)NULL);
764 safe_signal(SIGINT, _coll_saveint);
765 safe_signal(SIGHUP, _coll_savehup);
766 safe_signal(SIGTSTP, _coll_savetstp);
767 safe_signal(SIGTTOU, _coll_savettou);
768 safe_signal(SIGTTIN, _coll_savettin);
769 sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
770 return _coll_fp;
772 jerr:
773 if (tempMail != NULL) {
774 rm(tempMail);
775 Ftfree(&tempMail);
777 if (_coll_fp != NULL) {
778 Fclose(_coll_fp);
779 _coll_fp = NULL;
781 goto jout;
785 * Write a file, ex-like if f set.
787 static int
788 exwrite(char const *name, FILE *fp, int f)
790 FILE *of;
791 int c;
792 long cc;
793 int lc;
795 if (f) {
796 printf("\"%s\" ", name);
797 fflush(stdout);
799 if ((of = Fopen(name, "a")) == NULL) {
800 perror(NULL);
801 return(-1);
803 lc = 0;
804 cc = 0;
805 while ((c = getc(fp)) != EOF) {
806 cc++;
807 if (c == '\n')
808 lc++;
809 putc(c, of);
810 if (ferror(of)) {
811 perror(name);
812 Fclose(of);
813 return(-1);
816 Fclose(of);
817 printf(tr(65, "%d/%ld\n"), lc, cc);
818 fflush(stdout);
819 return(0);
822 static enum okay
823 makeheader(FILE *fp, struct header *hp)
825 char *tempEdit;
826 FILE *nf;
827 int c;
829 if ((nf = Ftemp(&tempEdit, "Re", "w+", 0600, 1)) == NULL) {
830 perror(tr(66, "temporary mail edit file"));
831 return STOP;
833 unlink(tempEdit);
834 Ftfree(&tempEdit);
836 extract_header(fp, hp);
837 while ((c = getc(fp)) != EOF) /* XXX bytewise, yuck! */
838 putc(c, nf);
839 if (fp != _coll_fp)
840 Fclose(_coll_fp);
841 Fclose(fp);
842 _coll_fp = nf;
843 if (check_from_and_sender(hp->h_from, hp->h_sender))
844 return STOP;
845 return OKAY;
849 * Edit the message being collected on fp.
850 * On return, make the edit file the new temp file.
852 static void
853 mesedit(int c, struct header *hp)
855 sighandler_type sigint = safe_signal(SIGINT, SIG_IGN);
856 char *saved = value("add-file-recipients");
857 FILE *nf;
859 assign("add-file-recipients", "");
860 nf = run_editor(_coll_fp, (off_t)-1, c, 0, hp, NULL, SEND_MBOX, sigint);
862 if (nf != NULL) {
863 if (hp) {
864 rewind(nf);
865 makeheader(nf, hp);
866 } else {
867 fseek(nf, 0L, SEEK_END);
868 Fclose(_coll_fp);
869 _coll_fp = nf;
873 assign("add-file-recipients", saved);
874 safe_signal(SIGINT, sigint);
878 * Pipe the message through the command.
879 * Old message is on stdin of command;
880 * New message collected from stdout.
881 * Sh -c must return 0 to accept the new message.
883 static void
884 mespipe(char *cmd)
886 FILE *nf;
887 sighandler_type sigint = safe_signal(SIGINT, SIG_IGN);
888 char *tempEdit;
889 char const *sh;
891 if ((nf = Ftemp(&tempEdit, "Re", "w+", 0600, 1)) == NULL) {
892 perror(tr(66, "temporary mail edit file"));
893 goto out;
895 fflush(_coll_fp);
896 unlink(tempEdit);
897 Ftfree(&tempEdit);
899 * stdin = current message.
900 * stdout = new message.
902 if ((sh = value("SHELL")) == NULL)
903 sh = SHELL;
904 if (run_command(sh, 0, fileno(_coll_fp), fileno(nf), "-c", cmd, NULL)
905 < 0) {
906 Fclose(nf);
907 goto out;
909 if (fsize(nf) == 0) {
910 fprintf(stderr, tr(67, "No bytes from \"%s\" !?\n"), cmd);
911 Fclose(nf);
912 goto out;
915 * Take new files.
917 fseek(nf, 0L, SEEK_END);
918 Fclose(_coll_fp);
919 _coll_fp = nf;
920 out:
921 safe_signal(SIGINT, sigint);
925 * Interpolate the named messages into the current
926 * message, preceding each line with a tab.
927 * Return a count of the number of characters now in
928 * the message, or -1 if an error is encountered writing
929 * the message temporary. The flag argument is 'm' if we
930 * should shift over and 'f' if not.
932 static int
933 forward(char *ms, FILE *fp, int f)
935 int *msgvec;
936 struct ignoretab *ig;
937 char const *tabst;
938 enum sendaction action;
940 /*LINTED*/
941 msgvec = (int *)salloc((msgCount+1) * sizeof *msgvec);
942 if (msgvec == NULL)
943 return(0);
944 if (getmsglist(ms, msgvec, 0) < 0)
945 return(0);
946 if (*msgvec == 0) {
947 *msgvec = first(0, MMNORM);
948 if (*msgvec == 0) {
949 fputs(tr(68, "No appropriate messages\n"), stderr);
950 return 0;
952 msgvec[1] = 0;
954 if (f == 'f' || f == 'F')
955 tabst = NULL;
956 else if ((tabst = value("indentprefix")) == NULL)
957 tabst = "\t";
958 ig = upperchar(f) ? (struct ignoretab *)NULL : ignore;
959 action = upperchar(f) ? SEND_QUOTE_ALL : SEND_QUOTE;
960 printf(tr(69, "Interpolating:"));
961 for (; *msgvec != 0; msgvec++) {
962 struct message *mp = message + *msgvec - 1;
964 touch(mp);
965 printf(" %d", *msgvec);
966 if (sendmp(mp, fp, ig, tabst, action, NULL) < 0) {
967 perror(tr(70, "temporary mail file"));
968 return(-1);
971 printf("\n");
972 return(0);
976 * Print (continue) when continued after ^Z.
978 /*ARGSUSED*/
979 static void
980 collstop(int s)
982 sighandler_type old_action = safe_signal(s, SIG_DFL);
983 sigset_t nset;
985 sigemptyset(&nset);
986 sigaddset(&nset, s);
987 sigprocmask(SIG_UNBLOCK, &nset, (sigset_t *)NULL);
988 kill(0, s);
989 sigprocmask(SIG_BLOCK, &nset, (sigset_t *)NULL);
990 safe_signal(s, old_action);
991 if (_coll_jmp_p) {
992 _coll_jmp_p = 0;
993 _coll_hadintr = 0;
994 siglongjmp(_coll_jmp, 1);
999 * On interrupt, come here to save the partial message in ~/dead.letter.
1000 * Then jump out of the collection loop.
1002 /*ARGSUSED*/
1003 static void
1004 collint(int s)
1006 /* the control flow is subtle, because we can be called from ~q */
1007 if (_coll_hadintr == 0) {
1008 if (value("ignore") != NULL) {
1009 puts("@");
1010 fflush(stdout);
1011 clearerr(stdin);
1012 return;
1014 _coll_hadintr = 1;
1015 siglongjmp(_coll_jmp, 1);
1017 exit_status |= 04;
1018 if (value("save") != NULL && s != 0)
1019 savedeadletter(_coll_fp, 1);
1020 /* Aborting message, no need to fflush() .. */
1021 siglongjmp(_coll_abort, 1);
1024 /*ARGSUSED*/
1025 static void
1026 collhup(int s)
1028 (void)s;
1029 savedeadletter(_coll_fp, 1);
1031 * Let's pretend nobody else wants to clean up,
1032 * a true statement at this time.
1034 exit(1);
1037 FL void
1038 savedeadletter(FILE *fp, int fflush_rewind_first)
1040 char const *cp;
1041 int c;
1042 FILE *dbuf;
1043 ul_it lines, bytes;
1045 if (fflush_rewind_first) {
1046 (void)fflush(fp);
1047 rewind(fp);
1049 if (fsize(fp) == 0)
1050 goto jleave;
1052 cp = getdeadletter();
1053 c = umask(077);
1054 dbuf = Fopen(cp, "a");
1055 umask(c);
1056 if (dbuf == NULL)
1057 goto jleave;
1060 * There are problems with dup()ing of file-descriptors for child
1061 * processes. As long as those are not fixed in equal spirit to
1062 * (outof(): FIX and recode.., 2012-10-04), and to avoid reviving of
1063 * bugs like (If *record* is set, avoid writing dead content twice..,
1064 * 2012-09-14), we have to somehow accomplish that the FILE* fp
1065 * makes itself comfortable with the *real* offset of the underlaying
1066 * file descriptor. Unfortunately Standard I/O and POSIX don't
1067 * describe a way for that -- fflush();rewind(); won't do it.
1068 * This fseek(END),rewind() pair works around the problem on *BSD.
1070 (void)fseek(fp, 0, SEEK_END);
1071 rewind(fp);
1073 printf("\"%s\" ", cp);
1074 for (lines = bytes = 0; (c = getc(fp)) != EOF; ++bytes) {
1075 putc(c, dbuf);
1076 if (c == '\n')
1077 ++lines;
1079 printf("%lu/%lu\n", lines, bytes);
1081 Fclose(dbuf);
1082 rewind(fp);
1083 jleave: ;
1086 static int
1087 putesc(const char *s, FILE *stream)
1089 int n = 0;
1091 while (s[0]) {
1092 if (s[0] == '\\') {
1093 if (s[1] == 't') {
1094 if (putc('\t', stream) == EOF)
1095 return (-1);
1096 n++;
1097 s += 2;
1098 continue;
1100 if (s[1] == 'n') {
1101 if (putc('\n', stream) == EOF)
1102 return (-1);
1103 n++;
1104 s += 2;
1105 continue;
1108 if (putc(s[0], stream) == EOF)
1109 return (-1);
1110 n++;
1111 s++;
1113 if (putc('\n', stream) == EOF)
1114 return (-1);
1115 return (++n);