Correct BSD License clause numbering from 1-2-4 to 1-2-3.
[dragonfly.git] / usr.bin / mail / collect.c
1 /*
2  * Copyright (c) 1980, 1993
3  *      The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the University nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * @(#)collect.c        8.2 (Berkeley) 4/19/94
30  * $FreeBSD: src/usr.bin/mail/collect.c,v 1.4.6.3 2003/01/06 05:46:03 mikeh Exp $
31  * $DragonFly: src/usr.bin/mail/collect.c,v 1.4 2004/09/08 03:01:11 joerg Exp $
32  */
33
34 /*
35  * Mail -- a mail program
36  *
37  * Collect input from standard input, handling
38  * ~ escapes.
39  */
40
41 #include "rcv.h"
42 #include <fcntl.h>
43 #include "extern.h"
44
45 /*
46  * Read a message from standard output and return a read file to it
47  * or NULL on error.
48  */
49
50 /*
51  * The following hokiness with global variables is so that on
52  * receipt of an interrupt signal, the partial message can be salted
53  * away on dead.letter.
54  */
55
56 static  sig_t   saveint;                /* Previous SIGINT value */
57 static  sig_t   savehup;                /* Previous SIGHUP value */
58 static  sig_t   savetstp;               /* Previous SIGTSTP value */
59 static  sig_t   savettou;               /* Previous SIGTTOU value */
60 static  sig_t   savettin;               /* Previous SIGTTIN value */
61 static  FILE    *collf;                 /* File for saving away */
62 static  int     hadintr;                /* Have seen one SIGINT so far */
63
64 static  jmp_buf colljmp;                /* To get back to work */
65 static  int     colljmp_p;              /* whether to long jump */
66 static  jmp_buf collabort;              /* To end collection with error */
67
68 FILE *
69 collect(struct header *hp, int printheaders)
70 {
71         FILE *fbuf;
72         int lc, cc, escape, eofcount, fd, c, t;
73         char linebuf[LINESIZE], tempname[PATHSIZE], *cp, getsub;
74         sigset_t nset;
75         int longline, lastlong, rc;     /* So we don't make 2 or more lines
76                                            out of a long input line. */
77
78         collf = NULL;
79         /*
80          * Start catching signals from here, but we're still die on interrupts
81          * until we're in the main loop.
82          */
83         sigemptyset(&nset);
84         sigaddset(&nset, SIGINT);
85         sigaddset(&nset, SIGHUP);
86         sigprocmask(SIG_BLOCK, &nset, NULL);
87         if ((saveint = signal(SIGINT, SIG_IGN)) != SIG_IGN)
88                 signal(SIGINT, collint);
89         if ((savehup = signal(SIGHUP, SIG_IGN)) != SIG_IGN)
90                 signal(SIGHUP, collhup);
91         savetstp = signal(SIGTSTP, collstop);
92         savettou = signal(SIGTTOU, collstop);
93         savettin = signal(SIGTTIN, collstop);
94         if (setjmp(collabort) || setjmp(colljmp)) {
95                 rm(tempname);
96                 goto err;
97         }
98         sigprocmask(SIG_UNBLOCK, &nset, NULL);
99
100         noreset++;
101         snprintf(tempname, sizeof(tempname), "%s/mail.RsXXXXXXXXXX", tmpdir);
102         if ((fd = mkstemp(tempname)) == -1 ||
103             (collf = Fdopen(fd, "w+")) == NULL) {
104                 warn("%s", tempname);
105                 goto err;
106         }
107         rm(tempname);
108
109         /*
110          * If we are going to prompt for a subject,
111          * refrain from printing a newline after
112          * the headers (since some people mind).
113          */
114         t = GTO|GSUBJECT|GCC|GNL;
115         getsub = 0;
116         if (hp->h_subject == NULL && value("interactive") != NULL &&
117             (value("ask") != NULL || value("asksub") != NULL))
118                 t &= ~GNL, getsub++;
119         if (printheaders) {
120                 puthead(hp, stdout, t);
121                 fflush(stdout);
122         }
123         if ((cp = value("escape")) != NULL)
124                 escape = *cp;
125         else
126                 escape = ESCAPE;
127         eofcount = 0;
128         hadintr = 0;
129         lastlong = 0;
130         longline = 0;
131
132         if (!setjmp(colljmp)) {
133                 if (getsub)
134                         grabh(hp, GSUBJECT);
135         } else {
136                 /*
137                  * Come here for printing the after-signal message.
138                  * Duplicate messages won't be printed because
139                  * the write is aborted if we get a SIGTTOU.
140                  */
141 cont:
142                 if (hadintr) {
143                         fflush(stdout);
144                         fprintf(stderr,
145                         "\n(Interrupt -- one more to kill letter)\n");
146                 } else {
147                         printf("(continue)\n");
148                         fflush(stdout);
149                 }
150         }
151         for (;;) {
152                 colljmp_p = 1;
153                 c = readline(stdin, linebuf, LINESIZE);
154                 colljmp_p = 0;
155                 if (c < 0) {
156                         if (value("interactive") != NULL &&
157                             value("ignoreeof") != NULL && ++eofcount < 25) {
158                                 printf("Use \".\" to terminate letter\n");
159                                 continue;
160                         }
161                         break;
162                 }
163                 lastlong = longline;
164                 longline = c == LINESIZE - 1;
165                 eofcount = 0;
166                 hadintr = 0;
167                 if (linebuf[0] == '.' && linebuf[1] == '\0' &&
168                     value("interactive") != NULL && !lastlong &&
169                     (value("dot") != NULL || value("ignoreeof") != NULL))
170                         break;
171                 if (linebuf[0] != escape || value("interactive") == NULL ||
172                     lastlong) {
173                         if (putline(collf, linebuf, !longline) < 0)
174                                 goto err;
175                         continue;
176                 }
177                 c = linebuf[1];
178                 switch (c) {
179                 default:
180                         /*
181                          * On double escape, just send the single one.
182                          * Otherwise, it's an error.
183                          */
184                         if (c == escape) {
185                                 if (putline(collf, &linebuf[1], !longline) < 0)
186                                         goto err;
187                                 else
188                                         break;
189                         }
190                         printf("Unknown tilde escape.\n");
191                         break;
192                 case 'C':
193                         /*
194                          * Dump core.
195                          */
196                         core();
197                         break;
198                 case '!':
199                         /*
200                          * Shell escape, send the balance of the
201                          * line to sh -c.
202                          */
203                         shell(&linebuf[2]);
204                         break;
205                 case ':':
206                 case '_':
207                         /*
208                          * Escape to command mode, but be nice!
209                          */
210                         execute(&linebuf[2], 1);
211                         goto cont;
212                 case '.':
213                         /*
214                          * Simulate end of file on input.
215                          */
216                         goto out;
217                 case 'q':
218                         /*
219                          * Force a quit of sending mail.
220                          * Act like an interrupt happened.
221                          */
222                         hadintr++;
223                         collint(SIGINT);
224                         exit(1);
225                 case 'x':
226                         /*
227                          * Exit, do not save in dead.letter.
228                          */
229                         goto err;
230                 case 'h':
231                         /*
232                          * Grab a bunch of headers.
233                          */
234                         grabh(hp, GTO|GSUBJECT|GCC|GBCC);
235                         goto cont;
236                 case 't':
237                         /*
238                          * Add to the To list.
239                          */
240                         hp->h_to = cat(hp->h_to, extract(&linebuf[2], GTO));
241                         break;
242                 case 's':
243                         /*
244                          * Set the Subject line.
245                          */
246                         cp = &linebuf[2];
247                         while (isspace((unsigned char)*cp))
248                                 cp++;
249                         hp->h_subject = savestr(cp);
250                         break;
251                 case 'R':
252                         /*
253                          * Set the Reply-To line.
254                          */
255                         cp = &linebuf[2];
256                         while (isspace((unsigned char)*cp))
257                                 cp++;
258                         hp->h_replyto = savestr(cp);
259                         break;
260                 case 'c':
261                         /*
262                          * Add to the CC list.
263                          */
264                         hp->h_cc = cat(hp->h_cc, extract(&linebuf[2], GCC));
265                         break;
266                 case 'b':
267                         /*
268                          * Add to the BCC list.
269                          */
270                         hp->h_bcc = cat(hp->h_bcc, extract(&linebuf[2], GBCC));
271                         break;
272                 case 'i':
273                 case 'A':
274                 case 'a':
275                         /*
276                          * Insert named variable in message.
277                          */
278                         switch(c) {
279                                 case 'i':
280                                         cp = &linebuf[2];
281                                         while(isspace((unsigned char)*cp))
282                                                 cp++;
283                                         break;
284                                 case 'a':
285                                         cp = "sign";
286                                         break;
287                                 case 'A':
288                                         cp = "Sign";
289                                         break;
290                                 default:
291                                         goto err;
292                         }
293
294                         if(*cp != '\0' && (cp = value(cp)) != NULL) {
295                                 printf("%s\n", cp);
296                                 if(putline(collf, cp, 1) < 0)
297                                         goto err;
298                         }
299
300                         break;
301                 case 'd':
302                         /*
303                          * Read in the dead letter file.
304                          */
305                         if (strlcpy(linebuf + 2, getdeadletter(),
306                                 sizeof(linebuf) - 2)
307                             >= sizeof(linebuf) - 2) {
308                                 printf("Line buffer overflow\n");
309                                 break;
310                         }
311                         /* FALLTHROUGH */
312                 case 'r':
313                 case '<':
314                         /*
315                          * Invoke a file:
316                          * Search for the file name,
317                          * then open it and copy the contents to collf.
318                          */
319                         cp = &linebuf[2];
320                         while (isspace((unsigned char)*cp))
321                                 cp++;
322                         if (*cp == '\0') {
323                                 printf("Interpolate what file?\n");
324                                 break;
325                         }
326                         cp = expand(cp);
327                         if (cp == NULL)
328                                 break;
329                         if (*cp == '!') {
330                                 /*
331                                  * Insert stdout of command.
332                                  */
333                                 char *sh;
334                                 int nullfd, tempfd, rc;
335                                 char tempname2[PATHSIZE];
336
337                                 if ((nullfd = open("/dev/null", O_RDONLY, 0))
338                                     == -1) {
339                                         warn("/dev/null");
340                                         break;
341                                 }
342
343                                 snprintf(tempname2, sizeof(tempname2),
344                                          "%s/mail.ReXXXXXXXXXX", tmpdir);
345                                 if ((tempfd = mkstemp(tempname2)) == -1 ||
346                                     (fbuf = Fdopen(tempfd, "w+")) == NULL) {
347                                         warn("%s", tempname2);
348                                         break;
349                                 }
350                                 unlink(tempname2);
351
352                                 if ((sh = value("SHELL")) == NULL)
353                                         sh = _PATH_CSHELL;
354
355                                 rc = run_command(sh, 0, nullfd, fileno(fbuf),
356                                     "-c", cp+1, NULL);
357
358                                 close(nullfd);
359
360                                 if (rc < 0) {
361                                         Fclose(fbuf);
362                                         break;
363                                 }
364
365                                 if (fsize(fbuf) == 0) {
366                                         fprintf(stderr,
367                                             "No bytes from command \"%s\"\n",
368                                             cp+1);
369                                         Fclose(fbuf);
370                                         break;
371                                 }
372
373                                 rewind(fbuf);
374                         } else if (isdir(cp)) {
375                                 printf("%s: Directory\n", cp);
376                                 break;
377                         } else if ((fbuf = Fopen(cp, "r")) == NULL) {
378                                 warn("%s", cp);
379                                 break;
380                         }
381                         printf("\"%s\" ", cp);
382                         fflush(stdout);
383                         lc = 0;
384                         cc = 0;
385                         while ((rc = readline(fbuf, linebuf, LINESIZE)) >= 0) {
386                                 if (rc != LINESIZE - 1)
387                                         lc++;
388                                 if ((t = putline(collf, linebuf,
389                                          rc != LINESIZE - 1)) < 0) {
390                                         Fclose(fbuf);
391                                         goto err;
392                                 }
393                                 cc += t;
394                         }
395                         Fclose(fbuf);
396                         printf("%d/%d\n", lc, cc);
397                         break;
398                 case 'w':
399                         /*
400                          * Write the message on a file.
401                          */
402                         cp = &linebuf[2];
403                         while (*cp == ' ' || *cp == '\t')
404                                 cp++;
405                         if (*cp == '\0') {
406                                 fprintf(stderr, "Write what file!?\n");
407                                 break;
408                         }
409                         if ((cp = expand(cp)) == NULL)
410                                 break;
411                         rewind(collf);
412                         exwrite(cp, collf, 1);
413                         break;
414                 case 'm':
415                 case 'M':
416                 case 'f':
417                 case 'F':
418                         /*
419                          * Interpolate the named messages, if we
420                          * are in receiving mail mode.  Does the
421                          * standard list processing garbage.
422                          * If ~f is given, we don't shift over.
423                          */
424                         if (forward(linebuf + 2, collf, tempname, c) < 0)
425                                 goto err;
426                         goto cont;
427                 case '?':
428                         if ((fbuf = Fopen(_PATH_TILDE, "r")) == NULL) {
429                                 warn("%s", _PATH_TILDE);
430                                 break;
431                         }
432                         while ((t = getc(fbuf)) != EOF)
433                                 putchar(t);
434                         Fclose(fbuf);
435                         break;
436                 case 'p':
437                         /*
438                          * Print out the current state of the
439                          * message without altering anything.
440                          */
441                         rewind(collf);
442                         printf("-------\nMessage contains:\n");
443                         puthead(hp, stdout, GTO|GSUBJECT|GCC|GBCC|GNL);
444                         while ((t = getc(collf)) != EOF)
445                                 putchar(t);
446                         goto cont;
447                 case '|':
448                         /*
449                          * Pipe message through command.
450                          * Collect output as new message.
451                          */
452                         rewind(collf);
453                         mespipe(collf, &linebuf[2]);
454                         goto cont;
455                 case 'v':
456                 case 'e':
457                         /*
458                          * Edit the current message.
459                          * 'e' means to use EDITOR
460                          * 'v' means to use VISUAL
461                          */
462                         rewind(collf);
463                         mesedit(collf, c);
464                         goto cont;
465                 }
466         }
467         goto out;
468 err:
469         if (collf != NULL) {
470                 Fclose(collf);
471                 collf = NULL;
472         }
473 out:
474         if (collf != NULL)
475                 rewind(collf);
476         noreset--;
477         sigprocmask(SIG_BLOCK, &nset, NULL);
478         signal(SIGINT, saveint);
479         signal(SIGHUP, savehup);
480         signal(SIGTSTP, savetstp);
481         signal(SIGTTOU, savettou);
482         signal(SIGTTIN, savettin);
483         sigprocmask(SIG_UNBLOCK, &nset, NULL);
484         return (collf);
485 }
486
487 /*
488  * Write a file, ex-like if f set.
489  */
490 int
491 exwrite(char *name, FILE *fp, int f)
492 {
493         FILE *of;
494         int c, lc;
495         long cc;
496         struct stat junk;
497
498         if (f) {
499                 printf("\"%s\" ", name);
500                 fflush(stdout);
501         }
502         if (stat(name, &junk) >= 0 && S_ISREG(junk.st_mode)) {
503                 if (!f)
504                         fprintf(stderr, "%s: ", name);
505                 fprintf(stderr, "File exists\n");
506                 return (-1);
507         }
508         if ((of = Fopen(name, "w")) == NULL) {
509                 warn(NULL);
510                 return (-1);
511         }
512         lc = 0;
513         cc = 0;
514         while ((c = getc(fp)) != EOF) {
515                 cc++;
516                 if (c == '\n')
517                         lc++;
518                 putc(c, of);
519                 if (ferror(of)) {
520                         warnx("%s", name);
521                         Fclose(of);
522                         return (-1);
523                 }
524         }
525         Fclose(of);
526         printf("%d/%ld\n", lc, cc);
527         fflush(stdout);
528         return (0);
529 }
530
531 /*
532  * Edit the message being collected on fp.
533  * On return, make the edit file the new temp file.
534  */
535 void
536 mesedit(FILE *fp, int c)
537 {
538         sig_t sigint = signal(SIGINT, SIG_IGN);
539         FILE *nf = run_editor(fp, (off_t)-1, c, 0);
540
541         if (nf != NULL) {
542                 fseeko(nf, (off_t)0, SEEK_END);
543                 collf = nf;
544                 Fclose(fp);
545         }
546         signal(SIGINT, sigint);
547 }
548
549 /*
550  * Pipe the message through the command.
551  * Old message is on stdin of command;
552  * New message collected from stdout.
553  * Sh -c must return 0 to accept the new message.
554  */
555 void
556 mespipe(FILE *fp, char *cmd)
557 {
558         FILE *nf;
559         int fd;
560         sig_t sigint = signal(SIGINT, SIG_IGN);
561         char *sh, tempname[PATHSIZE];
562
563         snprintf(tempname, sizeof(tempname), "%s/mail.ReXXXXXXXXXX", tmpdir);
564         if ((fd = mkstemp(tempname)) == -1 ||
565             (nf = Fdopen(fd, "w+")) == NULL) {
566                 warn("%s", tempname);
567                 goto out;
568         }
569         rm(tempname);
570         /*
571          * stdin = current message.
572          * stdout = new message.
573          */
574         if ((sh = value("SHELL")) == NULL)
575                 sh = _PATH_CSHELL;
576         if (run_command(sh, 0, fileno(fp), fileno(nf), "-c", cmd, NULL) < 0) {
577                 Fclose(nf);
578                 goto out;
579         }
580         if (fsize(nf) == 0) {
581                 fprintf(stderr, "No bytes from \"%s\" !?\n", cmd);
582                 Fclose(nf);
583                 goto out;
584         }
585         /*
586          * Take new files.
587          */
588         fseeko(nf, (off_t)0, SEEK_END);
589         collf = nf;
590         Fclose(fp);
591 out:
592         signal(SIGINT, sigint);
593 }
594
595 /*
596  * Interpolate the named messages into the current
597  * message, preceding each line with a tab.
598  * Return a count of the number of characters now in
599  * the message, or -1 if an error is encountered writing
600  * the message temporary.  The flag argument is 'm' if we
601  * should shift over and 'f' if not.
602  */
603 int
604 forward(char *ms, FILE *fp, char *fn, int f)
605 {
606         int *msgvec;
607         struct ignoretab *ig;
608         char *tabst;
609
610         msgvec = (int *)salloc((msgCount+1) * sizeof(*msgvec));
611         if (msgvec == NULL)
612                 return (0);
613         if (getmsglist(ms, msgvec, 0) < 0)
614                 return (0);
615         if (*msgvec == 0) {
616                 *msgvec = first(0, MMNORM);
617                 if (*msgvec == 0) {
618                         printf("No appropriate messages\n");
619                         return (0);
620                 }
621                 msgvec[1] = 0;
622         }
623         if (f == 'f' || f == 'F')
624                 tabst = NULL;
625         else if ((tabst = value("indentprefix")) == NULL)
626                 tabst = "\t";
627         ig = isupper((unsigned char)f) ? NULL : ignore;
628         printf("Interpolating:");
629         for (; *msgvec != 0; msgvec++) {
630                 struct message *mp = message + *msgvec - 1;
631
632                 touch(mp);
633                 printf(" %d", *msgvec);
634                 if (sendmessage(mp, fp, ig, tabst) < 0) {
635                         warnx("%s", fn);
636                         return (-1);
637                 }
638         }
639         printf("\n");
640         return (0);
641 }
642
643 /*
644  * Print (continue) when continued after ^Z.
645  */
646 /*ARGSUSED*/
647 void
648 collstop(int s)
649 {
650         sig_t old_action = signal(s, SIG_DFL);
651         sigset_t nset;
652
653         sigemptyset(&nset);
654         sigaddset(&nset, s);
655         sigprocmask(SIG_UNBLOCK, &nset, NULL);
656         kill(0, s);
657         sigprocmask(SIG_BLOCK, &nset, NULL);
658         signal(s, old_action);
659         if (colljmp_p) {
660                 colljmp_p = 0;
661                 hadintr = 0;
662                 longjmp(colljmp, 1);
663         }
664 }
665
666 /*
667  * On interrupt, come here to save the partial message in ~/dead.letter.
668  * Then jump out of the collection loop.
669  */
670 /*ARGSUSED*/
671 void
672 collint(int s)
673 {
674         /*
675          * the control flow is subtle, because we can be called from ~q.
676          */
677         if (!hadintr) {
678                 if (value("ignore") != NULL) {
679                         printf("@");
680                         fflush(stdout);
681                         clearerr(stdin);
682                         return;
683                 }
684                 hadintr = 1;
685                 longjmp(colljmp, 1);
686         }
687         rewind(collf);
688         if (value("nosave") == NULL)
689                 savedeadletter(collf);
690         longjmp(collabort, 1);
691 }
692
693 /*ARGSUSED*/
694 void
695 collhup(int s)
696 {
697         rewind(collf);
698         savedeadletter(collf);
699         /*
700          * Let's pretend nobody else wants to clean up,
701          * a true statement at this time.
702          */
703         exit(1);
704 }
705
706 void
707 savedeadletter(FILE *fp)
708 {
709         FILE *dbuf;
710         int c;
711         char *cp;
712
713         if (fsize(fp) == 0)
714                 return;
715         cp = getdeadletter();
716         c = umask(077);
717         dbuf = Fopen(cp, "a");
718         umask(c);
719         if (dbuf == NULL)
720                 return;
721         while ((c = getc(fp)) != EOF)
722                 putc(c, dbuf);
723         Fclose(dbuf);
724         rewind(fp);
725 }