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