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