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