collect.c revision 1.16 1 /* $NetBSD: collect.c,v 1.16 1997/10/31 22:21:37 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.16 1997/10/31 22:21:37 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 sigprocmask(SIG_BLOCK, &nset, NULL);
402 signal(SIGINT, saveint);
403 signal(SIGHUP, savehup);
404 signal(SIGTSTP, savetstp);
405 signal(SIGTTOU, savettou);
406 signal(SIGTTIN, savettin);
407 sigprocmask(SIG_UNBLOCK, &nset, NULL);
408 return collf;
409 }
410
411 /*
412 * Write a file, ex-like if f set.
413 */
414 int
415 exwrite(name, fp, f)
416 char name[];
417 FILE *fp;
418 int f;
419 {
420 FILE *of;
421 int c;
422 long cc;
423 int lc;
424 struct stat junk;
425
426 if (f) {
427 printf("\"%s\" ", name);
428 fflush(stdout);
429 }
430 if (stat(name, &junk) >= 0 && S_ISREG(junk.st_mode)) {
431 if (!f)
432 fprintf(stderr, "%s: ", name);
433 fprintf(stderr, "File exists\n");
434 return(-1);
435 }
436 if ((of = Fopen(name, "w")) == NULL) {
437 perror(NOSTR);
438 return(-1);
439 }
440 lc = 0;
441 cc = 0;
442 while ((c = getc(fp)) != EOF) {
443 cc++;
444 if (c == '\n')
445 lc++;
446 (void) putc(c, of);
447 if (ferror(of)) {
448 perror(name);
449 Fclose(of);
450 return(-1);
451 }
452 }
453 Fclose(of);
454 printf("%d/%ld\n", lc, cc);
455 fflush(stdout);
456 return(0);
457 }
458
459 /*
460 * Edit the message being collected on fp.
461 * On return, make the edit file the new temp file.
462 */
463 void
464 mesedit(fp, c)
465 FILE *fp;
466 int c;
467 {
468 sig_t sigint = signal(SIGINT, SIG_IGN);
469 FILE *nf = run_editor(fp, (off_t)-1, c, 0);
470
471 if (nf != NULL) {
472 fseek(nf, 0L, 2);
473 collf = nf;
474 Fclose(fp);
475 }
476 (void) signal(SIGINT, sigint);
477 }
478
479 /*
480 * Pipe the message through the command.
481 * Old message is on stdin of command;
482 * New message collected from stdout.
483 * Sh -c must return 0 to accept the new message.
484 */
485 void
486 mespipe(fp, cmd)
487 FILE *fp;
488 char cmd[];
489 {
490 FILE *nf;
491 sig_t sigint = signal(SIGINT, SIG_IGN);
492 extern char *tempEdit;
493 char *shell;
494
495 if ((nf = Fopen(tempEdit, "w+")) == NULL) {
496 perror(tempEdit);
497 goto out;
498 }
499 (void) unlink(tempEdit);
500 /*
501 * stdin = current message.
502 * stdout = new message.
503 */
504 if ((shell = value("SHELL")) == NOSTR)
505 shell = _PATH_CSHELL;
506 if (run_command(shell,
507 0, fileno(fp), fileno(nf), "-c", cmd, NOSTR) < 0) {
508 (void) Fclose(nf);
509 goto out;
510 }
511 if (fsize(nf) == 0) {
512 fprintf(stderr, "No bytes from \"%s\" !?\n", cmd);
513 (void) Fclose(nf);
514 goto out;
515 }
516 /*
517 * Take new files.
518 */
519 (void) fseek(nf, 0L, 2);
520 collf = nf;
521 (void) Fclose(fp);
522 out:
523 (void) signal(SIGINT, sigint);
524 }
525
526 /*
527 * Interpolate the named messages into the current
528 * message, preceding each line with a tab.
529 * Return a count of the number of characters now in
530 * the message, or -1 if an error is encountered writing
531 * the message temporary. The flag argument is 'm' if we
532 * should shift over and 'f' if not.
533 */
534 int
535 forward(ms, fp, f)
536 char ms[];
537 FILE *fp;
538 int f;
539 {
540 int *msgvec;
541 extern char *tempMail;
542 struct ignoretab *ig;
543 char *tabst;
544
545 msgvec = (int *) salloc((msgCount+1) * sizeof *msgvec);
546 if (msgvec == (int *) NOSTR)
547 return(0);
548 if (getmsglist(ms, msgvec, 0) < 0)
549 return(0);
550 if (*msgvec == 0) {
551 *msgvec = first(0, MMNORM);
552 if (*msgvec == 0) {
553 printf("No appropriate messages\n");
554 return(0);
555 }
556 msgvec[1] = 0;
557 }
558 if (f == 'f' || f == 'F')
559 tabst = NOSTR;
560 else if ((tabst = value("indentprefix")) == NOSTR)
561 tabst = "\t";
562 ig = isupper(f) ? NULL : ignore;
563 printf("Interpolating:");
564 for (; *msgvec != 0; msgvec++) {
565 struct message *mp = message + *msgvec - 1;
566
567 touch(mp);
568 printf(" %d", *msgvec);
569 if (send(mp, fp, ig, tabst) < 0) {
570 perror(tempMail);
571 return(-1);
572 }
573 }
574 printf("\n");
575 return(0);
576 }
577
578 /*
579 * Print (continue) when continued after ^Z.
580 */
581 /*ARGSUSED*/
582 void
583 collstop(s)
584 int s;
585 {
586 sig_t old_action = signal(s, SIG_DFL);
587 sigset_t nset;
588
589 sigemptyset(&nset);
590 sigaddset(&nset, s);
591 sigprocmask(SIG_UNBLOCK, &nset, NULL);
592 kill(0, s);
593 sigprocmask(SIG_BLOCK, &nset, NULL);
594 signal(s, old_action);
595 if (colljmp_p) {
596 colljmp_p = 0;
597 hadintr = 0;
598 longjmp(colljmp, 1);
599 }
600 }
601
602 /*
603 * On interrupt, come here to save the partial message in ~/dead.letter.
604 * Then jump out of the collection loop.
605 */
606 /*ARGSUSED*/
607 void
608 collint(s)
609 int s;
610 {
611 /*
612 * the control flow is subtle, because we can be called from ~q.
613 */
614 if (!hadintr) {
615 if (value("ignore") != NOSTR) {
616 puts("@");
617 fflush(stdout);
618 clearerr(stdin);
619 return;
620 }
621 hadintr = 1;
622 longjmp(colljmp, 1);
623 }
624 rewind(collf);
625 if (value("nosave") == NOSTR)
626 savedeadletter(collf);
627 longjmp(collabort, 1);
628 }
629
630 /*ARGSUSED*/
631 void
632 collhup(s)
633 int s;
634 {
635 rewind(collf);
636 savedeadletter(collf);
637 /*
638 * Let's pretend nobody else wants to clean up,
639 * a true statement at this time.
640 */
641 exit(1);
642 }
643
644 void
645 savedeadletter(fp)
646 FILE *fp;
647 {
648 FILE *dbuf;
649 int c;
650 char *cp;
651
652 if (fsize(fp) == 0)
653 return;
654 cp = getdeadletter();
655 c = umask(077);
656 dbuf = Fopen(cp, "a");
657 (void) umask(c);
658 if (dbuf == NULL)
659 return;
660 while ((c = getc(fp)) != EOF)
661 (void) putc(c, dbuf);
662 Fclose(dbuf);
663 rewind(fp);
664 }
665