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