1 /* $OpenBSD: collect.c,v 1.31 2009/07/28 16:05:04 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.31 2009/07/28 16:05:04 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 '!': 182 /* 183 * Shell escape, send the balance of the 184 * line to sh -c. 185 */ 186 shell(&linebuf[2]); 187 break; 188 case ':': 189 case '_': 190 /* 191 * Escape to command mode, but be nice! 192 */ 193 execute(&linebuf[2], 1); 194 goto cont; 195 case '.': 196 /* 197 * Simulate end of file on input. 198 */ 199 goto out; 200 case 'q': 201 /* 202 * Force a quit of sending mail. 203 * Act like an interrupt happened. 204 */ 205 hadintr++; 206 collabort(); 207 fputs("Interrupt\n", stderr); 208 goto err; 209 case 'x': 210 /* 211 * Force a quit of sending mail. 212 * Do not save the message. 213 */ 214 goto err; 215 case 'h': 216 /* 217 * Grab a bunch of headers. 218 */ 219 grabh(hp, GTO|GSUBJECT|GCC|GBCC); 220 goto cont; 221 case 't': 222 /* 223 * Add to the To list. 224 */ 225 hp->h_to = cat(hp->h_to, extract(&linebuf[2], GTO)); 226 break; 227 case 's': 228 /* 229 * Set the Subject list. 230 */ 231 cp = &linebuf[2]; 232 while (isspace(*cp)) 233 cp++; 234 hp->h_subject = savestr(cp); 235 break; 236 case 'c': 237 /* 238 * Add to the CC list. 239 */ 240 hp->h_cc = cat(hp->h_cc, extract(&linebuf[2], GCC)); 241 break; 242 case 'b': 243 /* 244 * Add stuff to blind carbon copies list. 245 */ 246 hp->h_bcc = cat(hp->h_bcc, extract(&linebuf[2], GBCC)); 247 break; 248 case 'd': 249 linebuf[2] = '\0'; 250 strlcat(linebuf, getdeadletter(), sizeof(linebuf)); 251 /* fall into . . . */ 252 case 'r': 253 case '<': 254 /* 255 * Invoke a file: 256 * Search for the file name, 257 * then open it and copy the contents to collf. 258 */ 259 cp = &linebuf[2]; 260 while (isspace(*cp)) 261 cp++; 262 if (*cp == '\0') { 263 puts("Interpolate what file?"); 264 break; 265 } 266 cp = expand(cp); 267 if (cp == NULL) 268 break; 269 if (isdir(cp)) { 270 printf("%s: Directory\n", cp); 271 break; 272 } 273 if ((fbuf = Fopen(cp, "r")) == NULL) { 274 warn("%s", cp); 275 break; 276 } 277 printf("\"%s\" ", cp); 278 fflush(stdout); 279 lc = 0; 280 cc = 0; 281 while ((rc = readline(fbuf, linebuf, LINESIZE, NULL)) >= 0) { 282 if (rc != LINESIZE - 1) 283 lc++; 284 if ((t = putline(collf, linebuf, 285 rc != LINESIZE-1)) < 0) { 286 (void)Fclose(fbuf); 287 goto err; 288 } 289 cc += t; 290 } 291 (void)Fclose(fbuf); 292 printf("%d/%d\n", lc, cc); 293 break; 294 case 'w': 295 /* 296 * Write the message on a file. 297 */ 298 cp = &linebuf[2]; 299 while (*cp == ' ' || *cp == '\t') 300 cp++; 301 if (*cp == '\0') { 302 fputs("Write what file!?\n", stderr); 303 break; 304 } 305 if ((cp = expand(cp)) == NULL) 306 break; 307 rewind(collf); 308 exwrite(cp, collf, 1); 309 break; 310 case 'm': 311 case 'M': 312 case 'f': 313 case 'F': 314 /* 315 * Interpolate the named messages, if we 316 * are in receiving mail mode. Does the 317 * standard list processing garbage. 318 * If ~f is given, we don't shift over. 319 */ 320 if (forward(linebuf + 2, collf, tempname, c) < 0) 321 goto err; 322 goto cont; 323 case '?': 324 if ((fbuf = Fopen(_PATH_TILDE, "r")) == NULL) { 325 warn(_PATH_TILDE); 326 break; 327 } 328 while ((t = getc(fbuf)) != EOF) 329 (void)putchar(t); 330 (void)Fclose(fbuf); 331 break; 332 case 'p': 333 /* 334 * Print out the current state of the 335 * message without altering anything. 336 */ 337 rewind(collf); 338 puts("-------\nMessage contains:"); 339 puthead(hp, stdout, GTO|GSUBJECT|GCC|GBCC|GNL); 340 while ((t = getc(collf)) != EOF) 341 (void)putchar(t); 342 goto cont; 343 case '|': 344 /* 345 * Pipe message through command. 346 * Collect output as new message. 347 */ 348 rewind(collf); 349 mespipe(collf, &linebuf[2]); 350 goto cont; 351 case 'v': 352 case 'e': 353 /* 354 * Edit the current message. 355 * 'e' means to use EDITOR 356 * 'v' means to use VISUAL 357 */ 358 rewind(collf); 359 mesedit(collf, c); 360 goto cont; 361 } 362 } 363 364 if (value("interactive") != NULL) { 365 if (value("askcc") != NULL || value("askbcc") != NULL) { 366 if (value("askcc") != NULL) { 367 if (gethfromtty(hp, GCC) == -1) 368 goto err; 369 } 370 if (value("askbcc") != NULL) { 371 if (gethfromtty(hp, GBCC) == -1) 372 goto err; 373 } 374 } else { 375 puts("EOT"); 376 (void)fflush(stdout); 377 } 378 } 379 goto out; 380 err: 381 if (collf != NULL) { 382 (void)Fclose(collf); 383 collf = NULL; 384 } 385 out: 386 if (collf != NULL) 387 rewind(collf); 388 noreset--; 389 return(collf); 390 } 391 392 /* 393 * Write a file, ex-like if f set. 394 */ 395 int 396 exwrite(char *name, FILE *fp, int f) 397 { 398 FILE *of; 399 int c; 400 ssize_t cc, lc; 401 struct stat junk; 402 403 if (f) { 404 printf("\"%s\" ", name); 405 fflush(stdout); 406 } 407 if (stat(name, &junk) >= 0 && S_ISREG(junk.st_mode)) { 408 if (!f) 409 fprintf(stderr, "%s: ", name); 410 fputs("File exists\n", stderr); 411 return(-1); 412 } 413 if ((of = Fopen(name, "w")) == NULL) { 414 warn(NULL); 415 return(-1); 416 } 417 lc = 0; 418 cc = 0; 419 while ((c = getc(fp)) != EOF) { 420 cc++; 421 if (c == '\n') 422 lc++; 423 (void)putc(c, of); 424 if (ferror(of)) { 425 warn("%s", name); 426 (void)Fclose(of); 427 return(-1); 428 } 429 } 430 (void)Fclose(of); 431 printf("%lld/%lld\n", (long long)lc, (long long)cc); 432 fflush(stdout); 433 return(0); 434 } 435 436 /* 437 * Edit the message being collected on fp. 438 * On return, make the edit file the new temp file. 439 */ 440 void 441 mesedit(FILE *fp, int c) 442 { 443 FILE *nf; 444 struct sigaction oact; 445 sigset_t oset; 446 447 (void)ignoresig(SIGINT, &oact, &oset); 448 nf = run_editor(fp, (off_t)-1, c, 0); 449 if (nf != NULL) { 450 fseek(nf, 0L, SEEK_END); 451 collf = nf; 452 (void)Fclose(fp); 453 } 454 (void)sigprocmask(SIG_SETMASK, &oset, NULL); 455 (void)sigaction(SIGINT, &oact, NULL); 456 } 457 458 /* 459 * Pipe the message through the command. 460 * Old message is on stdin of command; 461 * New message collected from stdout. 462 * Sh -c must return 0 to accept the new message. 463 */ 464 void 465 mespipe(FILE *fp, char *cmd) 466 { 467 FILE *nf; 468 int fd; 469 char *shell, tempname[PATHSIZE]; 470 struct sigaction oact; 471 sigset_t oset; 472 473 (void)ignoresig(SIGINT, &oact, &oset); 474 (void)snprintf(tempname, sizeof(tempname), 475 "%s/mail.ReXXXXXXXXXX", tmpdir); 476 if ((fd = mkstemp(tempname)) == -1 || 477 (nf = Fdopen(fd, "w+")) == NULL) { 478 warn("%s", tempname); 479 goto out; 480 } 481 (void)rm(tempname); 482 /* 483 * stdin = current message. 484 * stdout = new message. 485 */ 486 shell = value("SHELL"); 487 if (run_command(shell, 488 0, fileno(fp), fileno(nf), "-c", cmd, NULL) < 0) { 489 (void)Fclose(nf); 490 goto out; 491 } 492 if (fsize(nf) == 0) { 493 fprintf(stderr, "No bytes from \"%s\" !?\n", cmd); 494 (void)Fclose(nf); 495 goto out; 496 } 497 /* 498 * Take new files. 499 */ 500 (void)fseek(nf, 0L, SEEK_END); 501 collf = nf; 502 (void)Fclose(fp); 503 out: 504 (void)sigprocmask(SIG_SETMASK, &oset, NULL); 505 (void)sigaction(SIGINT, &oact, NULL); 506 } 507 508 /* 509 * Interpolate the named messages into the current 510 * message, preceding each line with a tab. 511 * Return a count of the number of characters now in 512 * the message, or -1 if an error is encountered writing 513 * the message temporary. The flag argument is 'm' if we 514 * should shift over and 'f' if not. 515 */ 516 int 517 forward(char *ms, FILE *fp, char *fn, int f) 518 { 519 int *msgvec; 520 struct ignoretab *ig; 521 char *tabst; 522 523 msgvec = (int *)salloc((msgCount+1) * sizeof(*msgvec)); 524 if (msgvec == NULL) 525 return(0); 526 if (getmsglist(ms, msgvec, 0) < 0) 527 return(0); 528 if (*msgvec == 0) { 529 *msgvec = first(0, MMNORM); 530 if (*msgvec == 0) { 531 puts("No appropriate messages"); 532 return(0); 533 } 534 msgvec[1] = NULL; 535 } 536 if (tolower(f) == 'f') 537 tabst = NULL; 538 else if ((tabst = value("indentprefix")) == NULL) 539 tabst = "\t"; 540 ig = isupper(f) ? NULL : ignore; 541 fputs("Interpolating:", stdout); 542 for (; *msgvec != 0; msgvec++) { 543 struct message *mp = message + *msgvec - 1; 544 545 touch(mp); 546 printf(" %d", *msgvec); 547 if (sendmessage(mp, fp, ig, tabst) < 0) { 548 warn("%s", fn); 549 return(-1); 550 } 551 } 552 putchar('\n'); 553 return(0); 554 } 555 556 /* 557 * User aborted during message composition. 558 * Save the partial message in ~/dead.letter. 559 */ 560 int 561 collabort(void) 562 { 563 /* 564 * the control flow is subtle, because we can be called from ~q. 565 */ 566 if (hadintr == 0 && isatty(0)) { 567 if (value("ignore") != NULL) { 568 puts("@"); 569 fflush(stdout); 570 clearerr(stdin); 571 } else { 572 fflush(stdout); 573 fputs("\n(Interrupt -- one more to kill letter)\n", 574 stderr); 575 hadintr++; 576 } 577 return(0); 578 } 579 fflush(stdout); 580 rewind(collf); 581 if (value("nosave") == NULL) 582 savedeadletter(collf); 583 return(1); 584 } 585 586 void 587 savedeadletter(FILE *fp) 588 { 589 FILE *dbuf; 590 int c; 591 char *cp; 592 593 if (fsize(fp) == 0) 594 return; 595 cp = getdeadletter(); 596 c = umask(077); 597 dbuf = Fopen(cp, "a"); 598 (void)umask(c); 599 if (dbuf == NULL) 600 return; 601 while ((c = getc(fp)) != EOF) 602 (void)putc(c, dbuf); 603 (void)Fclose(dbuf); 604 rewind(fp); 605 } 606 607 int 608 gethfromtty(struct header *hp, int gflags) 609 { 610 611 hadintr = 0; 612 while (grabh(hp, gflags) != 0) { 613 if (collabort()) 614 return(-1); 615 } 616 return(0); 617 } 618