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