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