1 /* $NetBSD: send.c,v 1.32 2007/10/30 02:28:32 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 35 static char sccsid[] = "@(#)send.c 8.1 (Berkeley) 6/6/93"; 36 #else 37 __RCSID("$NetBSD: send.c,v 1.32 2007/10/30 02:28:32 christos Exp $"); 38 #endif 39 #endif /* not lint */ 40 41 #include "rcv.h" 42 #include "extern.h" 43 #ifdef MIME_SUPPORT 44 #include "mime.h" 45 #endif 46 47 48 /* 49 * Mail -- a mail program 50 * 51 * Mail to others. 52 */ 53 54 /* 55 * compare twwo name structures. Support for get_smopts. 56 */ 57 static int 58 namecmp(struct name *np1, struct name *np2) 59 { 60 for (/*EMPTY*/; np1 && np2; np1 = np1->n_flink, np2 = np2->n_flink) { 61 if (strcmp(np1->n_name, np2->n_name) != 0) 62 return 1; 63 } 64 return np1 || np2; 65 } 66 67 /* 68 * Get the sendmail options from the smopts list. 69 */ 70 static struct name * 71 get_smopts(struct name *to) 72 { 73 struct smopts_s *sp; 74 struct name *smargs, *np; 75 smargs = NULL; 76 for (np = to; np; np = np->n_flink) { 77 if ((sp = findsmopts(np->n_name, 0)) == NULL) 78 continue; 79 if (smargs == NULL) 80 smargs = sp->s_smopts; 81 else if (namecmp(smargs, sp->s_smopts) != 0) 82 return NULL; 83 } 84 if (smargs && 85 smargs->n_flink == NULL && 86 (smargs->n_name == NULL || smargs->n_name[0] == '\0')) 87 return NULL; 88 return smargs; 89 } 90 91 /* 92 * Output a reasonable looking status field. 93 */ 94 static void 95 statusput(struct message *mp, FILE *obuf, const char *prefix) 96 { 97 char statout[3]; 98 char *cp = statout; 99 100 if (mp->m_flag & MREAD) 101 *cp++ = 'R'; 102 if ((mp->m_flag & MNEW) == 0) 103 *cp++ = 'O'; 104 *cp = 0; 105 if (statout[0]) 106 (void)fprintf(obuf, "%sStatus: %s\n", 107 prefix == NULL ? "" : prefix, statout); 108 } 109 110 /* 111 * Send message described by the passed pointer to the 112 * passed output buffer. Return -1 on error. 113 * Adjust the status: field if need be. 114 * If doign is given, suppress ignored header fields. 115 * prefix is a string to prepend to each output line. 116 */ 117 PUBLIC int 118 sendmessage(struct message *mp, FILE *obuf, struct ignoretab *doign, 119 const char *prefix, struct mime_info *mip) 120 { 121 off_t len; 122 FILE *ibuf; 123 char line[LINESIZE]; 124 int isheadflag, infld, ignoring, dostat, firstline; 125 char *cp; 126 size_t prefixlen; 127 size_t linelen; 128 129 ignoring = 0; 130 prefixlen = 0; 131 132 /* 133 * Compute the prefix string, without trailing whitespace 134 */ 135 if (prefix != NULL) { 136 const char *dp, *dp2 = NULL; 137 for (dp = prefix; *dp; dp++) 138 if (!is_WSP(*dp)) 139 dp2 = dp; 140 prefixlen = dp2 == 0 ? 0 : dp2 - prefix + 1; 141 } 142 ibuf = setinput(mp); 143 len = mp->m_size; 144 isheadflag = 1; 145 dostat = doign == 0 || !isign("status", doign); 146 infld = 0; 147 firstline = 1; 148 /* 149 * Process headers first 150 */ 151 #ifdef MIME_SUPPORT 152 if (mip) 153 obuf = mime_decode_header(mip); 154 #endif 155 while (len > 0 && isheadflag) { 156 if (fgets(line, sizeof(line), ibuf) == NULL) 157 break; 158 len -= linelen = strlen(line); 159 if (firstline) { 160 /* 161 * First line is the From line, so no headers 162 * there to worry about 163 */ 164 firstline = 0; 165 ignoring = doign == ignoreall || doign == bouncetab; 166 #ifdef MIME_SUPPORT 167 /* XXX - ignore multipart boundary lines! */ 168 if (line[0] == '-' && line[1] == '-') 169 ignoring = 1; 170 #endif 171 } else if (line[0] == '\n') { 172 /* 173 * If line is blank, we've reached end of 174 * headers, so force out status: field 175 * and note that we are no longer in header 176 * fields 177 */ 178 if (dostat) { 179 statusput(mp, obuf, prefix); 180 dostat = 0; 181 } 182 isheadflag = 0; 183 ignoring = doign == ignoreall; 184 } else if (infld && is_WSP(line[0])) { 185 /* 186 * If this line is a continuation (via space or tab) 187 * of a previous header field, just echo it 188 * (unless the field should be ignored). 189 * In other words, nothing to do. 190 */ 191 } else { 192 /* 193 * Pick up the header field if we have one. 194 */ 195 char *save_cp; 196 for (cp = line; *cp && *cp != ':' && !is_WSP(*cp); cp++) 197 continue; 198 save_cp = cp; 199 cp = skip_WSP(cp); 200 if (*cp++ != ':') { 201 /* 202 * Not a header line, force out status: 203 * This happens in uucp style mail where 204 * there are no headers at all. 205 */ 206 if (dostat) { 207 statusput(mp, obuf, prefix); 208 dostat = 0; 209 } 210 if (doign != ignoreall) 211 /* add blank line */ 212 (void)putc('\n', obuf); 213 isheadflag = 0; 214 ignoring = 0; 215 } else { 216 /* 217 * If it is an ignored field and 218 * we care about such things, skip it. 219 */ 220 int save_c; 221 save_c = *save_cp; 222 *save_cp = 0; /* temporarily null terminate */ 223 if (doign && isign(line, doign)) 224 ignoring = 1; 225 else if ((line[0] == 's' || line[0] == 'S') && 226 strcasecmp(line, "status") == 0) { 227 /* 228 * If the field is "status," go compute 229 * and print the real Status: field 230 */ 231 if (dostat) { 232 statusput(mp, obuf, prefix); 233 dostat = 0; 234 } 235 ignoring = 1; 236 } else { 237 ignoring = 0; 238 } 239 infld = 1; 240 *save_cp = save_c; /* restore the line */ 241 } 242 } 243 if (!ignoring) { 244 /* 245 * Strip trailing whitespace from prefix 246 * if line is blank. 247 */ 248 if (prefix != NULL) { 249 if (linelen > 1) 250 (void)fputs(prefix, obuf); 251 else 252 (void)fwrite(prefix, sizeof(*prefix), 253 prefixlen, obuf); 254 } 255 (void)fwrite(line, sizeof(*line), linelen, obuf); 256 if (ferror(obuf)) 257 return -1; 258 } 259 } 260 /* 261 * Copy out message body 262 */ 263 #ifdef MIME_SUPPORT 264 if (mip) { 265 obuf = mime_decode_body(mip); 266 if (obuf == NULL) /* XXX - early out */ 267 return 0; 268 } 269 #endif 270 if (doign == ignoreall) 271 len--; /* skip final blank line */ 272 273 linelen = 0; /* needed for in case len == 0 */ 274 if (prefix != NULL) 275 while (len > 0) { 276 if (fgets(line, sizeof(line), ibuf) == NULL) { 277 linelen = 0; 278 break; 279 } 280 len -= linelen = strlen(line); 281 /* 282 * Strip trailing whitespace from prefix 283 * if line is blank. 284 */ 285 if (linelen > 1) 286 (void)fputs(prefix, obuf); 287 else 288 (void)fwrite(prefix, sizeof(*prefix), 289 prefixlen, obuf); 290 (void)fwrite(line, sizeof(*line), linelen, obuf); 291 if (ferror(obuf)) 292 return -1; 293 } 294 else 295 while (len > 0) { 296 linelen = len < sizeof(line) ? (size_t)len : sizeof(line); 297 if ((linelen = fread(line, sizeof(*line), linelen, ibuf)) == 0) 298 break; 299 len -= linelen; 300 if (fwrite(line, sizeof(*line), linelen, obuf) != linelen) 301 return -1; 302 } 303 if (doign == ignoreall && linelen > 0 && line[linelen - 1] != '\n') { 304 int c; 305 /* no final blank line */ 306 if ((c = getc(ibuf)) != EOF && putc(c, obuf) == EOF) 307 return -1; 308 } 309 return 0; 310 } 311 312 /* 313 * Fix the header by glopping all of the expanded names from 314 * the distribution list into the appropriate fields. 315 */ 316 static void 317 fixhead(struct header *hp, struct name *tolist) 318 { 319 struct name *np; 320 321 hp->h_to = NULL; 322 hp->h_cc = NULL; 323 hp->h_bcc = NULL; 324 for (np = tolist; np != NULL; np = np->n_flink) { 325 if (np->n_type & GDEL) 326 continue; /* Don't copy deleted addresses to the header */ 327 if ((np->n_type & GMASK) == GTO) 328 hp->h_to = 329 cat(hp->h_to, nalloc(np->n_name, np->n_type)); 330 else if ((np->n_type & GMASK) == GCC) 331 hp->h_cc = 332 cat(hp->h_cc, nalloc(np->n_name, np->n_type)); 333 else if ((np->n_type & GMASK) == GBCC) 334 hp->h_bcc = 335 cat(hp->h_bcc, nalloc(np->n_name, np->n_type)); 336 } 337 } 338 339 /* 340 * Format the given header line to not exceed 72 characters. 341 */ 342 static void 343 fmt(const char *str, struct name *np, FILE *fo, int comma) 344 { 345 int col, len; 346 347 comma = comma ? 1 : 0; 348 col = strlen(str); 349 if (col) 350 (void)fputs(str, fo); 351 for (/*EMPTY*/; np != NULL; np = np->n_flink) { 352 if (np->n_flink == NULL) 353 comma = 0; 354 len = strlen(np->n_name); 355 col++; /* for the space */ 356 if (col + len + comma > 72 && col > 4) { 357 (void)fputs("\n ", fo); 358 col = 4; 359 } else 360 (void)putc(' ', fo); 361 (void)fputs(np->n_name, fo); 362 if (comma) 363 (void)putc(',', fo); 364 col += len + comma; 365 } 366 (void)putc('\n', fo); 367 } 368 369 /* 370 * Dump the to, subject, cc header on the 371 * passed file buffer. 372 */ 373 PUBLIC int 374 puthead(struct header *hp, FILE *fo, int w) 375 { 376 int gotcha; 377 378 gotcha = 0; 379 if (hp->h_to != NULL && w & GTO) 380 fmt("To:", hp->h_to, fo, w&GCOMMA), gotcha++; 381 if (hp->h_subject != NULL && w & GSUBJECT) 382 (void)fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++; 383 if (hp->h_smopts != NULL && w & GSMOPTS) 384 (void)fprintf(fo, "(sendmail options: %s)\n", detract(hp->h_smopts, GSMOPTS)), gotcha++; 385 if (hp->h_cc != NULL && w & GCC) 386 fmt("Cc:", hp->h_cc, fo, w&GCOMMA), gotcha++; 387 if (hp->h_bcc != NULL && w & GBCC) 388 fmt("Bcc:", hp->h_bcc, fo, w&GCOMMA), gotcha++; 389 if (hp->h_in_reply_to != NULL && w & GMISC) 390 (void)fprintf(fo, "In-Reply-To: %s\n", hp->h_in_reply_to), gotcha++; 391 if (hp->h_references != NULL && w & GMISC) 392 fmt("References:", hp->h_references, fo, w&GCOMMA), gotcha++; 393 #ifdef MIME_SUPPORT 394 if (w & GMIME && (hp->h_attach || value(ENAME_MIME_ENCODE_MSG))) 395 mime_putheader(fo, hp), gotcha++; 396 #endif 397 if (gotcha && w & GNL) 398 (void)putc('\n', fo); 399 return 0; 400 } 401 402 /* 403 * Prepend a header in front of the collected stuff 404 * and return the new file. 405 */ 406 static FILE * 407 infix(struct header *hp, FILE *fi) 408 { 409 FILE *nfo, *nfi; 410 int c, fd; 411 char tempname[PATHSIZE]; 412 413 (void)snprintf(tempname, sizeof(tempname), 414 "%s/mail.RsXXXXXXXXXX", tmpdir); 415 if ((fd = mkstemp(tempname)) == -1 || 416 (nfo = Fdopen(fd, "w")) == NULL) { 417 if (fd != -1) 418 (void)close(fd); 419 warn("%s", tempname); 420 return fi; 421 } 422 if ((nfi = Fopen(tempname, "r")) == NULL) { 423 warn("%s", tempname); 424 (void)Fclose(nfo); 425 (void)rm(tempname); 426 return fi; 427 } 428 (void)rm(tempname); 429 #ifdef MIME_SUPPORT 430 (void)puthead(hp, nfo, GTO|GSUBJECT|GCC|GBCC|GMISC|GMIME|GNL|GCOMMA); 431 #else 432 (void)puthead(hp, nfo, GTO|GSUBJECT|GCC|GBCC|GMISC|GNL|GCOMMA); 433 #endif 434 435 c = getc(fi); 436 while (c != EOF) { 437 (void)putc(c, nfo); 438 c = getc(fi); 439 } 440 if (ferror(fi)) { 441 warn("read"); 442 rewind(fi); 443 return fi; 444 } 445 (void)fflush(nfo); 446 if (ferror(nfo)) { 447 warn("%s", tempname); 448 (void)Fclose(nfo); 449 (void)Fclose(nfi); 450 rewind(fi); 451 return fi; 452 } 453 (void)Fclose(nfo); 454 (void)Fclose(fi); 455 rewind(nfi); 456 return nfi; 457 } 458 459 /* 460 * Save the outgoing mail on the passed file. 461 * 462 * Take care not to save a valid headline or the mbox file will be 463 * broken. Prefix valid headlines with '>'. 464 * 465 * Note: most servers prefix any line starting with "From " in the 466 * body of the message. We are more restrictive to avoid header/body 467 * issues. 468 */ 469 /*ARGSUSED*/ 470 static int 471 savemail(const char name[], FILE *fi) 472 { 473 FILE *fo; 474 time_t now; 475 mode_t m; 476 char *line; 477 size_t linelen; 478 int afterblank; 479 480 m = umask(077); 481 fo = Fopen(name, "a"); 482 (void)umask(m); 483 if (fo == NULL) { 484 warn("%s", name); 485 return -1; 486 } 487 (void)time(&now); 488 (void)fprintf(fo, "From %s %s", myname, ctime(&now)); 489 afterblank = 0; 490 while ((line = fgetln(fi, &linelen)) != NULL) { 491 char c, *cp; 492 cp = line + linelen - 1; 493 if (afterblank && 494 linelen > sizeof("From . Aaa Aaa O0 00:00 0000") && 495 line[0] == 'F' && 496 line[1] == 'r' && 497 line[2] == 'o' && 498 line[3] == 'm' && 499 line[4] == ' ' && 500 *cp == '\n') { 501 c = *cp; 502 *cp = '\0'; 503 if (ishead(line)) 504 (void)fputc('>', fo); 505 *cp = c; 506 } 507 (void)fwrite(line, 1, linelen, fo); 508 afterblank = linelen == 1 && line[0] == '\n'; 509 } 510 (void)putc('\n', fo); 511 (void)fflush(fo); 512 if (ferror(fo)) 513 warn("%s", name); 514 (void)Fclose(fo); 515 rewind(fi); 516 return 0; 517 } 518 519 /* 520 * Mail a message that is already prepared in a file. 521 */ 522 PUBLIC void 523 mail2(FILE *mtf, const char **namelist) 524 { 525 int pid; 526 const char *cp; 527 528 if (debug) { 529 const char **t; 530 531 (void)printf("Sendmail arguments:"); 532 for (t = namelist; *t != NULL; t++) 533 (void)printf(" \"%s\"", *t); 534 (void)printf("\n"); 535 return; 536 } 537 if ((cp = value(ENAME_RECORD)) != NULL) 538 (void)savemail(expand(cp), mtf); 539 /* 540 * Fork, set up the temporary mail file as standard 541 * input for "mail", and exec with the user list we generated 542 * far above. 543 */ 544 pid = fork(); 545 switch (pid) { 546 case -1: 547 warn("fork"); 548 savedeadletter(mtf); 549 return; 550 case 0: 551 { 552 sigset_t nset; 553 (void)sigemptyset(&nset); 554 (void)sigaddset(&nset, SIGHUP); 555 (void)sigaddset(&nset, SIGINT); 556 (void)sigaddset(&nset, SIGQUIT); 557 (void)sigaddset(&nset, SIGTSTP); 558 (void)sigaddset(&nset, SIGTTIN); 559 (void)sigaddset(&nset, SIGTTOU); 560 prepare_child(&nset, fileno(mtf), -1); 561 if ((cp = value(ENAME_SENDMAIL)) != NULL) 562 cp = expand(cp); 563 else 564 cp = _PATH_SENDMAIL; 565 (void)execv(cp, (char *const *)__UNCONST(namelist)); 566 warn("%s", cp); 567 _exit(1); 568 break; /* Appease GCC */ 569 } 570 default: 571 if (value(ENAME_VERBOSE) != NULL) 572 (void)wait_child(pid); 573 else 574 free_child(pid); 575 break; 576 } 577 } 578 579 /* 580 * Mail a message on standard input to the people indicated 581 * in the passed header. (Internal interface). 582 */ 583 PUBLIC void 584 mail1(struct header *hp, int printheaders) 585 { 586 const char **namelist; 587 struct name *to; 588 FILE *mtf; 589 590 /* 591 * Collect user's mail from standard input. 592 * Get the result as mtf. 593 */ 594 if ((mtf = collect(hp, printheaders)) == NULL) 595 return; 596 597 if (value(ENAME_INTERACTIVE) != NULL) { 598 if (value(ENAME_ASKCC) != NULL || value(ENAME_ASKBCC) != NULL) { 599 if (value(ENAME_ASKCC) != NULL) 600 (void)grabh(hp, GCC); 601 if (value(ENAME_ASKBCC) != NULL) 602 (void)grabh(hp, GBCC); 603 } else { 604 (void)printf("EOT\n"); 605 (void)fflush(stdout); 606 } 607 } 608 if (fsize(mtf) == 0) { 609 if (value(ENAME_DONTSENDEMPTY) != NULL) 610 goto out; 611 if (hp->h_subject == NULL) 612 (void)printf("No message, no subject; hope that's ok\n"); 613 else 614 (void)printf("Null message body; hope that's ok\n"); 615 } 616 /* 617 * Now, take the user names from the combined 618 * to and cc lists and do all the alias 619 * processing. 620 */ 621 senderr = 0; 622 to = usermap(cat(hp->h_bcc, cat(hp->h_to, hp->h_cc))); 623 if (to == NULL) { 624 (void)printf("No recipients specified\n"); 625 senderr++; 626 } 627 #ifdef MIME_SUPPORT 628 /* 629 * If there are attachments, repackage the mail as a 630 * multi-part MIME message. 631 */ 632 if (hp->h_attach || value(ENAME_MIME_ENCODE_MSG)) 633 mtf = mime_encode(mtf, hp); 634 #endif 635 /* 636 * Look through the recipient list for names with /'s 637 * in them which we write to as files directly. 638 */ 639 to = outof(to, mtf, hp); 640 if (senderr) 641 savedeadletter(mtf); 642 to = elide(to); 643 if (count(to) == 0) 644 goto out; 645 fixhead(hp, to); 646 if ((mtf = infix(hp, mtf)) == NULL) { 647 (void)fprintf(stderr, ". . . message lost, sorry.\n"); 648 return; 649 } 650 if (hp->h_smopts == NULL) { 651 hp->h_smopts = get_smopts(to); 652 if (hp->h_smopts != NULL && 653 hp->h_smopts->n_name[0] != '\0' && 654 value(ENAME_SMOPTS_VERIFY) != NULL) 655 if (grabh(hp, GSMOPTS)) { 656 (void)printf("mail aborted!\n"); 657 savedeadletter(mtf); 658 goto out; 659 } 660 } 661 namelist = unpack(cat(hp->h_smopts, to)); 662 mail2(mtf, namelist); 663 out: 664 (void)Fclose(mtf); 665 } 666 667 /* 668 * Interface between the argument list and the mail1 routine 669 * which does all the dirty work. 670 */ 671 PUBLIC int 672 mail(struct name *to, struct name *cc, struct name *bcc, 673 struct name *smopts, char *subject, struct attachment *attach) 674 { 675 struct header head; 676 677 /* ensure that all header fields are initially NULL */ 678 (void)memset(&head, 0, sizeof(head)); 679 680 head.h_to = to; 681 head.h_subject = subject; 682 head.h_cc = cc; 683 head.h_bcc = bcc; 684 head.h_smopts = smopts; 685 #ifdef MIME_SUPPORT 686 head.h_attach = attach; 687 #endif 688 mail1(&head, 0); 689 return 0; 690 } 691 692 /* 693 * Send mail to a bunch of user names. The interface is through 694 * the mail1 routine above. 695 */ 696 PUBLIC int 697 sendmail(void *v) 698 { 699 char *str = v; 700 struct header head; 701 702 /* ensure that all header fields are initially NULL */ 703 (void)memset(&head, 0, sizeof(head)); 704 705 head.h_to = extract(str, GTO); 706 707 mail1(&head, 0); 708 return 0; 709 } 710