collect.c revision 1.19 1 /* $NetBSD: collect.c,v 1.19 1999/02/17 20:48:48 mjl 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. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by the University of
18 * California, Berkeley and its contributors.
19 * 4. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36 #include <sys/cdefs.h>
37 #ifndef lint
38 #if 0
39 static char sccsid[] = "@(#)collect.c 8.2 (Berkeley) 4/19/94";
40 #else
41 __RCSID("$NetBSD: collect.c,v 1.19 1999/02/17 20:48:48 mjl Exp $");
42 #endif
43 #endif /* not lint */
44
45 /*
46 * Mail -- a mail program
47 *
48 * Collect input from standard input, handling
49 * ~ escapes.
50 */
51
52 #include "rcv.h"
53 #include "extern.h"
54
55 /*
56 * Read a message from standard output 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 FILE *
79 collect(hp, printheaders)
80 struct header *hp;
81 int printheaders;
82 {
83 FILE *fbuf;
84 int lc, cc, escape, eofcount;
85 int c, t;
86 char linebuf[LINESIZE], *cp;
87 extern char *tempMail;
88 char getsub;
89 sigset_t nset;
90 int longline, lastlong, rc; /* So we don't make 2 or more lines
91 out of a long input line. */
92 #if __GNUC__
93 /* Avoid longjmp clobbering */
94 (void) &escape;
95 (void) &eofcount;
96 (void) &getsub;
97 (void) &longline;
98 #endif
99
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 sigemptyset(&nset);
106 sigaddset(&nset, SIGINT);
107 sigaddset(&nset, SIGHUP);
108 sigprocmask(SIG_BLOCK, &nset, NULL);
109 if ((saveint = signal(SIGINT, SIG_IGN)) != SIG_IGN)
110 signal(SIGINT, collint);
111 if ((savehup = signal(SIGHUP, SIG_IGN)) != SIG_IGN)
112 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 rm(tempMail);
118 goto err;
119 }
120 sigprocmask(SIG_UNBLOCK, &nset, NULL);
121
122 noreset++;
123 if ((collf = Fopen(tempMail, "w+")) == NULL) {
124 perror(tempMail);
125 goto err;
126 }
127 unlink(tempMail);
128
129 /*
130 * If we are going to prompt for a subject,
131 * refrain from printing a newline after
132 * the headers (since some people mind).
133 */
134 t = GTO|GSUBJECT|GCC|GNL;
135 getsub = 0;
136 if (hp->h_subject == NOSTR && value("interactive") != NOSTR &&
137 (value("ask") != NOSTR || value("asksub") != NOSTR))
138 t &= ~GNL, getsub++;
139 if (printheaders) {
140 puthead(hp, stdout, t);
141 fflush(stdout);
142 }
143 if ((cp = value("escape")) != NOSTR)
144 escape = *cp;
145 else
146 escape = ESCAPE;
147 eofcount = 0;
148 hadintr = 0;
149 lastlong = 0;
150 longline = 0;
151
152 if (!setjmp(colljmp)) {
153 if (getsub)
154 grabh(hp, GSUBJECT);
155 } else {
156 /*
157 * Come here for printing the after-signal message.
158 * Duplicate messages won't be printed because
159 * the write is aborted if we get a SIGTTOU.
160 */
161 cont:
162 if (hadintr) {
163 fflush(stdout);
164 fprintf(stderr,
165 "\n(Interrupt -- one more to kill letter)\n");
166 } else {
167 printf("(continue)\n");
168 fflush(stdout);
169 }
170 }
171 for (;;) {
172 colljmp_p = 1;
173 c = readline(stdin, linebuf, LINESIZE);
174 colljmp_p = 0;
175 if (c < 0) {
176 if (value("interactive") != NOSTR &&
177 value("ignoreeof") != NOSTR && ++eofcount < 25) {
178 printf("Use \".\" to terminate letter\n");
179 continue;
180 }
181 break;
182 }
183 lastlong = longline;
184 longline = c == LINESIZE-1;
185 eofcount = 0;
186 hadintr = 0;
187 if (linebuf[0] == '.' && linebuf[1] == '\0' &&
188 value("interactive") != NOSTR && !lastlong &&
189 (value("dot") != NOSTR || value("ignoreeof") != NOSTR))
190 break;
191 if (linebuf[0] != escape || value("interactive") == NOSTR ||
192 lastlong) {
193 if (putline(collf, linebuf, !longline) < 0)
194 goto err;
195 continue;
196 }
197 c = linebuf[1];
198 switch (c) {
199 default:
200 /*
201 * On double escape, just send the single one.
202 * Otherwise, it's an error.
203 */
204 if (c == escape) {
205 if (putline(collf, &linebuf[1], !longline) < 0)
206 goto err;
207 else
208 break;
209 }
210 printf("Unknown tilde escape.\n");
211 break;
212 case 'C':
213 /*
214 * Dump core.
215 */
216 core(NULL);
217 break;
218 case '!':
219 /*
220 * Shell escape, send the balance of the
221 * line to sh -c.
222 */
223 shell(&linebuf[2]);
224 break;
225 case ':':
226 case '_':
227 /*
228 * Escape to command mode, but be nice!
229 */
230 execute(&linebuf[2], 1);
231 goto cont;
232 case '.':
233 /*
234 * Simulate end of file on input.
235 */
236 goto out;
237 case 'q':
238 /*
239 * Force a quit of sending mail.
240 * Act like an interrupt happened.
241 */
242 hadintr++;
243 collint(SIGINT);
244 exit(1);
245
246 case 'x': /* exit, do not save in dead.letter */
247 goto err;
248
249 case 'h':
250 /*
251 * Grab a bunch of headers.
252 */
253 grabh(hp, GTO|GSUBJECT|GCC|GBCC);
254 goto cont;
255 case 't':
256 /*
257 * Add to the To list.
258 */
259 hp->h_to = cat(hp->h_to, extract(&linebuf[2], GTO));
260 break;
261 case 's':
262 /*
263 * Set the Subject list.
264 */
265 cp = &linebuf[2];
266 while (isspace((unsigned char)*cp))
267 cp++;
268 hp->h_subject = savestr(cp);
269 break;
270 case 'c':
271 /*
272 * Add to the CC list.
273 */
274 hp->h_cc = cat(hp->h_cc, extract(&linebuf[2], GCC));
275 break;
276 case 'b':
277 /*
278 * Add stuff to blind carbon copies list.
279 */
280 hp->h_bcc = cat(hp->h_bcc, extract(&linebuf[2], GBCC));
281 break;
282 case 'i':
283 case 'A':
284 case 'a':
285 /*
286 * Insert named variable in message
287 */
288
289 switch(c) {
290 case 'i':
291 cp = &linebuf[2];
292 while(isspace((unsigned char) *cp))
293 cp++;
294
295 break;
296 case 'a':
297 cp = "sign";
298 break;
299 case 'A':
300 cp = "Sign";
301 break;
302 default:
303 goto err;
304 }
305
306 if(*cp && (cp = value(cp)) != NOSTR) {
307 printf("%s\n", cp);
308 if(putline(collf, cp, 1) < 0)
309 goto err;
310 }
311
312 break;
313
314 case 'd':
315 strcpy(linebuf + 2, getdeadletter());
316 /* fall into . . . */
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
332 cp = expand(cp);
333 if (cp == NOSTR)
334 break;
335
336 if (*cp == '!') { /* insert stdout of command */
337 extern char *tempEdit;
338 char *shell;
339 int nullfd;
340 int rc;
341
342 if((nullfd = open("/dev/null", O_RDONLY, 0)) == -1) {
343 perror("/dev/null");
344 break;
345 }
346
347 if ((fbuf = Fopen(tempEdit, "w+")) == NULL) {
348 perror(tempEdit);
349 break;
350 }
351 (void) unlink(tempEdit);
352
353 if ((shell = value("SHELL")) == NOSTR)
354 shell = _PATH_CSHELL;
355
356 rc = run_command(shell, 0, nullfd, fileno(fbuf), "-c", cp+1, NOSTR);
357
358 close(nullfd);
359
360 if (rc < 0) {
361 (void) Fclose(fbuf);
362 break;
363 }
364
365 if (fsize(fbuf) == 0) {
366 fprintf(stderr, "No bytes from command \"%s\"\n", cp+1);
367 (void) Fclose(fbuf);
368 break;
369 }
370
371 rewind(fbuf);
372 }
373 else if (isdir(cp)) {
374 printf("%s: Directory\n", cp);
375 break;
376 }
377 else if ((fbuf = Fopen(cp, "r")) == NULL) {
378 perror(cp);
379 break;
380 }
381 printf("\"%s\" ", cp);
382 fflush(stdout);
383 lc = 0;
384 cc = 0;
385 while ((rc = readline(fbuf, linebuf, LINESIZE)) >= 0) {
386 if (rc != LINESIZE-1) lc++;
387 if ((t = putline(collf, linebuf,
388 rc != LINESIZE-1)) < 0) {
389 Fclose(fbuf);
390 goto err;
391 }
392 cc += t;
393 }
394 Fclose(fbuf);
395 printf("%d/%d\n", lc, cc);
396 break;
397 case 'w':
398 /*
399 * Write the message on a file.
400 */
401 cp = &linebuf[2];
402 while (*cp == ' ' || *cp == '\t')
403 cp++;
404 if (*cp == '\0') {
405 fprintf(stderr, "Write what file!?\n");
406 break;
407 }
408 if ((cp = expand(cp)) == NOSTR)
409 break;
410 rewind(collf);
411 exwrite(cp, collf, 1);
412 break;
413 case 'm':
414 case 'M':
415 case 'f':
416 case 'F':
417 /*
418 * Interpolate the named messages, if we
419 * are in receiving mail mode. Does the
420 * standard list processing garbage.
421 * If ~f is given, we don't shift over.
422 */
423 if (forward(linebuf + 2, collf, c) < 0)
424 goto err;
425 goto cont;
426 case '?':
427 if ((fbuf = Fopen(_PATH_TILDE, "r")) == NULL) {
428 perror(_PATH_TILDE);
429 break;
430 }
431 while ((t = getc(fbuf)) != EOF)
432 (void) putchar(t);
433 Fclose(fbuf);
434 break;
435 case 'p':
436 /*
437 * Print out the current state of the
438 * message without altering anything.
439 */
440 rewind(collf);
441 printf("-------\nMessage contains:\n");
442 puthead(hp, stdout, GTO|GSUBJECT|GCC|GBCC|GNL);
443 while ((t = getc(collf)) != EOF)
444 (void) putchar(t);
445 goto cont;
446 case '|':
447 /*
448 * Pipe message through command.
449 * Collect output as new message.
450 */
451 rewind(collf);
452 mespipe(collf, &linebuf[2]);
453 goto cont;
454 case 'v':
455 case 'e':
456 /*
457 * Edit the current message.
458 * 'e' means to use EDITOR
459 * 'v' means to use VISUAL
460 */
461 rewind(collf);
462 mesedit(collf, c);
463 goto cont;
464 }
465 }
466 goto out;
467 err:
468 if (collf != NULL) {
469 Fclose(collf);
470 collf = NULL;
471 }
472 out:
473 if (collf != NULL)
474 rewind(collf);
475 noreset--;
476 sigprocmask(SIG_BLOCK, &nset, NULL);
477 signal(SIGINT, saveint);
478 signal(SIGHUP, savehup);
479 signal(SIGTSTP, savetstp);
480 signal(SIGTTOU, savettou);
481 signal(SIGTTIN, savettin);
482 sigprocmask(SIG_UNBLOCK, &nset, NULL);
483 return collf;
484 }
485
486 /*
487 * Write a file, ex-like if f set.
488 */
489 int
490 exwrite(name, fp, f)
491 char name[];
492 FILE *fp;
493 int f;
494 {
495 FILE *of;
496 int c;
497 long cc;
498 int lc;
499 struct stat junk;
500
501 if (f) {
502 printf("\"%s\" ", name);
503 fflush(stdout);
504 }
505 if (stat(name, &junk) >= 0 && S_ISREG(junk.st_mode)) {
506 if (!f)
507 fprintf(stderr, "%s: ", name);
508 fprintf(stderr, "File exists\n");
509 return(-1);
510 }
511 if ((of = Fopen(name, "w")) == NULL) {
512 perror(name);
513 return(-1);
514 }
515 lc = 0;
516 cc = 0;
517 while ((c = getc(fp)) != EOF) {
518 cc++;
519 if (c == '\n')
520 lc++;
521 (void) putc(c, of);
522 if (ferror(of)) {
523 perror(name);
524 Fclose(of);
525 return(-1);
526 }
527 }
528 Fclose(of);
529 printf("%d/%ld\n", lc, cc);
530 fflush(stdout);
531 return(0);
532 }
533
534 /*
535 * Edit the message being collected on fp.
536 * On return, make the edit file the new temp file.
537 */
538 void
539 mesedit(fp, c)
540 FILE *fp;
541 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 fseek(nf, 0L, 2);
548 collf = nf;
549 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(fp, cmd)
562 FILE *fp;
563 char cmd[];
564 {
565 FILE *nf;
566 sig_t sigint = signal(SIGINT, SIG_IGN);
567 extern char *tempEdit;
568 char *shell;
569
570 if ((nf = Fopen(tempEdit, "w+")) == NULL) {
571 perror(tempEdit);
572 goto out;
573 }
574 (void) unlink(tempEdit);
575 /*
576 * stdin = current message.
577 * stdout = new message.
578 */
579 if ((shell = value("SHELL")) == NOSTR)
580 shell = _PATH_CSHELL;
581 if (run_command(shell,
582 0, fileno(fp), fileno(nf), "-c", cmd, NOSTR) < 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) fseek(nf, 0L, 2);
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(ms, fp, f)
611 char ms[];
612 FILE *fp;
613 int f;
614 {
615 int *msgvec;
616 extern char *tempMail;
617 struct ignoretab *ig;
618 char *tabst;
619
620 msgvec = (int *) salloc((msgCount+1) * sizeof *msgvec);
621 if (msgvec == (int *) NOSTR)
622 return(0);
623 if (getmsglist(ms, msgvec, 0) < 0)
624 return(0);
625 if (*msgvec == 0) {
626 *msgvec = first(0, MMNORM);
627 if (*msgvec == 0) {
628 printf("No appropriate messages\n");
629 return(0);
630 }
631 msgvec[1] = 0;
632 }
633 if (f == 'f' || f == 'F')
634 tabst = NOSTR;
635 else if ((tabst = value("indentprefix")) == NOSTR)
636 tabst = "\t";
637 ig = isupper(f) ? NULL : ignore;
638 printf("Interpolating:");
639 for (; *msgvec != 0; msgvec++) {
640 struct message *mp = message + *msgvec - 1;
641
642 touch(mp);
643 printf(" %d", *msgvec);
644 if (send(mp, fp, ig, tabst) < 0) {
645 perror(tempMail);
646 return(-1);
647 }
648 }
649 printf("\n");
650 return(0);
651 }
652
653 /*
654 * Print (continue) when continued after ^Z.
655 */
656 /*ARGSUSED*/
657 void
658 collstop(s)
659 int s;
660 {
661 sig_t old_action = signal(s, SIG_DFL);
662 sigset_t nset;
663
664 sigemptyset(&nset);
665 sigaddset(&nset, s);
666 sigprocmask(SIG_UNBLOCK, &nset, NULL);
667 kill(0, s);
668 sigprocmask(SIG_BLOCK, &nset, NULL);
669 signal(s, old_action);
670 if (colljmp_p) {
671 colljmp_p = 0;
672 hadintr = 0;
673 longjmp(colljmp, 1);
674 }
675 }
676
677 /*
678 * On interrupt, come here to save the partial message in ~/dead.letter.
679 * Then jump out of the collection loop.
680 */
681 /*ARGSUSED*/
682 void
683 collint(s)
684 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") != NOSTR) {
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") == NOSTR)
701 savedeadletter(collf);
702 longjmp(collabort, 1);
703 }
704
705 /*ARGSUSED*/
706 void
707 collhup(s)
708 int s;
709 {
710 rewind(collf);
711 savedeadletter(collf);
712 /*
713 * Let's pretend nobody else wants to clean up,
714 * a true statement at this time.
715 */
716 exit(1);
717 }
718
719 void
720 savedeadletter(fp)
721 FILE *fp;
722 {
723 FILE *dbuf;
724 int c;
725 char *cp;
726
727 if (fsize(fp) == 0)
728 return;
729 cp = getdeadletter();
730 c = umask(077);
731 dbuf = Fopen(cp, "a");
732 (void) umask(c);
733 if (dbuf == NULL)
734 return;
735 while ((c = getc(fp)) != EOF)
736 (void) putc(c, dbuf);
737 Fclose(dbuf);
738 rewind(fp);
739 }
740