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