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