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