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