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