1 /* $NetBSD: collect.c,v 1.30 2003/08/07 11:14:36 agc 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.30 2003/08/07 11:14:36 agc 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 extern char *tmpdir; 52 53 /* 54 * Read a message from standard input and return a read file to it 55 * or NULL on error. 56 */ 57 58 /* 59 * The following hokiness with global variables is so that on 60 * receipt of an interrupt signal, the partial message can be salted 61 * away on dead.letter. 62 */ 63 64 static sig_t saveint; /* Previous SIGINT value */ 65 static sig_t savehup; /* Previous SIGHUP value */ 66 static sig_t savetstp; /* Previous SIGTSTP value */ 67 static sig_t savettou; /* Previous SIGTTOU value */ 68 static sig_t savettin; /* Previous SIGTTIN value */ 69 static FILE *collf; /* File for saving away */ 70 static int hadintr; /* Have seen one SIGINT so far */ 71 72 static jmp_buf colljmp; /* To get back to work */ 73 static int colljmp_p; /* whether to long jump */ 74 static jmp_buf collabort; /* To end collection with error */ 75 76 FILE * 77 collect(struct header *hp, int printheaders) 78 { 79 FILE *fbuf; 80 int lc, cc, escape, eofcount; 81 int c, fd, t; 82 char linebuf[LINESIZE], *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 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 sigemptyset(&nset); 104 sigaddset(&nset, SIGINT); 105 sigaddset(&nset, SIGHUP); 106 sigprocmask(SIG_BLOCK, &nset, NULL); 107 if ((saveint = signal(SIGINT, SIG_IGN)) != SIG_IGN) 108 signal(SIGINT, collint); 109 if ((savehup = signal(SIGHUP, SIG_IGN)) != SIG_IGN) 110 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 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 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 puthead(hp, stdout, t); 144 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 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 fflush(stdout); 167 fprintf(stderr, 168 "\n(Interrupt -- one more to kill letter)\n"); 169 } else { 170 printf("(continue)\n"); 171 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 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 printf("Unknown tilde escape.\n"); 214 break; 215 case 'C': 216 /* 217 * Dump core. 218 */ 219 core(NULL); 220 break; 221 case '!': 222 /* 223 * Shell escape, send the balance of the 224 * line to sh -c. 225 */ 226 shell(&linebuf[2]); 227 break; 228 case ':': 229 case '_': 230 /* 231 * Escape to command mode, but be nice! 232 */ 233 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 249 case 'x': /* exit, do not save in dead.letter */ 250 goto err; 251 252 case 'h': 253 /* 254 * Grab a bunch of headers. 255 */ 256 grabh(hp, GTO|GSUBJECT|GCC|GBCC); 257 goto cont; 258 case 't': 259 /* 260 * Add to the To list. 261 */ 262 hp->h_to = cat(hp->h_to, extract(&linebuf[2], GTO)); 263 break; 264 case 's': 265 /* 266 * Set the Subject list. 267 */ 268 cp = &linebuf[2]; 269 while (isspace((unsigned char)*cp)) 270 cp++; 271 hp->h_subject = savestr(cp); 272 break; 273 case 'c': 274 /* 275 * Add to the CC list. 276 */ 277 hp->h_cc = cat(hp->h_cc, extract(&linebuf[2], GCC)); 278 break; 279 case 'b': 280 /* 281 * Add stuff to blind carbon copies list. 282 */ 283 hp->h_bcc = cat(hp->h_bcc, extract(&linebuf[2], GBCC)); 284 break; 285 case 'i': 286 case 'A': 287 case 'a': 288 /* 289 * Insert named variable in message 290 */ 291 292 switch(c) { 293 case 'i': 294 cp = &linebuf[2]; 295 while(isspace((unsigned char) *cp)) 296 cp++; 297 298 break; 299 case 'a': 300 cp = "sign"; 301 break; 302 case 'A': 303 cp = "Sign"; 304 break; 305 default: 306 goto err; 307 } 308 309 if(*cp && (cp = value(cp)) != NULL) { 310 printf("%s\n", cp); 311 if(putline(collf, cp, 1) < 0) 312 goto err; 313 } 314 315 break; 316 317 case 'd': 318 strcpy(linebuf + 2, getdeadletter()); 319 /* fall into . . . */ 320 case 'r': 321 case '<': 322 /* 323 * Invoke a file: 324 * Search for the file name, 325 * then open it and copy the contents to collf. 326 */ 327 cp = &linebuf[2]; 328 while (isspace((unsigned char)*cp)) 329 cp++; 330 if (*cp == '\0') { 331 printf("Interpolate what file?\n"); 332 break; 333 } 334 335 cp = expand(cp); 336 if (cp == NULL) 337 break; 338 339 if (*cp == '!') { /* insert stdout of command */ 340 char *shellcmd; 341 int nullfd; 342 int rc2; 343 344 if((nullfd = open("/dev/null", O_RDONLY, 0)) == -1) { 345 warn("/dev/null"); 346 break; 347 } 348 349 (void)snprintf(tempname, sizeof(tempname), 350 "%s/mail.ReXXXXXXXXXX", tmpdir); 351 if ((fd = mkstemp(tempname)) == -1 || 352 (fbuf = Fdopen(fd, "w+")) == NULL) { 353 if (fd != -1) 354 close(fd); 355 warn("%s", tempname); 356 break; 357 } 358 (void)unlink(tempname); 359 360 if ((shellcmd = value("SHELL")) == NULL) 361 shellcmd = _PATH_CSHELL; 362 363 rc2 = run_command(shellcmd, 0, nullfd, fileno(fbuf), "-c", cp+1, NULL); 364 365 close(nullfd); 366 367 if (rc2 < 0) { 368 (void)Fclose(fbuf); 369 break; 370 } 371 372 if (fsize(fbuf) == 0) { 373 fprintf(stderr, "No bytes from command \"%s\"\n", cp+1); 374 (void)Fclose(fbuf); 375 break; 376 } 377 378 rewind(fbuf); 379 } 380 else if (isdir(cp)) { 381 printf("%s: Directory\n", cp); 382 break; 383 } 384 else if ((fbuf = Fopen(cp, "r")) == NULL) { 385 warn("%s", cp); 386 break; 387 } 388 printf("\"%s\" ", cp); 389 fflush(stdout); 390 lc = 0; 391 cc = 0; 392 while ((rc = readline(fbuf, linebuf, LINESIZE)) >= 0) { 393 if (rc != LINESIZE-1) lc++; 394 if ((t = putline(collf, linebuf, 395 rc != LINESIZE-1)) < 0) { 396 Fclose(fbuf); 397 goto err; 398 } 399 cc += t; 400 } 401 Fclose(fbuf); 402 printf("%d/%d\n", lc, cc); 403 break; 404 case 'w': 405 /* 406 * Write the message on a file. 407 */ 408 cp = &linebuf[2]; 409 while (*cp == ' ' || *cp == '\t') 410 cp++; 411 if (*cp == '\0') { 412 fprintf(stderr, "Write what file!?\n"); 413 break; 414 } 415 if ((cp = expand(cp)) == NULL) 416 break; 417 rewind(collf); 418 exwrite(cp, collf, 1); 419 break; 420 case 'm': 421 case 'M': 422 case 'f': 423 case 'F': 424 /* 425 * Interpolate the named messages, if we 426 * are in receiving mail mode. Does the 427 * standard list processing garbage. 428 * If ~f is given, we don't shift over. 429 */ 430 if (forward(linebuf + 2, collf, mailtempname, c) < 0) 431 goto err; 432 goto cont; 433 case '?': 434 if ((fbuf = Fopen(_PATH_TILDE, "r")) == NULL) { 435 warn(_PATH_TILDE); 436 break; 437 } 438 while ((t = getc(fbuf)) != EOF) 439 (void)putchar(t); 440 Fclose(fbuf); 441 break; 442 case 'p': 443 /* 444 * Print out the current state of the 445 * message without altering anything. 446 */ 447 rewind(collf); 448 printf("-------\nMessage contains:\n"); 449 puthead(hp, stdout, GTO|GSUBJECT|GCC|GBCC|GNL); 450 while ((t = getc(collf)) != EOF) 451 (void)putchar(t); 452 goto cont; 453 case '|': 454 /* 455 * Pipe message through command. 456 * Collect output as new message. 457 */ 458 rewind(collf); 459 mespipe(collf, &linebuf[2]); 460 goto cont; 461 case 'v': 462 case 'e': 463 /* 464 * Edit the current message. 465 * 'e' means to use EDITOR 466 * 'v' means to use VISUAL 467 */ 468 rewind(collf); 469 mesedit(collf, c); 470 goto cont; 471 } 472 } 473 goto out; 474 err: 475 if (collf != NULL) { 476 Fclose(collf); 477 collf = NULL; 478 } 479 out: 480 if (collf != NULL) 481 rewind(collf); 482 noreset--; 483 sigprocmask(SIG_BLOCK, &nset, NULL); 484 signal(SIGINT, saveint); 485 signal(SIGHUP, savehup); 486 signal(SIGTSTP, savetstp); 487 signal(SIGTTOU, savettou); 488 signal(SIGTTIN, savettin); 489 sigprocmask(SIG_UNBLOCK, &nset, NULL); 490 return collf; 491 } 492 493 /* 494 * Write a file, ex-like if f set. 495 */ 496 int 497 exwrite(char name[], FILE *fp, int f) 498 { 499 FILE *of; 500 int c; 501 long cc; 502 int lc; 503 struct stat junk; 504 505 if (f) { 506 printf("\"%s\" ", name); 507 fflush(stdout); 508 } 509 if (stat(name, &junk) >= 0 && S_ISREG(junk.st_mode)) { 510 if (!f) 511 fprintf(stderr, "%s: ", name); 512 fprintf(stderr, "File exists\n"); 513 return(-1); 514 } 515 if ((of = Fopen(name, "w")) == NULL) { 516 warn("%s", name); 517 return(-1); 518 } 519 lc = 0; 520 cc = 0; 521 while ((c = getc(fp)) != EOF) { 522 cc++; 523 if (c == '\n') 524 lc++; 525 (void)putc(c, of); 526 if (ferror(of)) { 527 warn("%s", name); 528 Fclose(of); 529 return(-1); 530 } 531 } 532 Fclose(of); 533 printf("%d/%ld\n", lc, cc); 534 fflush(stdout); 535 return(0); 536 } 537 538 /* 539 * Edit the message being collected on fp. 540 * On return, make the edit file the new temp file. 541 */ 542 void 543 mesedit(FILE *fp, int c) 544 { 545 sig_t sigint = signal(SIGINT, SIG_IGN); 546 FILE *nf = run_editor(fp, (off_t)-1, c, 0); 547 548 if (nf != NULL) { 549 fseek(nf, 0L, 2); 550 collf = nf; 551 Fclose(fp); 552 } 553 (void)signal(SIGINT, sigint); 554 } 555 556 /* 557 * Pipe the message through the command. 558 * Old message is on stdin of command; 559 * New message collected from stdout. 560 * Sh -c must return 0 to accept the new message. 561 */ 562 void 563 mespipe(FILE *fp, char cmd[]) 564 { 565 FILE *nf; 566 sig_t sigint = signal(SIGINT, SIG_IGN); 567 char *shellcmd; 568 int fd; 569 char tempname[PATHSIZE]; 570 571 (void)snprintf(tempname, sizeof(tempname), 572 "%s/mail.ReXXXXXXXXXX", tmpdir); 573 if ((fd = mkstemp(tempname)) == -1 || 574 (nf = Fdopen(fd, "w+")) == NULL) { 575 if (fd != -1) 576 close(fd); 577 warn("%s", tempname); 578 goto out; 579 } 580 (void)unlink(tempname); 581 /* 582 * stdin = current message. 583 * stdout = new message. 584 */ 585 if ((shellcmd = value("SHELL")) == NULL) 586 shellcmd = _PATH_CSHELL; 587 if (run_command(shellcmd, 588 0, fileno(fp), fileno(nf), "-c", cmd, NULL) < 0) { 589 (void)Fclose(nf); 590 goto out; 591 } 592 if (fsize(nf) == 0) { 593 fprintf(stderr, "No bytes from \"%s\" !?\n", cmd); 594 (void)Fclose(nf); 595 goto out; 596 } 597 /* 598 * Take new files. 599 */ 600 (void)fseek(nf, 0L, 2); 601 collf = nf; 602 (void)Fclose(fp); 603 out: 604 (void)signal(SIGINT, sigint); 605 } 606 607 /* 608 * Interpolate the named messages into the current 609 * message, preceding each line with a tab. 610 * Return a count of the number of characters now in 611 * the message, or -1 if an error is encountered writing 612 * the message temporary. The flag argument is 'm' if we 613 * should shift over and 'f' if not. 614 */ 615 int 616 forward(char ms[], FILE *fp, char *fn, int f) 617 { 618 int *msgvec; 619 struct ignoretab *ig; 620 char *tabst; 621 622 msgvec = (int *) salloc((msgCount+1) * sizeof *msgvec); 623 if (msgvec == (int *) NULL) 624 return(0); 625 if (getmsglist(ms, msgvec, 0) < 0) 626 return(0); 627 if (*msgvec == 0) { 628 *msgvec = first(0, MMNORM); 629 if (*msgvec == 0) { 630 printf("No appropriate messages\n"); 631 return(0); 632 } 633 msgvec[1] = 0; 634 } 635 if (f == 'f' || f == 'F') 636 tabst = NULL; 637 else if ((tabst = value("indentprefix")) == NULL) 638 tabst = "\t"; 639 ig = isupper(f) ? NULL : ignore; 640 printf("Interpolating:"); 641 for (; *msgvec != 0; msgvec++) { 642 struct message *mp = message + *msgvec - 1; 643 644 touch(mp); 645 printf(" %d", *msgvec); 646 if (sendmessage(mp, fp, ig, tabst) < 0) { 647 warn("%s", fn); 648 return(-1); 649 } 650 } 651 printf("\n"); 652 return(0); 653 } 654 655 /* 656 * Print (continue) when continued after ^Z. 657 */ 658 /*ARGSUSED*/ 659 void 660 collstop(int s) 661 { 662 sig_t old_action = signal(s, SIG_DFL); 663 sigset_t nset; 664 665 sigemptyset(&nset); 666 sigaddset(&nset, s); 667 sigprocmask(SIG_UNBLOCK, &nset, NULL); 668 kill(0, s); 669 sigprocmask(SIG_BLOCK, &nset, NULL); 670 signal(s, old_action); 671 if (colljmp_p) { 672 colljmp_p = 0; 673 hadintr = 0; 674 longjmp(colljmp, 1); 675 } 676 } 677 678 /* 679 * On interrupt, come here to save the partial message in ~/dead.letter. 680 * Then jump out of the collection loop. 681 */ 682 /*ARGSUSED*/ 683 void 684 collint(int s) 685 { 686 /* 687 * the control flow is subtle, because we can be called from ~q. 688 */ 689 if (!hadintr) { 690 if (value("ignore") != NULL) { 691 puts("@"); 692 fflush(stdout); 693 clearerr(stdin); 694 return; 695 } 696 hadintr = 1; 697 longjmp(colljmp, 1); 698 } 699 rewind(collf); 700 if (value("nosave") == NULL) 701 savedeadletter(collf); 702 longjmp(collabort, 1); 703 } 704 705 /*ARGSUSED*/ 706 void 707 collhup(int s) 708 { 709 rewind(collf); 710 savedeadletter(collf); 711 /* 712 * Let's pretend nobody else wants to clean up, 713 * a true statement at this time. 714 */ 715 exit(1); 716 } 717 718 void 719 savedeadletter(FILE *fp) 720 { 721 FILE *dbuf; 722 int c; 723 char *cp; 724 725 if (fsize(fp) == 0) 726 return; 727 cp = getdeadletter(); 728 c = umask(077); 729 dbuf = Fopen(cp, "a"); 730 (void)umask(c); 731 if (dbuf == NULL) 732 return; 733 while ((c = getc(fp)) != EOF) 734 (void)putc(c, dbuf); 735 Fclose(dbuf); 736 rewind(fp); 737 } 738