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