]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/mail/collect.c
MFC r327235:
[FreeBSD/FreeBSD.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  * 4. 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
30 #ifndef lint
31 #if 0
32 static char sccsid[] = "@(#)collect.c   8.2 (Berkeley) 4/19/94";
33 #endif
34 #endif /* not lint */
35 #include <sys/cdefs.h>
36 __FBSDID("$FreeBSD$");
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         (void)sigemptyset(&nset);
88         (void)sigaddset(&nset, SIGINT);
89         (void)sigaddset(&nset, SIGHUP);
90         (void)sigprocmask(SIG_BLOCK, &nset, NULL);
91         if ((saveint = signal(SIGINT, SIG_IGN)) != SIG_IGN)
92                 (void)signal(SIGINT, collint);
93         if ((savehup = signal(SIGHUP, SIG_IGN)) != SIG_IGN)
94                 (void)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                 (void)rm(tempname);
100                 goto err;
101         }
102         (void)sigprocmask(SIG_UNBLOCK, &nset, NULL);
103
104         noreset++;
105         (void)snprintf(tempname, sizeof(tempname),
106             "%s/mail.RsXXXXXXXXXX", tmpdir);
107         if ((fd = mkstemp(tempname)) == -1 ||
108             (collf = Fdopen(fd, "w+")) == NULL) {
109                 warn("%s", tempname);
110                 goto err;
111         }
112         (void)rm(tempname);
113
114         /*
115          * If we are going to prompt for a subject,
116          * refrain from printing a newline after
117          * the headers (since some people mind).
118          */
119         t = GTO|GSUBJECT|GCC|GNL;
120         getsub = 0;
121         if (hp->h_subject == NULL && value("interactive") != NULL &&
122             (value("ask") != NULL || value("asksub") != NULL))
123                 t &= ~GNL, getsub++;
124         if (printheaders) {
125                 puthead(hp, stdout, t);
126                 (void)fflush(stdout);
127         }
128         if ((cp = value("escape")) != NULL)
129                 escape = *cp;
130         else
131                 escape = ESCAPE;
132         eofcount = 0;
133         hadintr = 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                         (void)fflush(stdout);
148                         fprintf(stderr,
149                         "\n(Interrupt -- one more to kill letter)\n");
150                 } else {
151                         printf("(continue)\n");
152                         (void)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(_PATH_DEVNULL, O_RDONLY, 0))
342                                     == -1) {
343                                         warn(_PATH_DEVNULL);
344                                         break;
345                                 }
346
347                                 (void)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                                 (void)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                                         (void)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                                         (void)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                         (void)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                                         (void)Fclose(fbuf);
395                                         goto err;
396                                 }
397                                 cc += t;
398                         }
399                         (void)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                                 (void)putchar(t);
438                         (void)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                                 (void)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                 (void)Fclose(collf);
475                 collf = NULL;
476         }
477 out:
478         if (collf != NULL)
479                 rewind(collf);
480         noreset--;
481         (void)sigprocmask(SIG_BLOCK, &nset, NULL);
482         (void)signal(SIGINT, saveint);
483         (void)signal(SIGHUP, savehup);
484         (void)signal(SIGTSTP, savetstp);
485         (void)signal(SIGTTOU, savettou);
486         (void)signal(SIGTTIN, savettin);
487         (void)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                 (void)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((char *)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                 (void)putc(c, of);
523                 if (ferror(of)) {
524                         warnx("%s", name);
525                         (void)Fclose(of);
526                         return (-1);
527                 }
528         }
529         (void)Fclose(of);
530         printf("%d/%ld\n", lc, cc);
531         (void)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                 (void)fseeko(nf, (off_t)0, SEEK_END);
547                 collf = nf;
548                 (void)Fclose(fp);
549         }
550         (void)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         (void)snprintf(tempname, sizeof(tempname),
568             "%s/mail.ReXXXXXXXXXX", tmpdir);
569         if ((fd = mkstemp(tempname)) == -1 ||
570             (nf = Fdopen(fd, "w+")) == NULL) {
571                 warn("%s", tempname);
572                 goto out;
573         }
574         (void)rm(tempname);
575         /*
576          * stdin = current message.
577          * stdout = new message.
578          */
579         if ((sh = value("SHELL")) == NULL)
580                 sh = _PATH_CSHELL;
581         if (run_command(sh,
582             0, fileno(fp), fileno(nf), "-c", cmd, NULL) < 0) {
583                 (void)Fclose(nf);
584                 goto out;
585         }
586         if (fsize(nf) == 0) {
587                 fprintf(stderr, "No bytes from \"%s\" !?\n", cmd);
588                 (void)Fclose(nf);
589                 goto out;
590         }
591         /*
592          * Take new files.
593          */
594         (void)fseeko(nf, (off_t)0, SEEK_END);
595         collf = nf;
596         (void)Fclose(fp);
597 out:
598         (void)signal(SIGINT, sigint);
599 }
600
601 /*
602  * Interpolate the named messages into the current
603  * message, preceding each line with a tab.
604  * Return a count of the number of characters now in
605  * the message, or -1 if an error is encountered writing
606  * the message temporary.  The flag argument is 'm' if we
607  * should shift over and 'f' if not.
608  */
609 int
610 forward(char ms[], FILE *fp, char *fn, int f)
611 {
612         int *msgvec;
613         struct ignoretab *ig;
614         char *tabst;
615
616         msgvec = (int *)salloc((msgCount+1) * sizeof(*msgvec));
617         if (msgvec == NULL)
618                 return (0);
619         if (getmsglist(ms, msgvec, 0) < 0)
620                 return (0);
621         if (*msgvec == 0) {
622                 *msgvec = first(0, MMNORM);
623                 if (*msgvec == 0) {
624                         printf("No appropriate messages\n");
625                         return (0);
626                 }
627                 msgvec[1] = 0;
628         }
629         if (f == 'f' || f == 'F')
630                 tabst = NULL;
631         else if ((tabst = value("indentprefix")) == NULL)
632                 tabst = "\t";
633         ig = isupper((unsigned char)f) ? NULL : ignore;
634         printf("Interpolating:");
635         for (; *msgvec != 0; msgvec++) {
636                 struct message *mp = message + *msgvec - 1;
637
638                 touch(mp);
639                 printf(" %d", *msgvec);
640                 if (sendmessage(mp, fp, ig, tabst) < 0) {
641                         warnx("%s", fn);
642                         return (-1);
643                 }
644         }
645         printf("\n");
646         return (0);
647 }
648
649 /*
650  * Print (continue) when continued after ^Z.
651  */
652 /*ARGSUSED*/
653 void
654 collstop(int s)
655 {
656         sig_t old_action = signal(s, SIG_DFL);
657         sigset_t nset;
658
659         (void)sigemptyset(&nset);
660         (void)sigaddset(&nset, s);
661         (void)sigprocmask(SIG_UNBLOCK, &nset, NULL);
662         (void)kill(0, s);
663         (void)sigprocmask(SIG_BLOCK, &nset, NULL);
664         (void)signal(s, old_action);
665         if (colljmp_p) {
666                 colljmp_p = 0;
667                 hadintr = 0;
668                 longjmp(colljmp, 1);
669         }
670 }
671
672 /*
673  * On interrupt, come here to save the partial message in ~/dead.letter.
674  * Then jump out of the collection loop.
675  */
676 /*ARGSUSED*/
677 void
678 collint(int s __unused)
679 {
680         /*
681          * the control flow is subtle, because we can be called from ~q.
682          */
683         if (!hadintr) {
684                 if (value("ignore") != NULL) {
685                         printf("@");
686                         (void)fflush(stdout);
687                         clearerr(stdin);
688                         return;
689                 }
690                 hadintr = 1;
691                 longjmp(colljmp, 1);
692         }
693         rewind(collf);
694         if (value("nosave") == NULL)
695                 savedeadletter(collf);
696         longjmp(collabort, 1);
697 }
698
699 /*ARGSUSED*/
700 void
701 collhup(int s __unused)
702 {
703         rewind(collf);
704         savedeadletter(collf);
705         /*
706          * Let's pretend nobody else wants to clean up,
707          * a true statement at this time.
708          */
709         exit(1);
710 }
711
712 void
713 savedeadletter(FILE *fp)
714 {
715         FILE *dbuf;
716         int c;
717         char *cp;
718
719         if (fsize(fp) == 0)
720                 return;
721         cp = getdeadletter();
722         c = umask(077);
723         dbuf = Fopen(cp, "a");
724         (void)umask(c);
725         if (dbuf == NULL)
726                 return;
727         while ((c = getc(fp)) != EOF)
728                 (void)putc(c, dbuf);
729         (void)Fclose(dbuf);
730         rewind(fp);
731 }