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