new template
[unix-history] / usr / src / usr.bin / mail / collect.c
CommitLineData
9552e6b8
DF
1/*
2 * Copyright (c) 1980 Regents of the University of California.
3 * All rights reserved. The Berkeley software License Agreement
4 * specifies the terms and conditions for redistribution.
5 */
6
2ae9f53f 7#ifndef lint
828615a1 8static char *sccsid = "@(#)collect.c 5.3 (Berkeley) %G%";
9552e6b8 9#endif not lint
06300dd9
KS
10
11/*
12 * Mail -- a mail program
13 *
14 * Collect input from standard input, handling
15 * ~ escapes.
16 */
17
06300dd9
KS
18#include "rcv.h"
19#include <sys/stat.h>
828615a1 20#include <sys/wait.h>
06300dd9
KS
21
22/*
23 * Read a message from standard output and return a read file to it
24 * or NULL on error.
25 */
26
27/*
28 * The following hokiness with global variables is so that on
29 * receipt of an interrupt signal, the partial message can be salted
828615a1 30 * away on dead.letter.
06300dd9
KS
31 */
32
828615a1 33static int (*saveint)(); /* Previous SIGINT value */
7feecf47 34static int (*savehup)(); /* Previous SIGHUP value */
2d55c063 35static int (*savecont)(); /* Previous SIGCONT value */
828615a1 36static FILE *collf; /* File for saving away */
06300dd9
KS
37static int hadintr; /* Have seen one SIGINT so far */
38
39static jmp_buf coljmp; /* To get back to work */
40
41FILE *
42collect(hp)
43 struct header *hp;
44{
828615a1
EW
45 FILE *fp, *fbuf;
46 int lc, cc, escape, eof;
47 int collrub(), intack(), collcont();
06300dd9
KS
48 register int c, t;
49 char linebuf[LINESIZE], *cp;
50 extern char tempMail[];
97bff179 51 int notify();
4e161d3b 52 char getsub;
828615a1 53 int omask;
06300dd9
KS
54
55 noreset++;
828615a1
EW
56 fp = NULL;
57 collf = NULL;
58
59 /*
60 * Start catching signals from here, but we're still die on interrupts
61 * until we're in the main loop.
62 */
63 omask = sigblock(sigmask(SIGINT) | sigmask(SIGHUP));
64 if ((saveint = signal(SIGINT, SIG_IGN)) != SIG_IGN)
65 signal(SIGINT, value("ignore") != NOSTR ? intack : collrub);
66 if ((savehup = signal(SIGHUP, SIG_IGN)) != SIG_IGN)
67 signal(SIGHUP, collrub);
68 savecont = signal(SIGCONT, SIG_DFL);
69 if (setjmp(coljmp)) {
70 remove(tempMail);
06300dd9
KS
71 goto err;
72 }
828615a1
EW
73 sigsetmask(omask & ~(sigmask(SIGINT) | sigmask(SIGHUP)));
74
75 if ((fp = fopen(tempMail, "w+")) == NULL) {
06300dd9 76 perror(tempMail);
06300dd9
KS
77 goto err;
78 }
828615a1 79 collf = fp;
06300dd9
KS
80 remove(tempMail);
81
82 /*
83 * If we are going to prompt for a subject,
84 * refrain from printing a newline after
85 * the headers (since some people mind).
86 */
06300dd9 87 t = GTO|GSUBJECT|GCC|GNL;
4e161d3b 88 getsub = 0;
06300dd9 89 if (intty && sflag == NOSTR && hp->h_subject == NOSTR && value("ask"))
4e161d3b 90 t &= ~GNL, getsub++;
06300dd9
KS
91 if (hp->h_seq != 0) {
92 puthead(hp, stdout, t);
93 fflush(stdout);
94 }
06300dd9
KS
95 escape = ESCAPE;
96 if ((cp = value("escape")) != NOSTR)
97 escape = *cp;
0fa68535 98 eof = 0;
828615a1
EW
99 hadintr = 0;
100
101 /*
102 * We can put the setjmp here because register variable
103 * needs to be saved in the loop.
104 */
105 if (!setjmp(coljmp)) {
106 signal(SIGCONT, collcont);
107 if (getsub)
4e161d3b 108 grabh(hp, GSUBJECT);
828615a1
EW
109 } else {
110 /*
111 * Come here for printing the after-signal message.
112 * Duplicate messages won't be printed because
113 * the write is aborted if we get a SIGTTOU.
114 */
115cont:
116 if (hadintr) {
117 fflush(stdout);
118 fprintf(stderr,
119 "\n(Interrupt -- one more to kill letter)\n");
120 } else {
121 printf("(continue)\n");
122 fflush(stdout);
4e161d3b 123 }
828615a1
EW
124 }
125 for (;;) {
126 if (readline(stdin, linebuf) < 0) {
0fa68535
KS
127 if (intty && value("ignoreeof") != NOSTR) {
128 if (++eof > 35)
129 break;
828615a1 130 printf("Use \".\" to terminate letter\n");
0fa68535
KS
131 continue;
132 }
06300dd9 133 break;
0fa68535
KS
134 }
135 eof = 0;
06300dd9 136 hadintr = 0;
b068a67e
KS
137 if (intty && equal(".", linebuf) &&
138 (value("dot") != NOSTR || value("ignoreeof") != NOSTR))
06300dd9 139 break;
3dffb9c8 140 if (linebuf[0] != escape || rflag != NOSTR) {
828615a1 141 if (putline(fp, linebuf) < 0)
06300dd9
KS
142 goto err;
143 continue;
144 }
145 c = linebuf[1];
06300dd9
KS
146 switch (c) {
147 default:
148 /*
149 * On double escape, just send the single one.
150 * Otherwise, it's an error.
151 */
152
153 if (c == escape) {
828615a1 154 if (putline(fp, &linebuf[1]) < 0)
06300dd9
KS
155 goto err;
156 else
157 break;
158 }
159 printf("Unknown tilde escape.\n");
160 break;
161
162 case 'C':
163 /*
164 * Dump core.
165 */
166
167 core();
168 break;
169
170 case '!':
171 /*
172 * Shell escape, send the balance of the
173 * line to sh -c.
174 */
175
176 shell(&linebuf[2]);
177 break;
178
179 case ':':
180 case '_':
181 /*
182 * Escape to command mode, but be nice!
183 */
184
343c874e 185 execute(&linebuf[2], 1);
828615a1 186 goto cont;
06300dd9
KS
187
188 case '.':
189 /*
190 * Simulate end of file on input.
191 */
828615a1 192 goto out;
06300dd9
KS
193
194 case 'q':
195 case 'Q':
196 /*
197 * Force a quit of sending mail.
198 * Act like an interrupt happened.
199 */
200
06300dd9
KS
201 hadintr++;
202 collrub(SIGINT);
203 exit(1);
204
205 case 'h':
206 /*
207 * Grab a bunch of headers.
208 */
209 if (!intty || !outtty) {
210 printf("~h: no can do!?\n");
211 break;
212 }
213 grabh(hp, GTO|GSUBJECT|GCC|GBCC);
828615a1 214 goto cont;
06300dd9
KS
215
216 case 't':
217 /*
218 * Add to the To list.
219 */
220
221 hp->h_to = addto(hp->h_to, &linebuf[2]);
222 hp->h_seq++;
223 break;
224
225 case 's':
226 /*
227 * Set the Subject list.
228 */
229
230 cp = &linebuf[2];
828615a1 231 while (isspace(*cp))
06300dd9
KS
232 cp++;
233 hp->h_subject = savestr(cp);
234 hp->h_seq++;
235 break;
236
237 case 'c':
238 /*
239 * Add to the CC list.
240 */
241
242 hp->h_cc = addto(hp->h_cc, &linebuf[2]);
243 hp->h_seq++;
244 break;
245
246 case 'b':
247 /*
248 * Add stuff to blind carbon copies list.
249 */
250 hp->h_bcc = addto(hp->h_bcc, &linebuf[2]);
251 hp->h_seq++;
252 break;
253
254 case 'd':
828615a1 255 strcpy(linebuf + 2, deadletter);
06300dd9
KS
256 /* fall into . . . */
257
258 case 'r':
259 /*
260 * Invoke a file:
261 * Search for the file name,
828615a1 262 * then open it and copy the contents to fp.
06300dd9
KS
263 */
264
265 cp = &linebuf[2];
828615a1 266 while (isspace(*cp))
06300dd9
KS
267 cp++;
268 if (*cp == '\0') {
269 printf("Interpolate what file?\n");
270 break;
271 }
272 cp = expand(cp);
273 if (cp == NOSTR)
274 break;
275 if (isdir(cp)) {
828615a1 276 printf("%s: Directory\n", cp);
06300dd9
KS
277 break;
278 }
279 if ((fbuf = fopen(cp, "r")) == NULL) {
280 perror(cp);
281 break;
282 }
283 printf("\"%s\" ", cp);
80187484 284 fflush(stdout);
06300dd9
KS
285 lc = 0;
286 cc = 0;
828615a1 287 while (readline(fbuf, linebuf) >= 0) {
06300dd9 288 lc++;
828615a1 289 if ((t = putline(fp, linebuf)) < 0) {
06300dd9
KS
290 fclose(fbuf);
291 goto err;
292 }
293 cc += t;
294 }
295 fclose(fbuf);
296 printf("%d/%d\n", lc, cc);
297 break;
298
299 case 'w':
300 /*
301 * Write the message on a file.
302 */
303
304 cp = &linebuf[2];
305 while (any(*cp, " \t"))
306 cp++;
307 if (*cp == '\0') {
308 fprintf(stderr, "Write what file!?\n");
309 break;
310 }
311 if ((cp = expand(cp)) == NOSTR)
312 break;
828615a1
EW
313 rewind(fp);
314 exwrite(cp, fp, 1);
06300dd9
KS
315 break;
316
317 case 'm':
318 case 'f':
319 /*
320 * Interpolate the named messages, if we
321 * are in receiving mail mode. Does the
322 * standard list processing garbage.
323 * If ~f is given, we don't shift over.
324 */
325
326 if (!rcvmode) {
327 printf("No messages to send from!?!\n");
328 break;
329 }
330 cp = &linebuf[2];
331 while (any(*cp, " \t"))
332 cp++;
828615a1 333 if (forward(cp, fp, c) < 0)
06300dd9 334 goto err;
828615a1 335 goto cont;
06300dd9
KS
336
337 case '?':
06300dd9 338 if ((fbuf = fopen(THELPFILE, "r")) == NULL) {
e5cd0021 339 perror(THELPFILE);
06300dd9
KS
340 break;
341 }
828615a1 342 while ((t = getc(fp)) != EOF)
06300dd9 343 putchar(t);
06300dd9
KS
344 fclose(fbuf);
345 break;
346
347 case 'p':
348 /*
349 * Print out the current state of the
350 * message without altering anything.
351 */
352
828615a1 353 rewind(fp);
06300dd9
KS
354 printf("-------\nMessage contains:\n");
355 puthead(hp, stdout, GTO|GSUBJECT|GCC|GBCC|GNL);
828615a1 356 while ((t = getc(fp)) != EOF)
06300dd9 357 putchar(t);
828615a1 358 goto cont;
06300dd9
KS
359
360 case '^':
361 case '|':
362 /*
363 * Pipe message through command.
364 * Collect output as new message.
365 */
366
828615a1
EW
367 rewind(fp);
368 fp = mespipe(fp, &linebuf[2]);
369 goto cont;
06300dd9
KS
370
371 case 'v':
372 case 'e':
373 /*
374 * Edit the current message.
375 * 'e' means to use EDITOR
376 * 'v' means to use VISUAL
377 */
378
828615a1
EW
379 rewind(fp);
380 if ((fp = mesedit(fp, c)) == NULL)
06300dd9 381 goto err;
828615a1 382 goto cont;
06300dd9
KS
383 }
384 }
828615a1 385 goto out;
06300dd9 386err:
828615a1
EW
387 if (fp != NULL) {
388 fclose(fp);
389 fp = NULL;
390 }
391out:
392 if (fp != NULL)
393 rewind(fp);
394 signal(SIGINT, saveint);
395 signal(SIGHUP, savehup);
396 signal(SIGCONT, savecont);
397 sigsetmask(omask);
06300dd9 398 noreset = 0;
828615a1 399 return(fp);
06300dd9
KS
400}
401
402/*
403 * Write a file, ex-like if f set.
404 */
405
828615a1 406exwrite(name, fp, f)
06300dd9 407 char name[];
828615a1 408 FILE *fp;
06300dd9
KS
409{
410 register FILE *of;
411 register int c;
412 long cc;
413 int lc;
414 struct stat junk;
415
416 if (f) {
417 printf("\"%s\" ", name);
418 fflush(stdout);
419 }
6d1cdf8d 420 if (stat(name, &junk) >= 0 && (junk.st_mode & S_IFMT) == S_IFREG) {
06300dd9
KS
421 if (!f)
422 fprintf(stderr, "%s: ", name);
828615a1 423 fprintf(stderr, "File exists\n");
06300dd9
KS
424 return(-1);
425 }
426 if ((of = fopen(name, "w")) == NULL) {
427 perror(NOSTR);
428 return(-1);
429 }
430 lc = 0;
431 cc = 0;
828615a1 432 while ((c = getc(fp)) != EOF) {
06300dd9
KS
433 cc++;
434 if (c == '\n')
435 lc++;
436 putc(c, of);
437 if (ferror(of)) {
438 perror(name);
439 fclose(of);
440 return(-1);
441 }
442 }
443 fclose(of);
444 printf("%d/%ld\n", lc, cc);
445 fflush(stdout);
446 return(0);
447}
448
449/*
828615a1 450 * Edit the message being collected on fp.
06300dd9
KS
451 * Write the message out onto some poorly-named temp file
452 * and point an editor at it.
453 *
454 * On return, make the edit file the new temp file.
455 */
456
457FILE *
828615a1
EW
458mesedit(fp, c)
459 FILE *fp;
06300dd9 460{
828615a1
EW
461 int pid;
462 union wait s;
06300dd9
KS
463 FILE *fbuf;
464 register int t;
828615a1 465 int (*sigint)(), (*sigcont)();
06300dd9
KS
466 struct stat sbuf;
467 extern char tempMail[], tempEdit[];
468 register char *edit;
469
828615a1
EW
470 sigint = signal(SIGINT, SIG_IGN);
471 sigcont = signal(SIGCONT, SIG_DFL);
06300dd9
KS
472 if (stat(tempEdit, &sbuf) >= 0) {
473 printf("%s: file exists\n", tempEdit);
474 goto out;
475 }
476 close(creat(tempEdit, 0600));
477 if ((fbuf = fopen(tempEdit, "w")) == NULL) {
478 perror(tempEdit);
479 goto out;
480 }
828615a1 481 while ((t = getc(fp)) != EOF)
06300dd9 482 putc(t, fbuf);
06300dd9
KS
483 fflush(fbuf);
484 if (ferror(fbuf)) {
485 perror(tempEdit);
486 remove(tempEdit);
487 goto fix;
488 }
489 fclose(fbuf);
490 if ((edit = value(c == 'e' ? "EDITOR" : "VISUAL")) == NOSTR)
491 edit = c == 'e' ? EDITOR : VISUAL;
492 pid = vfork();
493 if (pid == 0) {
828615a1
EW
494 if (sigint != SIG_IGN)
495 signal(SIGINT, SIG_DFL);
06300dd9
KS
496 execl(edit, edit, tempEdit, 0);
497 perror(edit);
498 _exit(1);
499 }
500 if (pid == -1) {
501 perror("fork");
502 remove(tempEdit);
503 goto out;
504 }
505 while (wait(&s) != pid)
506 ;
828615a1 507 if (s.w_status != 0) {
06300dd9
KS
508 printf("Fatal error in \"%s\"\n", edit);
509 remove(tempEdit);
510 goto out;
511 }
512
513 /*
514 * Now switch to new file.
515 */
516
828615a1 517 if ((fbuf = fopen(tempEdit, "a+")) == NULL) {
06300dd9
KS
518 perror(tempEdit);
519 remove(tempEdit);
520 goto out;
521 }
06300dd9 522 remove(tempEdit);
828615a1
EW
523 collf = fbuf;
524 fclose(fp);
525 fp = fbuf;
06300dd9
KS
526 goto out;
527fix:
528 perror(tempEdit);
529out:
828615a1
EW
530 signal(SIGCONT, sigcont);
531 signal(SIGINT, sigint);
532 return(fp);
06300dd9
KS
533}
534
535/*
536 * Pipe the message through the command.
537 * Old message is on stdin of command;
538 * New message collected from stdout.
539 * Sh -c must return 0 to accept the new message.
540 */
541
542FILE *
828615a1
EW
543mespipe(fp, cmd)
544 FILE *fp;
06300dd9
KS
545 char cmd[];
546{
828615a1
EW
547 register FILE *nf;
548 int pid;
549 union wait s;
550 int (*saveint)();
06300dd9
KS
551 char *Shell;
552
828615a1 553 if ((nf = fopen(tempEdit, "w+")) == NULL) {
06300dd9 554 perror(tempEdit);
828615a1 555 return(fp);
06300dd9
KS
556 }
557 remove(tempEdit);
828615a1 558 saveint = signal(SIGINT, SIG_IGN);
06300dd9
KS
559 if ((Shell = value("SHELL")) == NULL)
560 Shell = "/bin/sh";
561 if ((pid = vfork()) == -1) {
562 perror("fork");
563 goto err;
564 }
565 if (pid == 0) {
828615a1 566 int fd;
06300dd9
KS
567 /*
568 * stdin = current message.
569 * stdout = new message.
570 */
571
572 close(0);
828615a1 573 dup(fileno(fp));
06300dd9 574 close(1);
828615a1
EW
575 dup(fileno(nf));
576 for (fd = getdtablesize(); --fd > 2;)
577 close(fd);
06300dd9
KS
578 execl(Shell, Shell, "-c", cmd, 0);
579 perror(Shell);
580 _exit(1);
581 }
582 while (wait(&s) != pid)
583 ;
828615a1 584 if (s.w_status != 0 || pid == -1) {
06300dd9
KS
585 fprintf(stderr, "\"%s\" failed!?\n", cmd);
586 goto err;
587 }
828615a1 588 if (fsize(nf) == 0) {
06300dd9
KS
589 fprintf(stderr, "No bytes from \"%s\" !?\n", cmd);
590 goto err;
591 }
592
593 /*
594 * Take new files.
595 */
596
828615a1
EW
597 fseek(nf, 0L, 2);
598 collf = nf;
599 fclose(fp);
600 signal(SIGINT, saveint);
601 return(nf);
06300dd9
KS
602
603err:
828615a1
EW
604 fclose(nf);
605 signal(SIGINT, saveint);
606 return(fp);
06300dd9
KS
607}
608
609/*
610 * Interpolate the named messages into the current
611 * message, preceding each line with a tab.
612 * Return a count of the number of characters now in
613 * the message, or -1 if an error is encountered writing
614 * the message temporary. The flag argument is 'm' if we
615 * should shift over and 'f' if not.
616 */
828615a1 617forward(ms, fp, f)
06300dd9 618 char ms[];
828615a1 619 FILE *fp;
06300dd9
KS
620{
621 register int *msgvec, *ip;
622 extern char tempMail[];
623
624 msgvec = (int *) salloc((msgCount+1) * sizeof *msgvec);
625 if (msgvec == (int *) NOSTR)
626 return(0);
627 if (getmsglist(ms, msgvec, 0) < 0)
628 return(0);
629 if (*msgvec == NULL) {
630 *msgvec = first(0, MMNORM);
631 if (*msgvec == NULL) {
632 printf("No appropriate messages\n");
633 return(0);
634 }
635 msgvec[1] = NULL;
636 }
637 printf("Interpolating:");
638 for (ip = msgvec; *ip != NULL; ip++) {
639 touch(*ip);
640 printf(" %d", *ip);
641 if (f == 'm') {
828615a1 642 if (transmit(&message[*ip-1], fp) < 0L) {
06300dd9
KS
643 perror(tempMail);
644 return(-1);
645 }
646 } else
828615a1 647 if (send(&message[*ip-1], fp, 0) < 0) {
06300dd9
KS
648 perror(tempMail);
649 return(-1);
650 }
651 }
652 printf("\n");
653 return(0);
654}
655
656/*
657 * Send message described by the passed pointer to the
658 * passed output buffer. Insert a tab in front of each
659 * line. Return a count of the characters sent, or -1
660 * on error.
661 */
662
5deb0e1e 663long
828615a1 664transmit(mailp, fp)
06300dd9 665 struct message *mailp;
828615a1 666 FILE *fp;
06300dd9
KS
667{
668 register struct message *mp;
5deb0e1e
CS
669 register int ch;
670 long c, n;
671 int bol;
06300dd9
KS
672 FILE *ibuf;
673
674 mp = mailp;
675 ibuf = setinput(mp);
5deb0e1e 676 c = mp->m_size;
06300dd9
KS
677 n = c;
678 bol = 1;
5deb0e1e 679 while (c-- > 0L) {
828615a1
EW
680 ch = getc(ibuf);
681 if (ch == '\n')
682 bol = 1;
683 else if (bol) {
06300dd9 684 bol = 0;
828615a1 685 putc('\t', fp);
06300dd9 686 n++;
06300dd9 687 }
828615a1
EW
688 putc(ch, fp);
689 if (ferror(fp)) {
06300dd9 690 perror("/tmp");
5deb0e1e 691 return(-1L);
06300dd9
KS
692 }
693 }
694 return(n);
695}
696
2d55c063
KS
697/*
698 * Print (continue) when continued after ^Z.
699 */
828615a1 700/*ARGSUSED*/
2d55c063
KS
701collcont(s)
702{
703
828615a1
EW
704 hadintr = 0;
705 longjmp(coljmp, 1);
2d55c063
KS
706}
707
06300dd9
KS
708/*
709 * On interrupt, go here to save the partial
7feecf47 710 * message on ~/dead.letter.
06300dd9
KS
711 * Then restore signals and execute the normal
712 * signal routine. We only come here if signals
713 * were previously set anyway.
714 */
715
716collrub(s)
717{
718 register FILE *dbuf;
719 register int c;
720
7feecf47 721 if (s == SIGINT && hadintr == 0) {
828615a1 722 hadintr = 1;
06300dd9
KS
723 longjmp(coljmp, 1);
724 }
828615a1
EW
725 rewind(collf);
726 if (s == SIGINT && value("nosave") != NOSTR || fsize(collf) == 0)
06300dd9
KS
727 goto done;
728 if ((dbuf = fopen(deadletter, "w")) == NULL)
729 goto done;
730 chmod(deadletter, 0600);
828615a1 731 while ((c = getc(collf)) != EOF)
06300dd9
KS
732 putc(c, dbuf);
733 fclose(dbuf);
734
735done:
828615a1
EW
736 fclose(collf);
737 signal(SIGINT, saveint);
738 signal(SIGHUP, savehup);
739 signal(SIGCONT, savecont);
7feecf47
KS
740 if (rcvmode) {
741 if (s == SIGHUP)
742 hangup(SIGHUP);
743 else
2d55c063 744 stop(s);
828615a1 745 } else
06300dd9
KS
746 exit(1);
747}
748
749/*
750 * Acknowledge an interrupt signal from the tty by typing an @
751 */
752
828615a1 753/*ARGSUSED*/
06300dd9
KS
754intack(s)
755{
828615a1 756
06300dd9
KS
757 puts("@");
758 fflush(stdout);
759 clearerr(stdin);
06300dd9
KS
760}
761
762/*
763 * Add a string to the end of a header entry field.
764 */
765
766char *
767addto(hf, news)
768 char hf[], news[];
769{
770 register char *cp, *cp2, *linebuf;
771
772 if (hf == NOSTR)
773 hf = "";
774 if (*news == '\0')
775 return(hf);
776 linebuf = salloc(strlen(hf) + strlen(news) + 2);
777 for (cp = hf; any(*cp, " \t"); cp++)
778 ;
779 for (cp2 = linebuf; *cp;)
780 *cp2++ = *cp++;
781 *cp2++ = ' ';
782 for (cp = news; any(*cp, " \t"); cp++)
783 ;
784 while (*cp != '\0')
785 *cp2++ = *cp++;
786 *cp2 = '\0';
787 return(linebuf);
788}