NEWS: update for v14.5.2
[s-mailx.git] / collect.c
blob6f78c19cd55180e7cf6f57e9ce8ab8dc383a0528
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 - 2014 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);
106 break;
108 while ((ap = ap->a_flink) != NULL);
110 inhook = 0;
111 execute(linebuf, TRU1, linesize);
113 if (mnbuf != NULL) {
114 if (strncmp(mnbuf, mailname, mnlen))
115 fputs(tr(237, "Mailbox changed: it seems existing "
116 "rfc822 attachments became invalid!\n"),
117 stderr);
118 ac_free(mnbuf);
122 static int
123 _include_file(FILE *fbuf, char const *name, int *linecount, int *charcount,
124 bool_t doecho)
126 int ret = -1;
127 char *linebuf = NULL;
128 size_t linesize = 0, linelen, cnt;
130 if (fbuf == NULL) {
131 if ((fbuf = Fopen(name, "r")) == NULL) {
132 perror(name);
133 goto jleave;
135 } else
136 fflush_rewind(fbuf);
138 *linecount = *charcount = 0;
139 cnt = fsize(fbuf);
140 while (fgetline(&linebuf, &linesize, &cnt, &linelen, fbuf, 0)
141 != NULL) {
142 if (fwrite(linebuf, sizeof *linebuf, linelen, _coll_fp)
143 != linelen)
144 goto jleave;
145 if ((options & OPT_INTERACTIVE) && doecho)
146 fwrite(linebuf, sizeof *linebuf, linelen, stdout);
147 ++(*linecount);
148 (*charcount) += linelen;
150 if (fflush(_coll_fp))
151 goto jleave;
153 ret = 0;
154 jleave:
155 if (linebuf != NULL)
156 free(linebuf);
157 if (fbuf != NULL)
158 Fclose(fbuf);
159 return (ret);
162 /*ARGSUSED*/
163 static void
164 _collect_onpipe(int signo)
166 UNUSED(signo);
167 siglongjmp(_coll_pipejmp, 1);
171 * Execute cmd and insert its standard output into fp.
173 static void
174 insertcommand(FILE *fp, char const *cmd)
176 FILE *ibuf = NULL;
177 char const *cp;
178 int c;
180 cp = ok_vlook(SHELL);
181 if (cp == NULL)
182 cp = XSHELL;
183 if ((ibuf = Popen(cmd, "r", cp, 0)) != NULL) {
184 while ((c = getc(ibuf)) != EOF) /* XXX bytewise, yuck! */
185 putc(c, fp);
186 Pclose(ibuf, TRU1);
187 } else
188 perror(cmd);
192 * ~p command.
194 static void
195 print_collf(FILE *cf, struct header *hp)
197 char *lbuf = NULL;
198 FILE *volatile obuf = stdout;
199 struct attachment *ap;
200 char const *cp;
201 enum gfield gf;
202 size_t linecnt, maxlines, linesize = 0, linelen, cnt, cnt2;
204 fflush(cf);
205 rewind(cf);
206 cnt = cnt2 = fsize(cf);
208 if (IS_TTY_SESSION() && (cp = ok_vlook(crt)) != NULL) {
209 for (linecnt = 0;
210 fgetline(&lbuf, &linesize, &cnt2, NULL, cf, 0);
211 linecnt++);
212 rewind(cf);
213 maxlines = (*cp == '\0' ? screensize() : atoi(cp));
214 maxlines -= 4;
215 if (hp->h_to)
216 maxlines--;
217 if (hp->h_subject)
218 maxlines--;
219 if (hp->h_cc)
220 maxlines--;
221 if (hp->h_bcc)
222 maxlines--;
223 if (hp->h_attach)
224 maxlines--;
225 maxlines -= myaddrs(hp) != NULL || hp->h_from != NULL;
226 maxlines -= ok_vlook(ORGANIZATION) != NULL ||
227 hp->h_organization != NULL;
228 maxlines -= ok_vlook(replyto) != NULL || hp->h_replyto != NULL;
229 maxlines -= ok_vlook(sender) != NULL || hp->h_sender != NULL;
230 if ((long)maxlines < 0 || linecnt > maxlines) {
231 cp = get_pager();
232 if (sigsetjmp(_coll_pipejmp, 1))
233 goto endpipe;
234 obuf = Popen(cp, "w", NULL, 1);
235 if (obuf == NULL) {
236 perror(cp);
237 obuf = stdout;
238 } else
239 safe_signal(SIGPIPE, &_collect_onpipe);
243 fprintf(obuf, tr(62, "-------\nMessage contains:\n"));
244 gf = GIDENT|GTO|GSUBJECT|GCC|GBCC|GNL|GFILES;
245 if (ok_blook(fullnames))
246 gf |= GCOMMA;
247 puthead(hp, obuf, gf, SEND_TODISP, CONV_NONE, NULL, NULL);
248 while (fgetline(&lbuf, &linesize, &cnt, &linelen, cf, 1))
249 prout(lbuf, linelen, obuf);
250 if (hp->h_attach != NULL) {
251 fputs(tr(63, "-------\nAttachments:\n"), obuf);
252 for (ap = hp->h_attach; ap != NULL; ap = ap->a_flink) {
253 if (ap->a_msgno)
254 fprintf(obuf, " - message %u\n", ap->a_msgno);
255 else {
256 /* TODO after MIME/send layer rewrite we *know*
257 * TODO the details of the attachment here,
258 * TODO so adjust this again, then */
259 char const *cs, *csi = "-> ";
261 if ((cs = ap->a_charset) == NULL &&
262 (csi = "<- ",
263 cs = ap->a_input_charset)
264 == NULL)
265 cs = charset_get_lc();
266 if ((cp = ap->a_content_type) == NULL)
267 cp = "?";
268 else if (ascncasecmp(cp, "text/", 5) != 0)
269 csi = "";
271 fprintf(obuf, " - [%s, %s%s] %s\n",
272 cp, csi, cs, ap->a_name);
276 endpipe:
277 if (obuf != stdout) {
278 safe_signal(SIGPIPE, SIG_IGN);
279 Pclose(obuf, TRU1);
280 safe_signal(SIGPIPE, dflpipe);
282 if (lbuf)
283 free(lbuf);
286 FL FILE *
287 collect(struct header *hp, int printheaders, struct message *mp,
288 char *quotefile, int doprefix)
290 FILE *fbuf;
291 struct ignoretab *quoteig;
292 int lc, cc, eofcount, c, t;
293 int volatile escape, getfields;
294 char *linebuf = NULL, *quote = NULL, *tempMail = NULL;
295 char const *cp;
296 size_t linesize = 0;
297 long cnt;
298 enum sendaction action;
299 sigset_t oset, nset;
300 sighandler_type savedtop;
302 _coll_fp = NULL;
304 * Start catching signals from here, but we're still die on interrupts
305 * until we're in the main loop.
307 sigemptyset(&nset);
308 sigaddset(&nset, SIGINT);
309 sigaddset(&nset, SIGHUP);
310 sigprocmask(SIG_BLOCK, &nset, &oset);
311 handlerpush(collint);
312 if ((_coll_saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
313 safe_signal(SIGINT, collint);
314 if ((_coll_savehup = safe_signal(SIGHUP, SIG_IGN)) != SIG_IGN)
315 safe_signal(SIGHUP, collhup);
316 /* TODO We do a lot of redundant signal handling, especially
317 * TODO with the command line editor(s); try to merge this */
318 _coll_savetstp = safe_signal(SIGTSTP, collstop);
319 _coll_savettou = safe_signal(SIGTTOU, collstop);
320 _coll_savettin = safe_signal(SIGTTIN, collstop);
321 if (sigsetjmp(_coll_abort, 1))
322 goto jerr;
323 if (sigsetjmp(_coll_jmp, 1))
324 goto jerr;
325 sigprocmask(SIG_SETMASK, &oset, (sigset_t *)NULL);
327 noreset++;
328 if ((_coll_fp = Ftemp(&tempMail, "Rs", "w+", 0600, 1)) == NULL) {
329 perror(tr(51, "temporary mail file"));
330 goto jerr;
332 unlink(tempMail);
333 Ftfree(&tempMail);
335 if ((cp = ok_vlook(NAIL_HEAD)) != NULL && putesc(cp, _coll_fp) < 0)
336 goto jerr;
339 * If we are going to prompt for a subject,
340 * refrain from printing a newline after
341 * the headers (since some people mind).
343 getfields = 0;
344 if (! (options & OPT_t_FLAG)) {
345 t = GTO|GSUBJECT|GCC|GNL;
346 if (ok_blook(fullnames))
347 t |= GCOMMA;
348 if (hp->h_subject == NULL && (options & OPT_INTERACTIVE) &&
349 (ok_blook(ask) || ok_blook(asksub)))
350 t &= ~GNL, getfields |= GSUBJECT;
351 if (hp->h_to == NULL && (options & OPT_INTERACTIVE))
352 t &= ~GNL, getfields |= GTO;
353 if (!ok_blook(bsdcompat) && !ok_blook(askatend) &&
354 (options & OPT_INTERACTIVE)) {
355 if (hp->h_bcc == NULL && ok_blook(askbcc))
356 t &= ~GNL, getfields |= GBCC;
357 if (hp->h_cc == NULL && ok_blook(askcc))
358 t &= ~GNL, getfields |= GCC;
360 if (printheaders) {
361 (void)puthead(hp, stdout, t, SEND_TODISP, CONV_NONE,
362 NULL, NULL);
363 (void)fflush(stdout);
368 * Quote an original message
370 if (mp != NULL && (doprefix || (quote = ok_vlook(quote)) != NULL)) {
371 quoteig = allignore;
372 action = SEND_QUOTE;
373 if (doprefix) {
374 quoteig = fwdignore;
375 if ((cp = ok_vlook(fwdheading)) == NULL)
376 cp = "-------- Original Message --------";
377 if (*cp && fprintf(_coll_fp, "%s\n", cp) < 0)
378 goto jerr;
379 } else if (strcmp(quote, "noheading") == 0) {
380 /*EMPTY*/;
381 } else if (strcmp(quote, "headers") == 0) {
382 quoteig = ignore;
383 } else if (strcmp(quote, "allheaders") == 0) {
384 quoteig = NULL;
385 action = SEND_QUOTE_ALL;
386 } else {
387 cp = hfield1("from", mp);
388 if (cp != NULL && (cnt = (long)strlen(cp)) > 0) {
389 if (xmime_write(cp, cnt, _coll_fp,
390 CONV_FROMHDR, TD_NONE,
391 NULL) < 0)
392 goto jerr;
393 if (fprintf(_coll_fp,
394 tr(52, " wrote:\n\n")) < 0)
395 goto jerr;
398 if (fflush(_coll_fp))
399 goto jerr;
400 cp = ok_vlook(indentprefix);
401 if (cp != NULL && *cp == '\0')
402 cp = "\t";
403 if (sendmp(mp, _coll_fp, quoteig, (doprefix ? NULL : cp),
404 action, NULL) < 0)
405 goto jerr;
408 /* Print what we have sofar also on the terminal */
409 rewind(_coll_fp);
410 while ((c = getc(_coll_fp)) != EOF) /* XXX bytewise, yuck! */
411 (void)putc(c, stdout);
412 if (fseek(_coll_fp, 0, SEEK_END))
413 goto jerr;
415 escape = ((cp = ok_vlook(escape)) != NULL) ? *cp : ESCAPE;
416 eofcount = 0;
417 _coll_hadintr = 0;
419 if (!sigsetjmp(_coll_jmp, 1)) {
420 if (getfields)
421 grab_headers(hp, getfields, 1);
422 if (quotefile != NULL) {
423 if (_include_file(NULL, quotefile, &lc, &cc, TRU1) != 0)
424 goto jerr;
426 if ((options & OPT_INTERACTIVE) && ok_blook(editalong)) {
427 rewind(_coll_fp);
428 mesedit('e', hp);
429 goto jcont;
431 } else {
433 * Come here for printing the after-signal message.
434 * Duplicate messages won't be printed because
435 * the write is aborted if we get a SIGTTOU.
437 jcont:
438 if (_coll_hadintr) {
439 (void)fprintf(stderr, tr(53,
440 "\n(Interrupt -- one more to kill letter)\n"));
441 } else {
442 printf(tr(54, "(continue)\n"));
443 (void)fflush(stdout);
448 * No tilde escapes, interrupts not expected. Simply copy STDIN
450 if (! (options & (OPT_INTERACTIVE | OPT_t_FLAG|OPT_TILDE_FLAG))) {
451 linebuf = srealloc(linebuf, linesize = LINESIZE);
452 while ((cnt = fread(linebuf, sizeof *linebuf,
453 linesize, stdin)) > 0) {
454 if ((size_t)cnt != fwrite(linebuf, sizeof *linebuf,
455 cnt, _coll_fp))
456 goto jerr;
458 if (fflush(_coll_fp))
459 goto jerr;
460 goto jout;
464 * The interactive collect loop
466 for (;;) {
467 _coll_jmp_p = 1;
468 cnt = readline_input("", FAL0, &linebuf, &linesize, NULL);
469 _coll_jmp_p = 0;
471 if (cnt < 0) {
472 if ((options & OPT_INTERACTIVE) &&
473 ok_blook(ignoreeof) && ++eofcount < 25) {
474 printf(tr(55,
475 "Use \".\" to terminate letter\n"));
476 continue;
478 break;
480 if ((options & OPT_t_FLAG) && cnt == 0) {
481 rewind(_coll_fp);
482 if (makeheader(_coll_fp, hp) != OKAY)
483 goto jerr;
484 rewind(_coll_fp);
485 options &= ~OPT_t_FLAG;
486 continue;
489 eofcount = 0;
490 _coll_hadintr = 0;
491 if (linebuf[0] == '.' && linebuf[1] == '\0' &&
492 (options & (OPT_INTERACTIVE|OPT_TILDE_FLAG)) &&
493 (ok_blook(dot) || ok_blook(ignoreeof)))
494 break;
495 if (cnt == 0 || linebuf[0] != escape || ! (options &
496 (OPT_INTERACTIVE | OPT_TILDE_FLAG))) {
497 /* TODO calls putline(), which *always* appends LF;
498 * TODO thus, STDIN with -t will ALWAYS end with LF,
499 * TODO even if no trailing LF and QP CTE */
500 if (putline(_coll_fp, linebuf, cnt) < 0)
501 goto jerr;
502 continue;
505 c = linebuf[1];
506 switch (c) {
507 default:
509 * On double escape, just send the single one.
510 * Otherwise, it's an error.
512 if (c == escape) {
513 if (putline(_coll_fp, &linebuf[1], cnt - 1) < 0)
514 goto jerr;
515 else
516 break;
518 fputs(tr(56, "Unknown tilde escape.\n"), stderr);
519 break;
520 case '!':
521 /* Shell escape, send the balance of line to sh -c */
522 shell(&linebuf[2]);
523 break;
524 case ':':
525 /* FALLTHRU */
526 case '_':
527 /* Escape to command mode, but be nice! */
528 _execute_command(hp, linebuf + 2, cnt - 2);
529 goto jcont;
530 case '.':
531 /* Simulate end of file on input */
532 goto jout;
533 case 'x':
534 /* Same as 'q', but no dead.letter saving */
535 /* FALLTHRU */
536 case 'q':
537 /* Force a quit, act like an interrupt had happened */
538 ++_coll_hadintr;
539 collint((c == 'x') ? 0 : SIGINT);
540 exit(1);
541 /*NOTREACHED*/
542 case 'h':
543 /* Grab a bunch of headers */
545 grab_headers(hp, GTO|GSUBJECT|GCC|GBCC,
546 (ok_blook(bsdcompat) &&
547 ok_blook(bsdorder)));
548 while (hp->h_to == NULL);
549 goto jcont;
550 case 'H':
551 /* Grab extra headers */
553 grab_headers(hp, GEXTRA, 0);
554 while (check_from_and_sender(hp->h_from, hp->h_sender));
555 goto jcont;
556 case 't':
557 /* Add to the To list */
558 while ((hp->h_to = cat(hp->h_to, checkaddrs(
559 lextract(&linebuf[2], GTO|GFULL))))
560 == NULL)
562 break;
563 case 's':
564 /* Set the Subject list */
565 cp = &linebuf[2];
566 while (whitechar(*cp))
567 ++cp;
568 hp->h_subject = savestr(cp);
569 break;
570 #ifdef HAVE_DEBUG
571 case 'S':
572 c_sstats(NULL);
573 break;
574 #endif
575 case '@':
576 /* Edit the attachment list */
577 if (linebuf[2] != '\0')
578 hp->h_attach = append_attachments(hp->h_attach,
579 &linebuf[2]);
580 else
581 hp->h_attach = edit_attachments(hp->h_attach);
582 break;
583 case 'c':
584 /* Add to the CC list */
585 hp->h_cc = cat(hp->h_cc, checkaddrs(
586 lextract(&linebuf[2], GCC|GFULL)));
587 break;
588 case 'b':
589 /* Add stuff to blind carbon copies list */
590 hp->h_bcc = cat(hp->h_bcc, checkaddrs(
591 lextract(&linebuf[2], GBCC|GFULL)));
592 break;
593 case 'd':
594 strncpy(linebuf + 2, getdeadletter(), linesize - 2);
595 linebuf[linesize-1]='\0';
596 /*FALLTHRU*/
597 case 'r':
598 case '<':
600 * Invoke a file:
601 * Search for the file name,
602 * then open it and copy the contents to _coll_fp.
604 cp = &linebuf[2];
605 while (whitechar(*cp))
606 cp++;
607 if (*cp == '\0') {
608 fputs(tr(57, "Interpolate what file?\n"),
609 stderr);
610 break;
612 if (*cp == '!') {
613 insertcommand(_coll_fp, cp + 1);
614 break;
616 if ((cp = file_expand(cp)) == NULL)
617 break;
618 if (is_dir(cp)) {
619 fprintf(stderr, tr(58, "%s: Directory\n"), cp);
620 break;
622 if ((fbuf = Fopen(cp, "r")) == NULL) {
623 perror(cp);
624 break;
626 printf(tr(59, "\"%s\" "), cp);
627 fflush(stdout);
628 if (_include_file(fbuf, cp, &lc, &cc, FAL0) != 0)
629 goto jerr;
630 printf(tr(60, "%d/%d\n"), lc, cc);
631 break;
632 case 'i':
633 /* Insert a variable into the file */
634 cp = &linebuf[2];
635 while (whitechar(*cp))
636 ++cp;
637 if ((cp = vok_vlook(cp)) == NULL || *cp == '\0')
638 break;
639 if (putesc(cp, _coll_fp) < 0)
640 goto jerr;
641 if ((options & OPT_INTERACTIVE) &&
642 putesc(cp, stdout) < 0)
643 goto jerr;
644 break;
645 case 'a':
646 case 'A':
647 /* Insert the contents of a signature variable */
648 cp = (c == 'a') ? ok_vlook(sign) : ok_vlook(Sign);
649 if (cp != NULL && *cp != '\0') {
650 if (putesc(cp, _coll_fp) < 0)
651 goto jerr;
652 if ((options & OPT_INTERACTIVE) &&
653 putesc(cp, stdout) < 0)
654 goto jerr;
656 break;
657 case 'w':
658 /* Write the message on a file */
659 cp = &linebuf[2];
660 while (blankchar(*cp))
661 ++cp;
662 if (*cp == '\0' || (cp = file_expand(cp)) == NULL) {
663 fputs(tr(61, "Write what file!?\n"), stderr);
664 break;
666 rewind(_coll_fp);
667 if (exwrite(cp, _coll_fp, 1) < 0)
668 goto jerr;
669 break;
670 case 'm':
671 case 'M':
672 case 'f':
673 case 'F':
674 case 'u':
675 case 'U':
677 * Interpolate the named messages, if we
678 * are in receiving mail mode. Does the
679 * standard list processing garbage.
680 * If ~f is given, we don't shift over.
682 if (forward(linebuf + 2, _coll_fp, c) < 0)
683 goto jerr;
684 goto jcont;
685 case 'p':
687 * Print out the current state of the
688 * message without altering anything.
690 print_collf(_coll_fp, hp);
691 goto jcont;
692 case '|':
694 * Pipe message through command.
695 * Collect output as new message.
697 rewind(_coll_fp);
698 mespipe(&linebuf[2]);
699 goto jcont;
700 case 'v':
701 case 'e':
703 * Edit the current message.
704 * 'e' means to use EDITOR
705 * 'v' means to use VISUAL
707 rewind(_coll_fp);
708 mesedit(c, ok_blook(editheaders) ? hp : NULL);
709 goto jcont;
710 case '?':
712 * Last the lengthy help string.
713 * (Very ugly, but take care for compiler supported
714 * string lengths :()
716 (void)puts(tr(300,
717 "-------------------- ~ ESCAPES ----------------------------\n"
718 "~~ Quote a single tilde\n"
719 "~@ [file ...] Edit attachment list\n"
720 "~b users Add users to \"blind\" cc list\n"
721 "~c users Add users to cc list\n"
722 "~d Read in dead.letter\n"
723 "~e Edit the message buffer\n"
724 "~f messages Read in messages without indenting lines\n"
725 "~F messages Same as ~f, but keep all header lines\n"
726 "~h Prompt for to list, subject, cc, and \"blind\" cc list\n"));
727 (void)puts(tr(301,
728 "~r file Read a file into the message buffer\n"
729 "~p Print the message buffer\n"
730 "~q Abort message composition and save text to dead.letter\n"
731 "~m messages Read in messages with each line indented\n"
732 "~M messages Same as ~m, but keep all header lines\n"
733 "~s subject Set subject\n"
734 "~t users Add users to to list\n"
735 "~u messages Same as ~f, but without any headers\n"
736 "~U messages Same as ~m, but without any headers\n"));
737 (void)puts(tr(302,
738 "~v Invoke display editor on message\n"
739 "~w file Write message onto file\n"
740 "~x Abort message composition and discard text written so far\n"
741 "~!command Invoke the shell\n"
742 "~:command Execute a regular command\n"
743 "-----------------------------------------------------------\n"));
744 break;
748 jout:
749 if (_coll_fp != NULL) {
750 if ((cp = ok_vlook(NAIL_TAIL)) != NULL) {
751 if (putesc(cp, _coll_fp) < 0)
752 goto jerr;
753 if ((options & OPT_INTERACTIVE) &&
754 putesc(cp, stdout) < 0)
755 goto jerr;
757 rewind(_coll_fp);
759 if (linebuf != NULL)
760 free(linebuf);
761 handlerpop();
762 noreset--;
763 sigemptyset(&nset);
764 sigaddset(&nset, SIGINT);
765 sigaddset(&nset, SIGHUP);
766 sigprocmask(SIG_BLOCK, &nset, (sigset_t*)NULL);
767 safe_signal(SIGINT, _coll_saveint);
768 safe_signal(SIGHUP, _coll_savehup);
769 safe_signal(SIGTSTP, _coll_savetstp);
770 safe_signal(SIGTTOU, _coll_savettou);
771 safe_signal(SIGTTIN, _coll_savettin);
772 sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
773 return _coll_fp;
775 jerr:
776 if (tempMail != NULL) {
777 rm(tempMail);
778 Ftfree(&tempMail);
780 if (_coll_fp != NULL) {
781 Fclose(_coll_fp);
782 _coll_fp = NULL;
784 goto jout;
788 * Write a file, ex-like if f set.
790 static int
791 exwrite(char const *name, FILE *fp, int f)
793 FILE *of;
794 int c;
795 long cc;
796 int lc;
798 if (f) {
799 printf("\"%s\" ", name);
800 fflush(stdout);
802 if ((of = Fopen(name, "a")) == NULL) {
803 perror(NULL);
804 return(-1);
806 lc = 0;
807 cc = 0;
808 while ((c = getc(fp)) != EOF) {
809 cc++;
810 if (c == '\n')
811 lc++;
812 putc(c, of);
813 if (ferror(of)) {
814 perror(name);
815 Fclose(of);
816 return(-1);
819 Fclose(of);
820 printf(tr(65, "%d/%ld\n"), lc, cc);
821 fflush(stdout);
822 return(0);
825 static enum okay
826 makeheader(FILE *fp, struct header *hp)
828 char *tempEdit;
829 FILE *nf;
830 int c;
832 if ((nf = Ftemp(&tempEdit, "Re", "w+", 0600, 1)) == NULL) {
833 perror(tr(66, "temporary mail edit file"));
834 return STOP;
836 unlink(tempEdit);
837 Ftfree(&tempEdit);
839 extract_header(fp, hp);
840 while ((c = getc(fp)) != EOF) /* XXX bytewise, yuck! */
841 putc(c, nf);
842 if (fp != _coll_fp)
843 Fclose(_coll_fp);
844 Fclose(fp);
845 _coll_fp = nf;
846 if (check_from_and_sender(hp->h_from, hp->h_sender))
847 return STOP;
848 return OKAY;
852 * Edit the message being collected on fp.
853 * On return, make the edit file the new temp file.
855 static void
856 mesedit(int c, struct header *hp)
858 sighandler_type sigint = safe_signal(SIGINT, SIG_IGN);
859 bool_t saved = ok_blook(add_file_recipients);
860 FILE *nf;
862 ok_bset(add_file_recipients, TRU1);
863 nf = run_editor(_coll_fp, (off_t)-1, c, 0, hp, NULL, SEND_MBOX, sigint);
865 if (nf != NULL) {
866 if (hp) {
867 rewind(nf);
868 makeheader(nf, hp);
869 } else {
870 fseek(nf, 0L, SEEK_END);
871 Fclose(_coll_fp);
872 _coll_fp = nf;
876 ok_bset(add_file_recipients, saved);
877 safe_signal(SIGINT, sigint);
881 * Pipe the message through the command.
882 * Old message is on stdin of command;
883 * New message collected from stdout.
884 * Sh -c must return 0 to accept the new message.
886 static void
887 mespipe(char *cmd)
889 FILE *nf;
890 sighandler_type sigint = safe_signal(SIGINT, SIG_IGN);
891 char *tempEdit;
892 char const *sh;
894 if ((nf = Ftemp(&tempEdit, "Re", "w+", 0600, 1)) == NULL) {
895 perror(tr(66, "temporary mail edit file"));
896 goto out;
898 fflush(_coll_fp);
899 unlink(tempEdit);
900 Ftfree(&tempEdit);
902 * stdin = current message.
903 * stdout = new message.
905 if ((sh = ok_vlook(SHELL)) == NULL)
906 sh = XSHELL;
907 if (run_command(sh, 0, fileno(_coll_fp), fileno(nf), "-c", cmd, NULL)
908 < 0) {
909 Fclose(nf);
910 goto out;
912 if (fsize(nf) == 0) {
913 fprintf(stderr, tr(67, "No bytes from \"%s\" !?\n"), cmd);
914 Fclose(nf);
915 goto out;
918 * Take new files.
920 fseek(nf, 0L, SEEK_END);
921 Fclose(_coll_fp);
922 _coll_fp = nf;
923 out:
924 safe_signal(SIGINT, sigint);
928 * Interpolate the named messages into the current
929 * message, preceding each line with a tab.
930 * Return a count of the number of characters now in
931 * the message, or -1 if an error is encountered writing
932 * the message temporary. The flag argument is 'm' if we
933 * should shift over and 'f' if not.
935 static int
936 forward(char *ms, FILE *fp, int f)
938 int *msgvec;
939 struct ignoretab *ig;
940 char const *tabst;
941 enum sendaction action;
943 /*LINTED*/
944 msgvec = (int *)salloc((msgCount+1) * sizeof *msgvec);
945 if (msgvec == NULL)
946 return(0);
947 if (getmsglist(ms, msgvec, 0) < 0)
948 return(0);
949 if (*msgvec == 0) {
950 *msgvec = first(0, MMNORM);
951 if (*msgvec == 0) {
952 fputs(tr(68, "No appropriate messages\n"), stderr);
953 return 0;
955 msgvec[1] = 0;
958 if (f == 'f' || f == 'F' || f == 'u')
959 tabst = NULL;
960 else if ((tabst = ok_vlook(indentprefix)) == NULL)
961 tabst = "\t";
962 if (f == 'u' || f == 'U')
963 ig = allignore;
964 else
965 ig = upperchar(f) ? (struct ignoretab*)NULL : ignore;
966 action = (upperchar(f) && f != 'U') ? SEND_QUOTE_ALL : SEND_QUOTE;
968 printf(tr(69, "Interpolating:"));
969 for (; *msgvec != 0; msgvec++) {
970 struct message *mp = message + *msgvec - 1;
972 touch(mp);
973 printf(" %d", *msgvec);
974 if (sendmp(mp, fp, ig, tabst, action, NULL) < 0) {
975 perror(tr(70, "temporary mail file"));
976 return(-1);
979 printf("\n");
980 return(0);
984 * Print (continue) when continued after ^Z.
986 /*ARGSUSED*/
987 static void
988 collstop(int s)
990 sighandler_type old_action = safe_signal(s, SIG_DFL);
991 sigset_t nset;
993 sigemptyset(&nset);
994 sigaddset(&nset, s);
995 sigprocmask(SIG_UNBLOCK, &nset, (sigset_t *)NULL);
996 kill(0, s);
997 sigprocmask(SIG_BLOCK, &nset, (sigset_t *)NULL);
998 safe_signal(s, old_action);
999 if (_coll_jmp_p) {
1000 _coll_jmp_p = 0;
1001 _coll_hadintr = 0;
1002 siglongjmp(_coll_jmp, 1);
1007 * On interrupt, come here to save the partial message in ~/dead.letter.
1008 * Then jump out of the collection loop.
1010 /*ARGSUSED*/
1011 static void
1012 collint(int s)
1014 /* the control flow is subtle, because we can be called from ~q */
1015 if (_coll_hadintr == 0) {
1016 if (ok_blook(ignore)) {
1017 puts("@");
1018 fflush(stdout);
1019 clearerr(stdin);
1020 return;
1022 _coll_hadintr = 1;
1023 siglongjmp(_coll_jmp, 1);
1025 exit_status |= 04;
1026 if (ok_blook(save) && s != 0)
1027 savedeadletter(_coll_fp, 1);
1028 /* Aborting message, no need to fflush() .. */
1029 siglongjmp(_coll_abort, 1);
1032 /*ARGSUSED*/
1033 static void
1034 collhup(int s)
1036 (void)s;
1037 savedeadletter(_coll_fp, 1);
1039 * Let's pretend nobody else wants to clean up,
1040 * a true statement at this time.
1042 exit(1);
1045 FL void
1046 savedeadletter(FILE *fp, int fflush_rewind_first)
1048 char const *cp;
1049 int c;
1050 FILE *dbuf;
1051 ul_it lines, bytes;
1053 if (fflush_rewind_first) {
1054 (void)fflush(fp);
1055 rewind(fp);
1057 if (fsize(fp) == 0)
1058 goto jleave;
1060 cp = getdeadletter();
1061 c = umask(077);
1062 dbuf = Fopen(cp, "a");
1063 umask(c);
1064 if (dbuf == NULL)
1065 goto jleave;
1068 * There are problems with dup()ing of file-descriptors for child
1069 * processes. As long as those are not fixed in equal spirit to
1070 * (outof(): FIX and recode.., 2012-10-04), and to avoid reviving of
1071 * bugs like (If *record* is set, avoid writing dead content twice..,
1072 * 2012-09-14), we have to somehow accomplish that the FILE* fp
1073 * makes itself comfortable with the *real* offset of the underlaying
1074 * file descriptor. Unfortunately Standard I/O and POSIX don't
1075 * describe a way for that -- fflush();rewind(); won't do it.
1076 * This fseek(END),rewind() pair works around the problem on *BSD.
1078 (void)fseek(fp, 0, SEEK_END);
1079 rewind(fp);
1081 printf("\"%s\" ", cp);
1082 for (lines = bytes = 0; (c = getc(fp)) != EOF; ++bytes) {
1083 putc(c, dbuf);
1084 if (c == '\n')
1085 ++lines;
1087 printf("%lu/%lu\n", lines, bytes);
1089 Fclose(dbuf);
1090 rewind(fp);
1091 jleave: ;
1094 static int
1095 putesc(const char *s, FILE *stream)
1097 int n = 0;
1099 while (s[0]) {
1100 if (s[0] == '\\') {
1101 if (s[1] == 't') {
1102 if (putc('\t', stream) == EOF)
1103 return (-1);
1104 n++;
1105 s += 2;
1106 continue;
1108 if (s[1] == 'n') {
1109 if (putc('\n', stream) == EOF)
1110 return (-1);
1111 n++;
1112 s += 2;
1113 continue;
1116 if (putc(s[0], stream) == EOF)
1117 return (-1);
1118 n++;
1119 s++;
1121 if (putc('\n', stream) == EOF)
1122 return (-1);
1123 return (++n);