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