1 /*-
2 * SPDX-License-Identifier: BSD-3-Clause
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. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 /*
33 * Mail -- a mail program
34 *
35 * Collect input from standard input, handling
36 * ~ escapes.
37 */
38
39 #include "rcv.h"
40 #include <fcntl.h>
41 #include "extern.h"
42
43 /*
44 * Read a message from standard input and return a read file to it
45 * or NULL on error.
46 */
47
48 /*
49 * The following hokiness with global variables is so that on
50 * receipt of an interrupt signal, the partial message can be salted
51 * away on dead.letter.
52 */
53
54 static sig_t saveint; /* Previous SIGINT value */
55 static sig_t savehup; /* Previous SIGHUP value */
56 static sig_t savetstp; /* Previous SIGTSTP value */
57 static sig_t savettou; /* Previous SIGTTOU value */
58 static sig_t savettin; /* Previous SIGTTIN value */
59 static FILE *collf; /* File for saving away */
60 static int hadintr; /* Have seen one SIGINT so far */
61
62 static jmp_buf colljmp; /* To get back to work */
63 static int colljmp_p; /* whether to long jump */
64 static jmp_buf collabort; /* To end collection with error */
65
66 FILE *
collect(struct header * hp,int printheaders)67 collect(struct header *hp, int printheaders)
68 {
69 FILE *fbuf;
70 int lc, cc, escape, eofcount, fd, c, t;
71 char linebuf[LINESIZE], tempname[PATHSIZE], *cp, getsub;
72 sigset_t nset;
73 int longline, lastlong, rc; /* So we don't make 2 or more lines
74 out of a long input line. */
75
76 collf = NULL;
77 /*
78 * Start catching signals from here, but we're still die on interrupts
79 * until we're in the main loop.
80 */
81 (void)sigemptyset(&nset);
82 (void)sigaddset(&nset, SIGINT);
83 (void)sigaddset(&nset, SIGHUP);
84 (void)sigprocmask(SIG_BLOCK, &nset, NULL);
85 if ((saveint = signal(SIGINT, SIG_IGN)) != SIG_IGN)
86 (void)signal(SIGINT, collint);
87 if ((savehup = signal(SIGHUP, SIG_IGN)) != SIG_IGN)
88 (void)signal(SIGHUP, collhup);
89 savetstp = signal(SIGTSTP, collstop);
90 savettou = signal(SIGTTOU, collstop);
91 savettin = signal(SIGTTIN, collstop);
92 if (setjmp(collabort) || setjmp(colljmp)) {
93 (void)rm(tempname);
94 goto err;
95 }
96 (void)sigprocmask(SIG_UNBLOCK, &nset, NULL);
97
98 noreset++;
99 (void)snprintf(tempname, sizeof(tempname),
100 "%s/mail.RsXXXXXXXXXX", tmpdir);
101 if ((fd = mkstemp(tempname)) == -1 ||
102 (collf = Fdopen(fd, "w+")) == NULL) {
103 warn("%s", tempname);
104 goto err;
105 }
106 (void)rm(tempname);
107
108 /*
109 * If we are going to prompt for a subject,
110 * refrain from printing a newline after
111 * the headers (since some people mind).
112 */
113 t = GTO|GSUBJECT|GCC|GNL;
114 getsub = 0;
115 if (hp->h_subject == NULL && value("interactive") != NULL &&
116 (value("ask") != NULL || value("asksub") != NULL))
117 t &= ~GNL, getsub++;
118 if (printheaders) {
119 puthead(hp, stdout, t);
120 (void)fflush(stdout);
121 }
122 if ((cp = value("escape")) != NULL)
123 escape = *cp;
124 else
125 escape = ESCAPE;
126 eofcount = 0;
127 hadintr = 0;
128 longline = 0;
129
130 if (!setjmp(colljmp)) {
131 if (getsub)
132 grabh(hp, GSUBJECT);
133 } else {
134 /*
135 * Come here for printing the after-signal message.
136 * Duplicate messages won't be printed because
137 * the write is aborted if we get a SIGTTOU.
138 */
139 cont:
140 if (hadintr) {
141 (void)fflush(stdout);
142 fprintf(stderr,
143 "\n(Interrupt -- one more to kill letter)\n");
144 } else {
145 printf("(continue)\n");
146 (void)fflush(stdout);
147 }
148 }
149 for (;;) {
150 colljmp_p = 1;
151 c = readline(stdin, linebuf, LINESIZE);
152 colljmp_p = 0;
153 if (c < 0) {
154 if (value("interactive") != NULL &&
155 value("ignoreeof") != NULL && ++eofcount < 25) {
156 printf("Use \".\" to terminate letter\n");
157 continue;
158 }
159 break;
160 }
161 lastlong = longline;
162 longline = c == LINESIZE - 1;
163 eofcount = 0;
164 hadintr = 0;
165 if (linebuf[0] == '.' && linebuf[1] == '\0' &&
166 value("interactive") != NULL && !lastlong &&
167 (value("dot") != NULL || value("ignoreeof") != NULL))
168 break;
169 if (linebuf[0] != escape || value("interactive") == NULL ||
170 lastlong) {
171 if (putline(collf, linebuf, !longline) < 0)
172 goto err;
173 continue;
174 }
175 c = linebuf[1];
176 switch (c) {
177 default:
178 /*
179 * On double escape, just send the single one.
180 * Otherwise, it's an error.
181 */
182 if (c == escape) {
183 if (putline(collf, &linebuf[1], !longline) < 0)
184 goto err;
185 else
186 break;
187 }
188 printf("Unknown tilde escape.\n");
189 break;
190 case 'C':
191 /*
192 * Dump core.
193 */
194 core(NULL);
195 break;
196 case '!':
197 /*
198 * Shell escape, send the balance of the
199 * line to sh -c.
200 */
201 shell(&linebuf[2]);
202 break;
203 case ':':
204 case '_':
205 /*
206 * Escape to command mode, but be nice!
207 */
208 execute(&linebuf[2], 1);
209 goto cont;
210 case '.':
211 /*
212 * Simulate end of file on input.
213 */
214 goto out;
215 case 'q':
216 /*
217 * Force a quit of sending mail.
218 * Act like an interrupt happened.
219 */
220 hadintr++;
221 collint(SIGINT);
222 exit(1);
223 case 'x':
224 /*
225 * Exit, do not save in dead.letter.
226 */
227 goto err;
228 case 'h':
229 /*
230 * Grab a bunch of headers.
231 */
232 grabh(hp, GTO|GSUBJECT|GCC|GBCC);
233 goto cont;
234 case 't':
235 /*
236 * Add to the To list.
237 */
238 hp->h_to = cat(hp->h_to, extract(&linebuf[2], GTO));
239 break;
240 case 's':
241 /*
242 * Set the Subject line.
243 */
244 cp = &linebuf[2];
245 while (isspace((unsigned char)*cp))
246 cp++;
247 hp->h_subject = savestr(cp);
248 break;
249 case 'R':
250 /*
251 * Set the Reply-To line.
252 */
253 cp = &linebuf[2];
254 while (isspace((unsigned char)*cp))
255 cp++;
256 hp->h_replyto = savestr(cp);
257 break;
258 case 'c':
259 /*
260 * Add to the CC list.
261 */
262 hp->h_cc = cat(hp->h_cc, extract(&linebuf[2], GCC));
263 break;
264 case 'b':
265 /*
266 * Add to the BCC list.
267 */
268 hp->h_bcc = cat(hp->h_bcc, extract(&linebuf[2], GBCC));
269 break;
270 case 'i':
271 case 'A':
272 case 'a':
273 /*
274 * Insert named variable in message.
275 */
276 switch(c) {
277 case 'i':
278 cp = &linebuf[2];
279 while(isspace((unsigned char)*cp))
280 cp++;
281 break;
282 case 'a':
283 cp = "sign";
284 break;
285 case 'A':
286 cp = "Sign";
287 break;
288 default:
289 goto err;
290 }
291
292 if(*cp != '\0' && (cp = value(cp)) != NULL) {
293 printf("%s\n", cp);
294 if(putline(collf, cp, 1) < 0)
295 goto err;
296 }
297
298 break;
299 case 'd':
300 /*
301 * Read in the dead letter file.
302 */
303 if (strlcpy(linebuf + 2, getdeadletter(),
304 sizeof(linebuf) - 2)
305 >= sizeof(linebuf) - 2) {
306 printf("Line buffer overflow\n");
307 break;
308 }
309 /* FALLTHROUGH */
310 case 'r':
311 case '<':
312 /*
313 * Invoke a file:
314 * Search for the file name,
315 * then open it and copy the contents to collf.
316 */
317 cp = &linebuf[2];
318 while (isspace((unsigned char)*cp))
319 cp++;
320 if (*cp == '\0') {
321 printf("Interpolate what file?\n");
322 break;
323 }
324 cp = expand(cp);
325 if (cp == NULL)
326 break;
327 if (*cp == '!') {
328 /*
329 * Insert stdout of command.
330 */
331 char *sh;
332 int nullfd, tempfd, rc;
333 char tempname2[PATHSIZE];
334
335 if ((nullfd = open(_PATH_DEVNULL, O_RDONLY, 0))
336 == -1) {
337 warn(_PATH_DEVNULL);
338 break;
339 }
340
341 (void)snprintf(tempname2, sizeof(tempname2),
342 "%s/mail.ReXXXXXXXXXX", tmpdir);
343 if ((tempfd = mkstemp(tempname2)) == -1 ||
344 (fbuf = Fdopen(tempfd, "w+")) == NULL) {
345 warn("%s", tempname2);
346 break;
347 }
348 (void)unlink(tempname2);
349
350 if ((sh = value("SHELL")) == NULL)
351 sh = _PATH_CSHELL;
352
353 rc = run_command(sh, 0, nullfd, fileno(fbuf),
354 "-c", cp+1, NULL);
355
356 close(nullfd);
357
358 if (rc < 0) {
359 (void)Fclose(fbuf);
360 break;
361 }
362
363 if (fsize(fbuf) == 0) {
364 fprintf(stderr,
365 "No bytes from command \"%s\"\n",
366 cp+1);
367 (void)Fclose(fbuf);
368 break;
369 }
370
371 rewind(fbuf);
372 } else if (isdir(cp)) {
373 printf("%s: Directory\n", cp);
374 break;
375 } else if ((fbuf = Fopen(cp, "r")) == NULL) {
376 warn("%s", cp);
377 break;
378 }
379 printf("\"%s\" ", cp);
380 (void)fflush(stdout);
381 lc = 0;
382 cc = 0;
383 while ((rc = readline(fbuf, linebuf, LINESIZE)) >= 0) {
384 if (rc != LINESIZE - 1)
385 lc++;
386 if ((t = putline(collf, linebuf,
387 rc != LINESIZE - 1)) < 0) {
388 (void)Fclose(fbuf);
389 goto err;
390 }
391 cc += t;
392 }
393 (void)Fclose(fbuf);
394 printf("%d/%d\n", lc, cc);
395 break;
396 case 'w':
397 /*
398 * Write the message on a file.
399 */
400 cp = &linebuf[2];
401 while (*cp == ' ' || *cp == '\t')
402 cp++;
403 if (*cp == '\0') {
404 fprintf(stderr, "Write what file!?\n");
405 break;
406 }
407 if ((cp = expand(cp)) == NULL)
408 break;
409 rewind(collf);
410 exwrite(cp, collf, 1);
411 break;
412 case 'm':
413 case 'M':
414 case 'f':
415 case 'F':
416 /*
417 * Interpolate the named messages, if we
418 * are in receiving mail mode. Does the
419 * standard list processing garbage.
420 * If ~f is given, we don't shift over.
421 */
422 if (forward(linebuf + 2, collf, tempname, c) < 0)
423 goto err;
424 goto cont;
425 case '?':
426 if ((fbuf = Fopen(_PATH_TILDE, "r")) == NULL) {
427 warn("%s", _PATH_TILDE);
428 break;
429 }
430 while ((t = getc(fbuf)) != EOF)
431 (void)putchar(t);
432 (void)Fclose(fbuf);
433 break;
434 case 'p':
435 /*
436 * Print out the current state of the
437 * message without altering anything.
438 */
439 rewind(collf);
440 printf("-------\nMessage contains:\n");
441 puthead(hp, stdout, GTO|GSUBJECT|GCC|GBCC|GNL);
442 while ((t = getc(collf)) != EOF)
443 (void)putchar(t);
444 goto cont;
445 case '|':
446 /*
447 * Pipe message through command.
448 * Collect output as new message.
449 */
450 rewind(collf);
451 mespipe(collf, &linebuf[2]);
452 goto cont;
453 case 'v':
454 case 'e':
455 /*
456 * Edit the current message.
457 * 'e' means to use EDITOR
458 * 'v' means to use VISUAL
459 */
460 rewind(collf);
461 mesedit(collf, c);
462 goto cont;
463 }
464 }
465 goto out;
466 err:
467 if (collf != NULL) {
468 (void)Fclose(collf);
469 collf = NULL;
470 }
471 out:
472 if (collf != NULL)
473 rewind(collf);
474 noreset--;
475 (void)sigprocmask(SIG_BLOCK, &nset, NULL);
476 (void)signal(SIGINT, saveint);
477 (void)signal(SIGHUP, savehup);
478 (void)signal(SIGTSTP, savetstp);
479 (void)signal(SIGTTOU, savettou);
480 (void)signal(SIGTTIN, savettin);
481 (void)sigprocmask(SIG_UNBLOCK, &nset, NULL);
482 return (collf);
483 }
484
485 /*
486 * Write a file, ex-like if f set.
487 */
488 int
exwrite(char name[],FILE * fp,int f)489 exwrite(char name[], FILE *fp, int f)
490 {
491 FILE *of;
492 int c, lc;
493 long cc;
494 struct stat junk;
495
496 if (f) {
497 printf("\"%s\" ", name);
498 (void)fflush(stdout);
499 }
500 if (stat(name, &junk) >= 0 && S_ISREG(junk.st_mode)) {
501 if (!f)
502 fprintf(stderr, "%s: ", name);
503 fprintf(stderr, "File exists\n");
504 return (-1);
505 }
506 if ((of = Fopen(name, "w")) == NULL) {
507 warn((char *)NULL);
508 return (-1);
509 }
510 lc = 0;
511 cc = 0;
512 while ((c = getc(fp)) != EOF) {
513 cc++;
514 if (c == '\n')
515 lc++;
516 (void)putc(c, of);
517 if (ferror(of)) {
518 warnx("%s", name);
519 (void)Fclose(of);
520 return (-1);
521 }
522 }
523 (void)Fclose(of);
524 printf("%d/%ld\n", lc, cc);
525 (void)fflush(stdout);
526 return (0);
527 }
528
529 /*
530 * Edit the message being collected on fp.
531 * On return, make the edit file the new temp file.
532 */
533 void
mesedit(FILE * fp,int c)534 mesedit(FILE *fp, int c)
535 {
536 sig_t sigint = signal(SIGINT, SIG_IGN);
537 FILE *nf = run_editor(fp, (off_t)-1, c, 0);
538
539 if (nf != NULL) {
540 (void)fseeko(nf, (off_t)0, SEEK_END);
541 collf = nf;
542 (void)Fclose(fp);
543 }
544 (void)signal(SIGINT, sigint);
545 }
546
547 /*
548 * Pipe the message through the command.
549 * Old message is on stdin of command;
550 * New message collected from stdout.
551 * Sh -c must return 0 to accept the new message.
552 */
553 void
mespipe(FILE * fp,char cmd[])554 mespipe(FILE *fp, char cmd[])
555 {
556 FILE *nf;
557 int fd;
558 sig_t sigint = signal(SIGINT, SIG_IGN);
559 char *sh, tempname[PATHSIZE];
560
561 (void)snprintf(tempname, sizeof(tempname),
562 "%s/mail.ReXXXXXXXXXX", tmpdir);
563 if ((fd = mkstemp(tempname)) == -1 ||
564 (nf = Fdopen(fd, "w+")) == NULL) {
565 warn("%s", tempname);
566 goto out;
567 }
568 (void)rm(tempname);
569 /*
570 * stdin = current message.
571 * stdout = new message.
572 */
573 if ((sh = value("SHELL")) == NULL)
574 sh = _PATH_CSHELL;
575 if (run_command(sh,
576 0, fileno(fp), fileno(nf), "-c", cmd, NULL) < 0) {
577 (void)Fclose(nf);
578 goto out;
579 }
580 if (fsize(nf) == 0) {
581 fprintf(stderr, "No bytes from \"%s\" !?\n", cmd);
582 (void)Fclose(nf);
583 goto out;
584 }
585 /*
586 * Take new files.
587 */
588 (void)fseeko(nf, (off_t)0, SEEK_END);
589 collf = nf;
590 (void)Fclose(fp);
591 out:
592 (void)signal(SIGINT, sigint);
593 }
594
595 /*
596 * Interpolate the named messages into the current
597 * message, preceding each line with a tab.
598 * Return a count of the number of characters now in
599 * the message, or -1 if an error is encountered writing
600 * the message temporary. The flag argument is 'm' if we
601 * should shift over and 'f' if not.
602 */
603 int
forward(char ms[],FILE * fp,char * fn,int f)604 forward(char ms[], FILE *fp, char *fn, int f)
605 {
606 int *msgvec;
607 struct ignoretab *ig;
608 char *tabst;
609
610 msgvec = (int *)salloc((msgCount+1) * sizeof(*msgvec));
611 if (msgvec == NULL)
612 return (0);
613 if (getmsglist(ms, msgvec, 0) < 0)
614 return (0);
615 if (*msgvec == 0) {
616 *msgvec = first(0, MMNORM);
617 if (*msgvec == 0) {
618 printf("No appropriate messages\n");
619 return (0);
620 }
621 msgvec[1] = 0;
622 }
623 if (f == 'f' || f == 'F')
624 tabst = NULL;
625 else if ((tabst = value("indentprefix")) == NULL)
626 tabst = "\t";
627 ig = isupper((unsigned char)f) ? NULL : ignore;
628 printf("Interpolating:");
629 for (; *msgvec != 0; msgvec++) {
630 struct message *mp = message + *msgvec - 1;
631
632 touch(mp);
633 printf(" %d", *msgvec);
634 if (sendmessage(mp, fp, ig, tabst) < 0) {
635 warnx("%s", fn);
636 return (-1);
637 }
638 }
639 printf("\n");
640 return (0);
641 }
642
643 /*
644 * Print (continue) when continued after ^Z.
645 */
646 /*ARGSUSED*/
647 void
collstop(int s)648 collstop(int s)
649 {
650 sig_t old_action = signal(s, SIG_DFL);
651 sigset_t nset;
652
653 (void)sigemptyset(&nset);
654 (void)sigaddset(&nset, s);
655 (void)sigprocmask(SIG_UNBLOCK, &nset, NULL);
656 (void)kill(0, s);
657 (void)sigprocmask(SIG_BLOCK, &nset, NULL);
658 (void)signal(s, old_action);
659 if (colljmp_p) {
660 colljmp_p = 0;
661 hadintr = 0;
662 longjmp(colljmp, 1);
663 }
664 }
665
666 /*
667 * On interrupt, come here to save the partial message in ~/dead.letter.
668 * Then jump out of the collection loop.
669 */
670 /*ARGSUSED*/
671 void
collint(int s __unused)672 collint(int s __unused)
673 {
674 /*
675 * the control flow is subtle, because we can be called from ~q.
676 */
677 if (!hadintr) {
678 if (value("ignore") != NULL) {
679 printf("@");
680 (void)fflush(stdout);
681 clearerr(stdin);
682 return;
683 }
684 hadintr = 1;
685 longjmp(colljmp, 1);
686 }
687 rewind(collf);
688 if (value("nosave") == NULL)
689 savedeadletter(collf);
690 longjmp(collabort, 1);
691 }
692
693 /*ARGSUSED*/
694 void
collhup(int s __unused)695 collhup(int s __unused)
696 {
697 rewind(collf);
698 savedeadletter(collf);
699 /*
700 * Let's pretend nobody else wants to clean up,
701 * a true statement at this time.
702 */
703 exit(1);
704 }
705
706 void
savedeadletter(FILE * fp)707 savedeadletter(FILE *fp)
708 {
709 FILE *dbuf;
710 int c;
711 char *cp;
712
713 if (fsize(fp) == 0)
714 return;
715 cp = getdeadletter();
716 c = umask(077);
717 dbuf = Fopen(cp, "a");
718 (void)umask(c);
719 if (dbuf == NULL)
720 return;
721 while ((c = getc(fp)) != EOF)
722 (void)putc(c, dbuf);
723 (void)Fclose(dbuf);
724 rewind(fp);
725 }
726