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