1 /* 2 * Copyright (c) 1980 Regents of the University of California. 3 * All rights reserved. The Berkeley software License Agreement 4 * specifies the terms and conditions for redistribution. 5 */ 6 7 #ifndef lint 8 static char *sccsid = "@(#)collect.c 5.3 (Berkeley) 05/18/87"; 9 #endif not lint 10 11 /* 12 * Mail -- a mail program 13 * 14 * Collect input from standard input, handling 15 * ~ escapes. 16 */ 17 18 #include "rcv.h" 19 #include <sys/stat.h> 20 #include <sys/wait.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 int (*saveint)(); /* Previous SIGINT value */ 34 static int (*savehup)(); /* Previous SIGHUP value */ 35 static int (*savecont)(); /* Previous SIGCONT value */ 36 static FILE *collf; /* File for saving away */ 37 static int hadintr; /* Have seen one SIGINT so far */ 38 39 static jmp_buf coljmp; /* To get back to work */ 40 41 FILE * 42 collect(hp) 43 struct header *hp; 44 { 45 FILE *fp, *fbuf; 46 int lc, cc, escape, eof; 47 int collrub(), intack(), collcont(); 48 register int c, t; 49 char linebuf[LINESIZE], *cp; 50 extern char tempMail[]; 51 int notify(); 52 char getsub; 53 int omask; 54 55 noreset++; 56 fp = NULL; 57 collf = NULL; 58 59 /* 60 * Start catching signals from here, but we're still die on interrupts 61 * until we're in the main loop. 62 */ 63 omask = sigblock(sigmask(SIGINT) | sigmask(SIGHUP)); 64 if ((saveint = signal(SIGINT, SIG_IGN)) != SIG_IGN) 65 signal(SIGINT, value("ignore") != NOSTR ? intack : collrub); 66 if ((savehup = signal(SIGHUP, SIG_IGN)) != SIG_IGN) 67 signal(SIGHUP, collrub); 68 savecont = signal(SIGCONT, SIG_DFL); 69 if (setjmp(coljmp)) { 70 remove(tempMail); 71 goto err; 72 } 73 sigsetmask(omask & ~(sigmask(SIGINT) | sigmask(SIGHUP))); 74 75 if ((fp = fopen(tempMail, "w+")) == NULL) { 76 perror(tempMail); 77 goto err; 78 } 79 collf = fp; 80 remove(tempMail); 81 82 /* 83 * If we are going to prompt for a subject, 84 * refrain from printing a newline after 85 * the headers (since some people mind). 86 */ 87 t = GTO|GSUBJECT|GCC|GNL; 88 getsub = 0; 89 if (intty && sflag == NOSTR && hp->h_subject == NOSTR && value("ask")) 90 t &= ~GNL, getsub++; 91 if (hp->h_seq != 0) { 92 puthead(hp, stdout, t); 93 fflush(stdout); 94 } 95 escape = ESCAPE; 96 if ((cp = value("escape")) != NOSTR) 97 escape = *cp; 98 eof = 0; 99 hadintr = 0; 100 101 /* 102 * We can put the setjmp here because register variable 103 * needs to be saved in the loop. 104 */ 105 if (!setjmp(coljmp)) { 106 signal(SIGCONT, collcont); 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 if (readline(stdin, linebuf) < 0) { 127 if (intty && value("ignoreeof") != NOSTR) { 128 if (++eof > 35) 129 break; 130 printf("Use \".\" to terminate letter\n"); 131 continue; 132 } 133 break; 134 } 135 eof = 0; 136 hadintr = 0; 137 if (intty && equal(".", linebuf) && 138 (value("dot") != NOSTR || value("ignoreeof") != NOSTR)) 139 break; 140 if (linebuf[0] != escape || rflag != NOSTR) { 141 if (putline(fp, linebuf) < 0) 142 goto err; 143 continue; 144 } 145 c = linebuf[1]; 146 switch (c) { 147 default: 148 /* 149 * On double escape, just send the single one. 150 * Otherwise, it's an error. 151 */ 152 153 if (c == escape) { 154 if (putline(fp, &linebuf[1]) < 0) 155 goto err; 156 else 157 break; 158 } 159 printf("Unknown tilde escape.\n"); 160 break; 161 162 case 'C': 163 /* 164 * Dump core. 165 */ 166 167 core(); 168 break; 169 170 case '!': 171 /* 172 * Shell escape, send the balance of the 173 * line to sh -c. 174 */ 175 176 shell(&linebuf[2]); 177 break; 178 179 case ':': 180 case '_': 181 /* 182 * Escape to command mode, but be nice! 183 */ 184 185 execute(&linebuf[2], 1); 186 goto cont; 187 188 case '.': 189 /* 190 * Simulate end of file on input. 191 */ 192 goto out; 193 194 case 'q': 195 case 'Q': 196 /* 197 * Force a quit of sending mail. 198 * Act like an interrupt happened. 199 */ 200 201 hadintr++; 202 collrub(SIGINT); 203 exit(1); 204 205 case 'h': 206 /* 207 * Grab a bunch of headers. 208 */ 209 if (!intty || !outtty) { 210 printf("~h: no can do!?\n"); 211 break; 212 } 213 grabh(hp, GTO|GSUBJECT|GCC|GBCC); 214 goto cont; 215 216 case 't': 217 /* 218 * Add to the To list. 219 */ 220 221 hp->h_to = addto(hp->h_to, &linebuf[2]); 222 hp->h_seq++; 223 break; 224 225 case 's': 226 /* 227 * Set the Subject list. 228 */ 229 230 cp = &linebuf[2]; 231 while (isspace(*cp)) 232 cp++; 233 hp->h_subject = savestr(cp); 234 hp->h_seq++; 235 break; 236 237 case 'c': 238 /* 239 * Add to the CC list. 240 */ 241 242 hp->h_cc = addto(hp->h_cc, &linebuf[2]); 243 hp->h_seq++; 244 break; 245 246 case 'b': 247 /* 248 * Add stuff to blind carbon copies list. 249 */ 250 hp->h_bcc = addto(hp->h_bcc, &linebuf[2]); 251 hp->h_seq++; 252 break; 253 254 case 'd': 255 strcpy(linebuf + 2, deadletter); 256 /* fall into . . . */ 257 258 case 'r': 259 /* 260 * Invoke a file: 261 * Search for the file name, 262 * then open it and copy the contents to fp. 263 */ 264 265 cp = &linebuf[2]; 266 while (isspace(*cp)) 267 cp++; 268 if (*cp == '\0') { 269 printf("Interpolate what file?\n"); 270 break; 271 } 272 cp = expand(cp); 273 if (cp == NOSTR) 274 break; 275 if (isdir(cp)) { 276 printf("%s: Directory\n", cp); 277 break; 278 } 279 if ((fbuf = fopen(cp, "r")) == NULL) { 280 perror(cp); 281 break; 282 } 283 printf("\"%s\" ", cp); 284 fflush(stdout); 285 lc = 0; 286 cc = 0; 287 while (readline(fbuf, linebuf) >= 0) { 288 lc++; 289 if ((t = putline(fp, linebuf)) < 0) { 290 fclose(fbuf); 291 goto err; 292 } 293 cc += t; 294 } 295 fclose(fbuf); 296 printf("%d/%d\n", lc, cc); 297 break; 298 299 case 'w': 300 /* 301 * Write the message on a file. 302 */ 303 304 cp = &linebuf[2]; 305 while (any(*cp, " \t")) 306 cp++; 307 if (*cp == '\0') { 308 fprintf(stderr, "Write what file!?\n"); 309 break; 310 } 311 if ((cp = expand(cp)) == NOSTR) 312 break; 313 rewind(fp); 314 exwrite(cp, fp, 1); 315 break; 316 317 case 'm': 318 case 'f': 319 /* 320 * Interpolate the named messages, if we 321 * are in receiving mail mode. Does the 322 * standard list processing garbage. 323 * If ~f is given, we don't shift over. 324 */ 325 326 if (!rcvmode) { 327 printf("No messages to send from!?!\n"); 328 break; 329 } 330 cp = &linebuf[2]; 331 while (any(*cp, " \t")) 332 cp++; 333 if (forward(cp, fp, c) < 0) 334 goto err; 335 goto cont; 336 337 case '?': 338 if ((fbuf = fopen(THELPFILE, "r")) == NULL) { 339 perror(THELPFILE); 340 break; 341 } 342 while ((t = getc(fp)) != EOF) 343 putchar(t); 344 fclose(fbuf); 345 break; 346 347 case 'p': 348 /* 349 * Print out the current state of the 350 * message without altering anything. 351 */ 352 353 rewind(fp); 354 printf("-------\nMessage contains:\n"); 355 puthead(hp, stdout, GTO|GSUBJECT|GCC|GBCC|GNL); 356 while ((t = getc(fp)) != EOF) 357 putchar(t); 358 goto cont; 359 360 case '^': 361 case '|': 362 /* 363 * Pipe message through command. 364 * Collect output as new message. 365 */ 366 367 rewind(fp); 368 fp = mespipe(fp, &linebuf[2]); 369 goto cont; 370 371 case 'v': 372 case 'e': 373 /* 374 * Edit the current message. 375 * 'e' means to use EDITOR 376 * 'v' means to use VISUAL 377 */ 378 379 rewind(fp); 380 if ((fp = mesedit(fp, c)) == NULL) 381 goto err; 382 goto cont; 383 } 384 } 385 goto out; 386 err: 387 if (fp != NULL) { 388 fclose(fp); 389 fp = NULL; 390 } 391 out: 392 if (fp != NULL) 393 rewind(fp); 394 signal(SIGINT, saveint); 395 signal(SIGHUP, savehup); 396 signal(SIGCONT, savecont); 397 sigsetmask(omask); 398 noreset = 0; 399 return(fp); 400 } 401 402 /* 403 * Write a file, ex-like if f set. 404 */ 405 406 exwrite(name, fp, f) 407 char name[]; 408 FILE *fp; 409 { 410 register FILE *of; 411 register int c; 412 long cc; 413 int lc; 414 struct stat junk; 415 416 if (f) { 417 printf("\"%s\" ", name); 418 fflush(stdout); 419 } 420 if (stat(name, &junk) >= 0 && (junk.st_mode & S_IFMT) == S_IFREG) { 421 if (!f) 422 fprintf(stderr, "%s: ", name); 423 fprintf(stderr, "File exists\n"); 424 return(-1); 425 } 426 if ((of = fopen(name, "w")) == NULL) { 427 perror(NOSTR); 428 return(-1); 429 } 430 lc = 0; 431 cc = 0; 432 while ((c = getc(fp)) != EOF) { 433 cc++; 434 if (c == '\n') 435 lc++; 436 putc(c, of); 437 if (ferror(of)) { 438 perror(name); 439 fclose(of); 440 return(-1); 441 } 442 } 443 fclose(of); 444 printf("%d/%ld\n", lc, cc); 445 fflush(stdout); 446 return(0); 447 } 448 449 /* 450 * Edit the message being collected on fp. 451 * Write the message out onto some poorly-named temp file 452 * and point an editor at it. 453 * 454 * On return, make the edit file the new temp file. 455 */ 456 457 FILE * 458 mesedit(fp, c) 459 FILE *fp; 460 { 461 int pid; 462 union wait s; 463 FILE *fbuf; 464 register int t; 465 int (*sigint)(), (*sigcont)(); 466 struct stat sbuf; 467 extern char tempMail[], tempEdit[]; 468 register char *edit; 469 470 sigint = signal(SIGINT, SIG_IGN); 471 sigcont = signal(SIGCONT, SIG_DFL); 472 if (stat(tempEdit, &sbuf) >= 0) { 473 printf("%s: file exists\n", tempEdit); 474 goto out; 475 } 476 close(creat(tempEdit, 0600)); 477 if ((fbuf = fopen(tempEdit, "w")) == NULL) { 478 perror(tempEdit); 479 goto out; 480 } 481 while ((t = getc(fp)) != EOF) 482 putc(t, fbuf); 483 fflush(fbuf); 484 if (ferror(fbuf)) { 485 perror(tempEdit); 486 remove(tempEdit); 487 goto fix; 488 } 489 fclose(fbuf); 490 if ((edit = value(c == 'e' ? "EDITOR" : "VISUAL")) == NOSTR) 491 edit = c == 'e' ? EDITOR : VISUAL; 492 pid = vfork(); 493 if (pid == 0) { 494 if (sigint != SIG_IGN) 495 signal(SIGINT, SIG_DFL); 496 execl(edit, edit, tempEdit, 0); 497 perror(edit); 498 _exit(1); 499 } 500 if (pid == -1) { 501 perror("fork"); 502 remove(tempEdit); 503 goto out; 504 } 505 while (wait(&s) != pid) 506 ; 507 if (s.w_status != 0) { 508 printf("Fatal error in \"%s\"\n", edit); 509 remove(tempEdit); 510 goto out; 511 } 512 513 /* 514 * Now switch to new file. 515 */ 516 517 if ((fbuf = fopen(tempEdit, "a+")) == NULL) { 518 perror(tempEdit); 519 remove(tempEdit); 520 goto out; 521 } 522 remove(tempEdit); 523 collf = fbuf; 524 fclose(fp); 525 fp = fbuf; 526 goto out; 527 fix: 528 perror(tempEdit); 529 out: 530 signal(SIGCONT, sigcont); 531 signal(SIGINT, sigint); 532 return(fp); 533 } 534 535 /* 536 * Pipe the message through the command. 537 * Old message is on stdin of command; 538 * New message collected from stdout. 539 * Sh -c must return 0 to accept the new message. 540 */ 541 542 FILE * 543 mespipe(fp, cmd) 544 FILE *fp; 545 char cmd[]; 546 { 547 register FILE *nf; 548 int pid; 549 union wait s; 550 int (*saveint)(); 551 char *Shell; 552 553 if ((nf = fopen(tempEdit, "w+")) == NULL) { 554 perror(tempEdit); 555 return(fp); 556 } 557 remove(tempEdit); 558 saveint = signal(SIGINT, SIG_IGN); 559 if ((Shell = value("SHELL")) == NULL) 560 Shell = "/bin/sh"; 561 if ((pid = vfork()) == -1) { 562 perror("fork"); 563 goto err; 564 } 565 if (pid == 0) { 566 int fd; 567 /* 568 * stdin = current message. 569 * stdout = new message. 570 */ 571 572 close(0); 573 dup(fileno(fp)); 574 close(1); 575 dup(fileno(nf)); 576 for (fd = getdtablesize(); --fd > 2;) 577 close(fd); 578 execl(Shell, Shell, "-c", cmd, 0); 579 perror(Shell); 580 _exit(1); 581 } 582 while (wait(&s) != pid) 583 ; 584 if (s.w_status != 0 || pid == -1) { 585 fprintf(stderr, "\"%s\" failed!?\n", cmd); 586 goto err; 587 } 588 if (fsize(nf) == 0) { 589 fprintf(stderr, "No bytes from \"%s\" !?\n", cmd); 590 goto err; 591 } 592 593 /* 594 * Take new files. 595 */ 596 597 fseek(nf, 0L, 2); 598 collf = nf; 599 fclose(fp); 600 signal(SIGINT, saveint); 601 return(nf); 602 603 err: 604 fclose(nf); 605 signal(SIGINT, saveint); 606 return(fp); 607 } 608 609 /* 610 * Interpolate the named messages into the current 611 * message, preceding each line with a tab. 612 * Return a count of the number of characters now in 613 * the message, or -1 if an error is encountered writing 614 * the message temporary. The flag argument is 'm' if we 615 * should shift over and 'f' if not. 616 */ 617 forward(ms, fp, f) 618 char ms[]; 619 FILE *fp; 620 { 621 register int *msgvec, *ip; 622 extern char tempMail[]; 623 624 msgvec = (int *) salloc((msgCount+1) * sizeof *msgvec); 625 if (msgvec == (int *) NOSTR) 626 return(0); 627 if (getmsglist(ms, msgvec, 0) < 0) 628 return(0); 629 if (*msgvec == NULL) { 630 *msgvec = first(0, MMNORM); 631 if (*msgvec == NULL) { 632 printf("No appropriate messages\n"); 633 return(0); 634 } 635 msgvec[1] = NULL; 636 } 637 printf("Interpolating:"); 638 for (ip = msgvec; *ip != NULL; ip++) { 639 touch(*ip); 640 printf(" %d", *ip); 641 if (f == 'm') { 642 if (transmit(&message[*ip-1], fp) < 0L) { 643 perror(tempMail); 644 return(-1); 645 } 646 } else 647 if (send(&message[*ip-1], fp, 0) < 0) { 648 perror(tempMail); 649 return(-1); 650 } 651 } 652 printf("\n"); 653 return(0); 654 } 655 656 /* 657 * Send message described by the passed pointer to the 658 * passed output buffer. Insert a tab in front of each 659 * line. Return a count of the characters sent, or -1 660 * on error. 661 */ 662 663 long 664 transmit(mailp, fp) 665 struct message *mailp; 666 FILE *fp; 667 { 668 register struct message *mp; 669 register int ch; 670 long c, n; 671 int bol; 672 FILE *ibuf; 673 674 mp = mailp; 675 ibuf = setinput(mp); 676 c = mp->m_size; 677 n = c; 678 bol = 1; 679 while (c-- > 0L) { 680 ch = getc(ibuf); 681 if (ch == '\n') 682 bol = 1; 683 else if (bol) { 684 bol = 0; 685 putc('\t', fp); 686 n++; 687 } 688 putc(ch, fp); 689 if (ferror(fp)) { 690 perror("/tmp"); 691 return(-1L); 692 } 693 } 694 return(n); 695 } 696 697 /* 698 * Print (continue) when continued after ^Z. 699 */ 700 /*ARGSUSED*/ 701 collcont(s) 702 { 703 704 hadintr = 0; 705 longjmp(coljmp, 1); 706 } 707 708 /* 709 * On interrupt, go here to save the partial 710 * message on ~/dead.letter. 711 * Then restore signals and execute the normal 712 * signal routine. We only come here if signals 713 * were previously set anyway. 714 */ 715 716 collrub(s) 717 { 718 register FILE *dbuf; 719 register int c; 720 721 if (s == SIGINT && hadintr == 0) { 722 hadintr = 1; 723 longjmp(coljmp, 1); 724 } 725 rewind(collf); 726 if (s == SIGINT && value("nosave") != NOSTR || fsize(collf) == 0) 727 goto done; 728 if ((dbuf = fopen(deadletter, "w")) == NULL) 729 goto done; 730 chmod(deadletter, 0600); 731 while ((c = getc(collf)) != EOF) 732 putc(c, dbuf); 733 fclose(dbuf); 734 735 done: 736 fclose(collf); 737 signal(SIGINT, saveint); 738 signal(SIGHUP, savehup); 739 signal(SIGCONT, savecont); 740 if (rcvmode) { 741 if (s == SIGHUP) 742 hangup(SIGHUP); 743 else 744 stop(s); 745 } else 746 exit(1); 747 } 748 749 /* 750 * Acknowledge an interrupt signal from the tty by typing an @ 751 */ 752 753 /*ARGSUSED*/ 754 intack(s) 755 { 756 757 puts("@"); 758 fflush(stdout); 759 clearerr(stdin); 760 } 761 762 /* 763 * Add a string to the end of a header entry field. 764 */ 765 766 char * 767 addto(hf, news) 768 char hf[], news[]; 769 { 770 register char *cp, *cp2, *linebuf; 771 772 if (hf == NOSTR) 773 hf = ""; 774 if (*news == '\0') 775 return(hf); 776 linebuf = salloc(strlen(hf) + strlen(news) + 2); 777 for (cp = hf; any(*cp, " \t"); cp++) 778 ; 779 for (cp2 = linebuf; *cp;) 780 *cp2++ = *cp++; 781 *cp2++ = ' '; 782 for (cp = news; any(*cp, " \t"); cp++) 783 ; 784 while (*cp != '\0') 785 *cp2++ = *cp++; 786 *cp2 = '\0'; 787 return(linebuf); 788 } 789