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