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