Fix `|' command (Gavin Troy)..
[s-mailx.git] / collect.c
blob58c2ff27071a070be7a49815a3dc9c9b640db1e9
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(LNED_NONE, "", &linebuf, &linesize);
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 #ifdef HAVE_DEBUG
521 case 'C':
522 /* Dump core */
523 core(NULL);
524 break;
525 #endif
526 case '!':
527 /* Shell escape, send the balance of line to sh -c */
528 shell(&linebuf[2]);
529 break;
530 case ':':
531 /* FALLTHRU */
532 case '_':
533 /* Escape to command mode, but be nice! */
534 _execute_command(hp, linebuf + 2, cnt - 2);
535 goto jcont;
536 case '.':
537 /* Simulate end of file on input */
538 goto jout;
539 case 'x':
540 /* Same as 'q', but no dead.letter saving */
541 /* FALLTHRU */
542 case 'q':
543 /* Force a quit, act like an interrupt had happened */
544 ++_coll_hadintr;
545 collint((c == 'x') ? 0 : SIGINT);
546 exit(1);
547 /*NOTREACHED*/
548 case 'h':
549 /* Grab a bunch of headers */
551 grab_headers(hp, GTO|GSUBJECT|GCC|GBCC,
552 (ok_blook(bsdcompat) &&
553 ok_blook(bsdorder)));
554 while (hp->h_to == NULL);
555 goto jcont;
556 case 'H':
557 /* Grab extra headers */
559 grab_headers(hp, GEXTRA, 0);
560 while (check_from_and_sender(hp->h_from, hp->h_sender));
561 goto jcont;
562 case 't':
563 /* Add to the To list */
564 while ((hp->h_to = cat(hp->h_to, checkaddrs(
565 lextract(&linebuf[2], GTO|GFULL))))
566 == NULL)
568 break;
569 case 's':
570 /* Set the Subject list */
571 cp = &linebuf[2];
572 while (whitechar(*cp))
573 ++cp;
574 hp->h_subject = savestr(cp);
575 break;
576 #ifdef HAVE_DEBUG
577 case 'S':
578 c_sstats(NULL);
579 break;
580 #endif
581 case '@':
582 /* Edit the attachment list */
583 if (linebuf[2] != '\0')
584 hp->h_attach = append_attachments(hp->h_attach,
585 &linebuf[2]);
586 else
587 hp->h_attach = edit_attachments(hp->h_attach);
588 break;
589 case 'c':
590 /* Add to the CC list */
591 hp->h_cc = cat(hp->h_cc, checkaddrs(
592 lextract(&linebuf[2], GCC|GFULL)));
593 break;
594 case 'b':
595 /* Add stuff to blind carbon copies list */
596 hp->h_bcc = cat(hp->h_bcc, checkaddrs(
597 lextract(&linebuf[2], GBCC|GFULL)));
598 break;
599 case 'd':
600 strncpy(linebuf + 2, getdeadletter(), linesize - 2);
601 linebuf[linesize-1]='\0';
602 /*FALLTHRU*/
603 case 'r':
604 case '<':
606 * Invoke a file:
607 * Search for the file name,
608 * then open it and copy the contents to _coll_fp.
610 cp = &linebuf[2];
611 while (whitechar(*cp))
612 cp++;
613 if (*cp == '\0') {
614 fputs(tr(57, "Interpolate what file?\n"),
615 stderr);
616 break;
618 if (*cp == '!') {
619 insertcommand(_coll_fp, cp + 1);
620 break;
622 if ((cp = file_expand(cp)) == NULL)
623 break;
624 if (is_dir(cp)) {
625 fprintf(stderr, tr(58, "%s: Directory\n"), cp);
626 break;
628 if ((fbuf = Fopen(cp, "r")) == NULL) {
629 perror(cp);
630 break;
632 printf(tr(59, "\"%s\" "), cp);
633 fflush(stdout);
634 if (_include_file(fbuf, cp, &lc, &cc, FAL0) != 0)
635 goto jerr;
636 printf(tr(60, "%d/%d\n"), lc, cc);
637 break;
638 case 'i':
639 /* Insert a variable into the file */
640 cp = &linebuf[2];
641 while (whitechar(*cp))
642 ++cp;
643 if ((cp = vok_vlook(cp)) == NULL || *cp == '\0')
644 break;
645 if (putesc(cp, _coll_fp) < 0)
646 goto jerr;
647 if ((options & OPT_INTERACTIVE) &&
648 putesc(cp, stdout) < 0)
649 goto jerr;
650 break;
651 case 'a':
652 case 'A':
653 /* Insert the contents of a signature variable */
654 cp = (c == 'a') ? ok_vlook(sign) : ok_vlook(Sign);
655 if (cp != NULL && *cp != '\0') {
656 if (putesc(cp, _coll_fp) < 0)
657 goto jerr;
658 if ((options & OPT_INTERACTIVE) &&
659 putesc(cp, stdout) < 0)
660 goto jerr;
662 break;
663 case 'w':
664 /* Write the message on a file */
665 cp = &linebuf[2];
666 while (blankchar(*cp))
667 ++cp;
668 if (*cp == '\0' || (cp = file_expand(cp)) == NULL) {
669 fputs(tr(61, "Write what file!?\n"), stderr);
670 break;
672 rewind(_coll_fp);
673 if (exwrite(cp, _coll_fp, 1) < 0)
674 goto jerr;
675 break;
676 case 'm':
677 case 'M':
678 case 'f':
679 case 'F':
680 case 'u':
681 case 'U':
683 * Interpolate the named messages, if we
684 * are in receiving mail mode. Does the
685 * standard list processing garbage.
686 * If ~f is given, we don't shift over.
688 if (forward(linebuf + 2, _coll_fp, c) < 0)
689 goto jerr;
690 goto jcont;
691 case 'p':
693 * Print out the current state of the
694 * message without altering anything.
696 print_collf(_coll_fp, hp);
697 goto jcont;
698 case '|':
700 * Pipe message through command.
701 * Collect output as new message.
703 rewind(_coll_fp);
704 mespipe(&linebuf[2]);
705 goto jcont;
706 case 'v':
707 case 'e':
709 * Edit the current message.
710 * 'e' means to use EDITOR
711 * 'v' means to use VISUAL
713 rewind(_coll_fp);
714 mesedit(c, ok_blook(editheaders) ? hp : NULL);
715 goto jcont;
716 case '?':
718 * Last the lengthy help string.
719 * (Very ugly, but take care for compiler supported
720 * string lengths :()
722 (void)puts(tr(300,
723 "-------------------- ~ ESCAPES ----------------------------\n"
724 "~~ Quote a single tilde\n"
725 "~@ [file ...] Edit attachment list\n"
726 "~b users Add users to \"blind\" cc list\n"
727 "~c users Add users to cc list\n"
728 "~d Read in dead.letter\n"
729 "~e Edit the message buffer\n"
730 "~f messages Read in messages without indenting lines\n"
731 "~F messages Same as ~f, but keep all header lines\n"
732 "~h Prompt for to list, subject, cc, and \"blind\" cc list\n"));
733 (void)puts(tr(301,
734 "~r file Read a file into the message buffer\n"
735 "~p Print the message buffer\n"
736 "~q Abort message composition and save text to dead.letter\n"
737 "~m messages Read in messages with each line indented\n"
738 "~M messages Same as ~m, but keep all header lines\n"
739 "~s subject Set subject\n"
740 "~t users Add users to to list\n"
741 "~u messages Same as ~f, but without any headers\n"
742 "~U messages Same as ~m, but without any headers\n"));
743 (void)puts(tr(302,
744 "~v Invoke display editor on message\n"
745 "~w file Write message onto file\n"
746 "~x Abort message composition and discard text written so far\n"
747 "~!command Invoke the shell\n"
748 "~:command Execute a regular command\n"
749 "-----------------------------------------------------------\n"));
750 break;
754 jout:
755 if (_coll_fp != NULL) {
756 if ((cp = ok_vlook(NAIL_TAIL)) != NULL) {
757 if (putesc(cp, _coll_fp) < 0)
758 goto jerr;
759 if ((options & OPT_INTERACTIVE) &&
760 putesc(cp, stdout) < 0)
761 goto jerr;
763 rewind(_coll_fp);
765 if (linebuf != NULL)
766 free(linebuf);
767 handlerpop();
768 noreset--;
769 sigemptyset(&nset);
770 sigaddset(&nset, SIGINT);
771 sigaddset(&nset, SIGHUP);
772 sigprocmask(SIG_BLOCK, &nset, (sigset_t*)NULL);
773 safe_signal(SIGINT, _coll_saveint);
774 safe_signal(SIGHUP, _coll_savehup);
775 safe_signal(SIGTSTP, _coll_savetstp);
776 safe_signal(SIGTTOU, _coll_savettou);
777 safe_signal(SIGTTIN, _coll_savettin);
778 sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
779 return _coll_fp;
781 jerr:
782 if (tempMail != NULL) {
783 rm(tempMail);
784 Ftfree(&tempMail);
786 if (_coll_fp != NULL) {
787 Fclose(_coll_fp);
788 _coll_fp = NULL;
790 goto jout;
794 * Write a file, ex-like if f set.
796 static int
797 exwrite(char const *name, FILE *fp, int f)
799 FILE *of;
800 int c;
801 long cc;
802 int lc;
804 if (f) {
805 printf("\"%s\" ", name);
806 fflush(stdout);
808 if ((of = Fopen(name, "a")) == NULL) {
809 perror(NULL);
810 return(-1);
812 lc = 0;
813 cc = 0;
814 while ((c = getc(fp)) != EOF) {
815 cc++;
816 if (c == '\n')
817 lc++;
818 putc(c, of);
819 if (ferror(of)) {
820 perror(name);
821 Fclose(of);
822 return(-1);
825 Fclose(of);
826 printf(tr(65, "%d/%ld\n"), lc, cc);
827 fflush(stdout);
828 return(0);
831 static enum okay
832 makeheader(FILE *fp, struct header *hp)
834 char *tempEdit;
835 FILE *nf;
836 int c;
838 if ((nf = Ftemp(&tempEdit, "Re", "w+", 0600, 1)) == NULL) {
839 perror(tr(66, "temporary mail edit file"));
840 return STOP;
842 unlink(tempEdit);
843 Ftfree(&tempEdit);
845 extract_header(fp, hp);
846 while ((c = getc(fp)) != EOF) /* XXX bytewise, yuck! */
847 putc(c, nf);
848 if (fp != _coll_fp)
849 Fclose(_coll_fp);
850 Fclose(fp);
851 _coll_fp = nf;
852 if (check_from_and_sender(hp->h_from, hp->h_sender))
853 return STOP;
854 return OKAY;
858 * Edit the message being collected on fp.
859 * On return, make the edit file the new temp file.
861 static void
862 mesedit(int c, struct header *hp)
864 sighandler_type sigint = safe_signal(SIGINT, SIG_IGN);
865 bool_t saved = ok_blook(add_file_recipients);
866 FILE *nf;
868 ok_bset(add_file_recipients, FAL0);
869 nf = run_editor(_coll_fp, (off_t)-1, c, 0, hp, NULL, SEND_MBOX, sigint);
871 if (nf != NULL) {
872 if (hp) {
873 rewind(nf);
874 makeheader(nf, hp);
875 } else {
876 fseek(nf, 0L, SEEK_END);
877 Fclose(_coll_fp);
878 _coll_fp = nf;
882 ok_bset(add_file_recipients, saved);
883 safe_signal(SIGINT, sigint);
887 * Pipe the message through the command.
888 * Old message is on stdin of command;
889 * New message collected from stdout.
890 * Sh -c must return 0 to accept the new message.
892 static void
893 mespipe(char *cmd)
895 FILE *nf;
896 sighandler_type sigint = safe_signal(SIGINT, SIG_IGN);
897 char *tempEdit;
898 char const *sh;
900 if ((nf = Ftemp(&tempEdit, "Re", "w+", 0600, 1)) == NULL) {
901 perror(tr(66, "temporary mail edit file"));
902 goto out;
904 fflush(_coll_fp);
905 unlink(tempEdit);
906 Ftfree(&tempEdit);
908 * stdin = current message.
909 * stdout = new message.
911 if ((sh = ok_vlook(SHELL)) == NULL)
912 sh = XSHELL;
913 if (run_command(sh, 0, fileno(_coll_fp), fileno(nf), "-c", cmd, NULL)
914 < 0) {
915 Fclose(nf);
916 goto out;
918 if (fsize(nf) == 0) {
919 fprintf(stderr, tr(67, "No bytes from \"%s\" !?\n"), cmd);
920 Fclose(nf);
921 goto out;
924 * Take new files.
926 fseek(nf, 0L, SEEK_END);
927 Fclose(_coll_fp);
928 _coll_fp = nf;
929 out:
930 safe_signal(SIGINT, sigint);
934 * Interpolate the named messages into the current
935 * message, preceding each line with a tab.
936 * Return a count of the number of characters now in
937 * the message, or -1 if an error is encountered writing
938 * the message temporary. The flag argument is 'm' if we
939 * should shift over and 'f' if not.
941 static int
942 forward(char *ms, FILE *fp, int f)
944 int *msgvec;
945 struct ignoretab *ig;
946 char const *tabst;
947 enum sendaction action;
949 /*LINTED*/
950 msgvec = (int *)salloc((msgCount+1) * sizeof *msgvec);
951 if (msgvec == NULL)
952 return(0);
953 if (getmsglist(ms, msgvec, 0) < 0)
954 return(0);
955 if (*msgvec == 0) {
956 *msgvec = first(0, MMNORM);
957 if (*msgvec == 0) {
958 fputs(tr(68, "No appropriate messages\n"), stderr);
959 return 0;
961 msgvec[1] = 0;
964 if (f == 'f' || f == 'F' || f == 'u')
965 tabst = NULL;
966 else if ((tabst = ok_vlook(indentprefix)) == NULL)
967 tabst = "\t";
968 if (f == 'u' || f == 'U')
969 ig = allignore;
970 else
971 ig = upperchar(f) ? (struct ignoretab*)NULL : ignore;
972 action = (upperchar(f) && f != 'U') ? SEND_QUOTE_ALL : SEND_QUOTE;
974 printf(tr(69, "Interpolating:"));
975 for (; *msgvec != 0; msgvec++) {
976 struct message *mp = message + *msgvec - 1;
978 touch(mp);
979 printf(" %d", *msgvec);
980 if (sendmp(mp, fp, ig, tabst, action, NULL) < 0) {
981 perror(tr(70, "temporary mail file"));
982 return(-1);
985 printf("\n");
986 return(0);
990 * Print (continue) when continued after ^Z.
992 /*ARGSUSED*/
993 static void
994 collstop(int s)
996 sighandler_type old_action = safe_signal(s, SIG_DFL);
997 sigset_t nset;
999 sigemptyset(&nset);
1000 sigaddset(&nset, s);
1001 sigprocmask(SIG_UNBLOCK, &nset, (sigset_t *)NULL);
1002 kill(0, s);
1003 sigprocmask(SIG_BLOCK, &nset, (sigset_t *)NULL);
1004 safe_signal(s, old_action);
1005 if (_coll_jmp_p) {
1006 _coll_jmp_p = 0;
1007 _coll_hadintr = 0;
1008 siglongjmp(_coll_jmp, 1);
1013 * On interrupt, come here to save the partial message in ~/dead.letter.
1014 * Then jump out of the collection loop.
1016 /*ARGSUSED*/
1017 static void
1018 collint(int s)
1020 /* the control flow is subtle, because we can be called from ~q */
1021 if (_coll_hadintr == 0) {
1022 if (ok_blook(ignore)) {
1023 puts("@");
1024 fflush(stdout);
1025 clearerr(stdin);
1026 return;
1028 _coll_hadintr = 1;
1029 siglongjmp(_coll_jmp, 1);
1031 exit_status |= 04;
1032 if (ok_blook(save) && s != 0)
1033 savedeadletter(_coll_fp, 1);
1034 /* Aborting message, no need to fflush() .. */
1035 siglongjmp(_coll_abort, 1);
1038 /*ARGSUSED*/
1039 static void
1040 collhup(int s)
1042 (void)s;
1043 savedeadletter(_coll_fp, 1);
1045 * Let's pretend nobody else wants to clean up,
1046 * a true statement at this time.
1048 exit(1);
1051 FL void
1052 savedeadletter(FILE *fp, int fflush_rewind_first)
1054 char const *cp;
1055 int c;
1056 FILE *dbuf;
1057 ul_it lines, bytes;
1059 if (fflush_rewind_first) {
1060 (void)fflush(fp);
1061 rewind(fp);
1063 if (fsize(fp) == 0)
1064 goto jleave;
1066 cp = getdeadletter();
1067 c = umask(077);
1068 dbuf = Fopen(cp, "a");
1069 umask(c);
1070 if (dbuf == NULL)
1071 goto jleave;
1074 * There are problems with dup()ing of file-descriptors for child
1075 * processes. As long as those are not fixed in equal spirit to
1076 * (outof(): FIX and recode.., 2012-10-04), and to avoid reviving of
1077 * bugs like (If *record* is set, avoid writing dead content twice..,
1078 * 2012-09-14), we have to somehow accomplish that the FILE* fp
1079 * makes itself comfortable with the *real* offset of the underlaying
1080 * file descriptor. Unfortunately Standard I/O and POSIX don't
1081 * describe a way for that -- fflush();rewind(); won't do it.
1082 * This fseek(END),rewind() pair works around the problem on *BSD.
1084 (void)fseek(fp, 0, SEEK_END);
1085 rewind(fp);
1087 printf("\"%s\" ", cp);
1088 for (lines = bytes = 0; (c = getc(fp)) != EOF; ++bytes) {
1089 putc(c, dbuf);
1090 if (c == '\n')
1091 ++lines;
1093 printf("%lu/%lu\n", lines, bytes);
1095 Fclose(dbuf);
1096 rewind(fp);
1097 jleave: ;
1100 static int
1101 putesc(const char *s, FILE *stream)
1103 int n = 0;
1105 while (s[0]) {
1106 if (s[0] == '\\') {
1107 if (s[1] == 't') {
1108 if (putc('\t', stream) == EOF)
1109 return (-1);
1110 n++;
1111 s += 2;
1112 continue;
1114 if (s[1] == 'n') {
1115 if (putc('\n', stream) == EOF)
1116 return (-1);
1117 n++;
1118 s += 2;
1119 continue;
1122 if (putc(s[0], stream) == EOF)
1123 return (-1);
1124 n++;
1125 s++;
1127 if (putc('\n', stream) == EOF)
1128 return (-1);
1129 return (++n);