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