collect.c revision 1.15 1 /* $NetBSD: collect.c,v 1.15 1997/10/31 22:18:05 mycroft 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.15 1997/10/31 22:18:05 mycroft 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 case 'h':
246 /*
247 * Grab a bunch of headers.
248 */
249 grabh(hp, GTO|GSUBJECT|GCC|GBCC);
250 goto cont;
251 case 't':
252 /*
253 * Add to the To list.
254 */
255 hp->h_to = cat(hp->h_to, extract(&linebuf[2], GTO));
256 break;
257 case 's':
258 /*
259 * Set the Subject list.
260 */
261 cp = &linebuf[2];
262 while (isspace(*cp))
263 cp++;
264 hp->h_subject = savestr(cp);
265 break;
266 case 'c':
267 /*
268 * Add to the CC list.
269 */
270 hp->h_cc = cat(hp->h_cc, extract(&linebuf[2], GCC));
271 break;
272 case 'b':
273 /*
274 * Add stuff to blind carbon copies list.
275 */
276 hp->h_bcc = cat(hp->h_bcc, extract(&linebuf[2], GBCC));
277 break;
278 case 'd':
279 strcpy(linebuf + 2, getdeadletter());
280 /* fall into . . . */
281 case 'r':
282 case '<':
283 /*
284 * Invoke a file:
285 * Search for the file name,
286 * then open it and copy the contents to collf.
287 */
288 cp = &linebuf[2];
289 while (isspace(*cp))
290 cp++;
291 if (*cp == '\0') {
292 printf("Interpolate what file?\n");
293 break;
294 }
295 cp = expand(cp);
296 if (cp == NOSTR)
297 break;
298 if (isdir(cp)) {
299 printf("%s: Directory\n", cp);
300 break;
301 }
302 if ((fbuf = Fopen(cp, "r")) == NULL) {
303 perror(cp);
304 break;
305 }
306 printf("\"%s\" ", cp);
307 fflush(stdout);
308 lc = 0;
309 cc = 0;
310 while ((rc = readline(fbuf, linebuf, LINESIZE)) >= 0) {
311 if (rc != LINESIZE-1) lc++;
312 if ((t = putline(collf, linebuf,
313 rc != LINESIZE-1)) < 0) {
314 Fclose(fbuf);
315 goto err;
316 }
317 cc += t;
318 }
319 Fclose(fbuf);
320 printf("%d/%d\n", lc, cc);
321 break;
322 case 'w':
323 /*
324 * Write the message on a file.
325 */
326 cp = &linebuf[2];
327 while (*cp == ' ' || *cp == '\t')
328 cp++;
329 if (*cp == '\0') {
330 fprintf(stderr, "Write what file!?\n");
331 break;
332 }
333 if ((cp = expand(cp)) == NOSTR)
334 break;
335 rewind(collf);
336 exwrite(cp, collf, 1);
337 break;
338 case 'm':
339 case 'M':
340 case 'f':
341 case 'F':
342 /*
343 * Interpolate the named messages, if we
344 * are in receiving mail mode. Does the
345 * standard list processing garbage.
346 * If ~f is given, we don't shift over.
347 */
348 if (forward(linebuf + 2, collf, c) < 0)
349 goto err;
350 goto cont;
351 case '?':
352 if ((fbuf = Fopen(_PATH_TILDE, "r")) == NULL) {
353 perror(_PATH_TILDE);
354 break;
355 }
356 while ((t = getc(fbuf)) != EOF)
357 (void) putchar(t);
358 Fclose(fbuf);
359 break;
360 case 'p':
361 /*
362 * Print out the current state of the
363 * message without altering anything.
364 */
365 rewind(collf);
366 printf("-------\nMessage contains:\n");
367 puthead(hp, stdout, GTO|GSUBJECT|GCC|GBCC|GNL);
368 while ((t = getc(collf)) != EOF)
369 (void) putchar(t);
370 goto cont;
371 case '|':
372 /*
373 * Pipe message through command.
374 * Collect output as new message.
375 */
376 rewind(collf);
377 mespipe(collf, &linebuf[2]);
378 goto cont;
379 case 'v':
380 case 'e':
381 /*
382 * Edit the current message.
383 * 'e' means to use EDITOR
384 * 'v' means to use VISUAL
385 */
386 rewind(collf);
387 mesedit(collf, c);
388 goto cont;
389 }
390 }
391 goto out;
392 err:
393 if (collf != NULL) {
394 Fclose(collf);
395 collf = NULL;
396 }
397 out:
398 if (collf != NULL)
399 rewind(collf);
400 noreset--;
401 sigemptyset(&nset);
402 sigaddset(&nset, SIGINT);
403 sigaddset(&nset, SIGHUP);
404 sigprocmask(SIG_BLOCK, &nset, &oset);
405 signal(SIGINT, saveint);
406 signal(SIGHUP, savehup);
407 signal(SIGTSTP, savetstp);
408 signal(SIGTTOU, savettou);
409 signal(SIGTTIN, savettin);
410 sigprocmask(SIG_SETMASK, &oset, NULL);
411 return collf;
412 }
413
414 /*
415 * Write a file, ex-like if f set.
416 */
417 int
418 exwrite(name, fp, f)
419 char name[];
420 FILE *fp;
421 int f;
422 {
423 FILE *of;
424 int c;
425 long cc;
426 int lc;
427 struct stat junk;
428
429 if (f) {
430 printf("\"%s\" ", name);
431 fflush(stdout);
432 }
433 if (stat(name, &junk) >= 0 && S_ISREG(junk.st_mode)) {
434 if (!f)
435 fprintf(stderr, "%s: ", name);
436 fprintf(stderr, "File exists\n");
437 return(-1);
438 }
439 if ((of = Fopen(name, "w")) == NULL) {
440 perror(NOSTR);
441 return(-1);
442 }
443 lc = 0;
444 cc = 0;
445 while ((c = getc(fp)) != EOF) {
446 cc++;
447 if (c == '\n')
448 lc++;
449 (void) putc(c, of);
450 if (ferror(of)) {
451 perror(name);
452 Fclose(of);
453 return(-1);
454 }
455 }
456 Fclose(of);
457 printf("%d/%ld\n", lc, cc);
458 fflush(stdout);
459 return(0);
460 }
461
462 /*
463 * Edit the message being collected on fp.
464 * On return, make the edit file the new temp file.
465 */
466 void
467 mesedit(fp, c)
468 FILE *fp;
469 int c;
470 {
471 sig_t sigint = signal(SIGINT, SIG_IGN);
472 FILE *nf = run_editor(fp, (off_t)-1, c, 0);
473
474 if (nf != NULL) {
475 fseek(nf, 0L, 2);
476 collf = nf;
477 Fclose(fp);
478 }
479 (void) signal(SIGINT, sigint);
480 }
481
482 /*
483 * Pipe the message through the command.
484 * Old message is on stdin of command;
485 * New message collected from stdout.
486 * Sh -c must return 0 to accept the new message.
487 */
488 void
489 mespipe(fp, cmd)
490 FILE *fp;
491 char cmd[];
492 {
493 FILE *nf;
494 sig_t sigint = signal(SIGINT, SIG_IGN);
495 extern char *tempEdit;
496 char *shell;
497
498 if ((nf = Fopen(tempEdit, "w+")) == NULL) {
499 perror(tempEdit);
500 goto out;
501 }
502 (void) unlink(tempEdit);
503 /*
504 * stdin = current message.
505 * stdout = new message.
506 */
507 if ((shell = value("SHELL")) == NOSTR)
508 shell = _PATH_CSHELL;
509 if (run_command(shell,
510 0, fileno(fp), fileno(nf), "-c", cmd, NOSTR) < 0) {
511 (void) Fclose(nf);
512 goto out;
513 }
514 if (fsize(nf) == 0) {
515 fprintf(stderr, "No bytes from \"%s\" !?\n", cmd);
516 (void) Fclose(nf);
517 goto out;
518 }
519 /*
520 * Take new files.
521 */
522 (void) fseek(nf, 0L, 2);
523 collf = nf;
524 (void) Fclose(fp);
525 out:
526 (void) signal(SIGINT, sigint);
527 }
528
529 /*
530 * Interpolate the named messages into the current
531 * message, preceding each line with a tab.
532 * Return a count of the number of characters now in
533 * the message, or -1 if an error is encountered writing
534 * the message temporary. The flag argument is 'm' if we
535 * should shift over and 'f' if not.
536 */
537 int
538 forward(ms, fp, f)
539 char ms[];
540 FILE *fp;
541 int f;
542 {
543 int *msgvec;
544 extern char *tempMail;
545 struct ignoretab *ig;
546 char *tabst;
547
548 msgvec = (int *) salloc((msgCount+1) * sizeof *msgvec);
549 if (msgvec == (int *) NOSTR)
550 return(0);
551 if (getmsglist(ms, msgvec, 0) < 0)
552 return(0);
553 if (*msgvec == 0) {
554 *msgvec = first(0, MMNORM);
555 if (*msgvec == 0) {
556 printf("No appropriate messages\n");
557 return(0);
558 }
559 msgvec[1] = 0;
560 }
561 if (f == 'f' || f == 'F')
562 tabst = NOSTR;
563 else if ((tabst = value("indentprefix")) == NOSTR)
564 tabst = "\t";
565 ig = isupper(f) ? NULL : ignore;
566 printf("Interpolating:");
567 for (; *msgvec != 0; msgvec++) {
568 struct message *mp = message + *msgvec - 1;
569
570 touch(mp);
571 printf(" %d", *msgvec);
572 if (send(mp, fp, ig, tabst) < 0) {
573 perror(tempMail);
574 return(-1);
575 }
576 }
577 printf("\n");
578 return(0);
579 }
580
581 /*
582 * Print (continue) when continued after ^Z.
583 */
584 /*ARGSUSED*/
585 void
586 collstop(s)
587 int s;
588 {
589 sig_t old_action = signal(s, SIG_DFL);
590 sigset_t nset;
591
592 sigemptyset(&nset);
593 sigaddset(&nset, s);
594 sigprocmask(SIG_UNBLOCK, &nset, NULL);
595 kill(0, s);
596 sigprocmask(SIG_BLOCK, &nset, NULL);
597 signal(s, old_action);
598 if (colljmp_p) {
599 colljmp_p = 0;
600 hadintr = 0;
601 longjmp(colljmp, 1);
602 }
603 }
604
605 /*
606 * On interrupt, come here to save the partial message in ~/dead.letter.
607 * Then jump out of the collection loop.
608 */
609 /*ARGSUSED*/
610 void
611 collint(s)
612 int s;
613 {
614 /*
615 * the control flow is subtle, because we can be called from ~q.
616 */
617 if (!hadintr) {
618 if (value("ignore") != NOSTR) {
619 puts("@");
620 fflush(stdout);
621 clearerr(stdin);
622 return;
623 }
624 hadintr = 1;
625 longjmp(colljmp, 1);
626 }
627 rewind(collf);
628 if (value("nosave") == NOSTR)
629 savedeadletter(collf);
630 longjmp(collabort, 1);
631 }
632
633 /*ARGSUSED*/
634 void
635 collhup(s)
636 int s;
637 {
638 rewind(collf);
639 savedeadletter(collf);
640 /*
641 * Let's pretend nobody else wants to clean up,
642 * a true statement at this time.
643 */
644 exit(1);
645 }
646
647 void
648 savedeadletter(fp)
649 FILE *fp;
650 {
651 FILE *dbuf;
652 int c;
653 char *cp;
654
655 if (fsize(fp) == 0)
656 return;
657 cp = getdeadletter();
658 c = umask(077);
659 dbuf = Fopen(cp, "a");
660 (void) umask(c);
661 if (dbuf == NULL)
662 return;
663 while ((c = getc(fp)) != EOF)
664 (void) putc(c, dbuf);
665 Fclose(dbuf);
666 rewind(fp);
667 }
668