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