1 /* $NetBSD: send.c,v 1.28 2006/11/28 18:45: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.28 2006/11/28 18:45: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, *cp2; 126 int c; /* XXX - this variable is horribly abused */ 127 size_t length; 128 size_t prefixlen; 129 130 c = 0; 131 ignoring = 0; 132 prefixlen = 0; 133 134 /* 135 * Compute the prefix string, without trailing whitespace 136 */ 137 if (prefix != NULL) { 138 const char *dp, *dp2 = NULL; 139 for (dp = prefix; *dp; dp++) 140 if (*dp != ' ' && *dp != '\t') 141 dp2 = dp; 142 prefixlen = dp2 == 0 ? 0 : dp2 - prefix + 1; 143 } 144 ibuf = setinput(mp); 145 len = mp->m_size; 146 isheadflag = 1; 147 dostat = doign == 0 || !isign("status", doign); 148 infld = 0; 149 firstline = 1; 150 /* 151 * Process headers first 152 */ 153 #ifdef MIME_SUPPORT 154 if (mip) 155 obuf = mime_decode_header(mip); 156 #endif 157 while (len > 0 && isheadflag) { 158 if (fgets(line, LINESIZE, ibuf) == NULL) 159 break; 160 len -= length = strlen(line); 161 if (firstline) { 162 /* 163 * First line is the From line, so no headers 164 * there to worry about 165 */ 166 firstline = 0; 167 ignoring = doign == ignoreall; 168 #ifdef MIME_SUPPORT 169 /* XXX - ignore multipart boundary lines! */ 170 if (line[0] == '-' && line[1] == '-') 171 ignoring = 1; 172 #endif 173 } else if (line[0] == '\n') { 174 /* 175 * If line is blank, we've reached end of 176 * headers, so force out status: field 177 * and note that we are no longer in header 178 * fields 179 */ 180 if (dostat) { 181 statusput(mp, obuf, prefix); 182 dostat = 0; 183 } 184 isheadflag = 0; 185 ignoring = doign == ignoreall; 186 } else if (infld && (line[0] == ' ' || line[0] == '\t')) { 187 /* 188 * If this line is a continuation (via space or tab) 189 * of a previous header field, just echo it 190 * (unless the field should be ignored). 191 * In other words, nothing to do. 192 */ 193 } else { 194 /* 195 * Pick up the header field if we have one. 196 */ 197 for (cp = line; (c = *cp++) && c != ':' && !isspace(c); /*EMPTY*/) 198 continue; 199 cp2 = --cp; 200 while (isspace((unsigned char)*cp++)) 201 continue; 202 if (cp[-1] != ':') { 203 /* 204 * Not a header line, force out status: 205 * This happens in uucp style mail where 206 * there are no headers at all. 207 */ 208 if (dostat) { 209 statusput(mp, obuf, prefix); 210 dostat = 0; 211 } 212 if (doign != ignoreall) 213 /* add blank line */ 214 (void)putc('\n', obuf); 215 isheadflag = 0; 216 ignoring = 0; 217 } else { 218 /* 219 * If it is an ignored field and 220 * we care about such things, skip it. 221 */ 222 *cp2 = 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 *cp2 = c; /* restore */ 239 } 240 infld = 1; 241 } 242 } 243 if (!ignoring) { 244 /* 245 * Strip trailing whitespace from prefix 246 * if line is blank. 247 */ 248 if (prefix != NULL) { 249 if (length > 1) 250 (void)fputs(prefix, obuf); 251 else 252 (void)fwrite(prefix, sizeof *prefix, 253 prefixlen, obuf); 254 } 255 (void)fwrite(line, sizeof *line, length, 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 c = 0; /* This is needed if len == 0. It's used differently above. */ 271 if (doign == ignoreall) 272 len--; /* skip final blank line */ 273 274 if (prefix != NULL) 275 while (len > 0) { 276 if (fgets(line, LINESIZE, ibuf) == NULL) { 277 c = 0; 278 break; 279 } 280 len -= c = strlen(line); 281 /* 282 * Strip trailing whitespace from prefix 283 * if line is blank. 284 */ 285 if (c > 1) 286 (void)fputs(prefix, obuf); 287 else 288 (void)fwrite(prefix, sizeof *prefix, 289 prefixlen, obuf); 290 (void)fwrite(line, sizeof *line, (size_t)c, obuf); 291 if (ferror(obuf)) 292 return -1; 293 } 294 else 295 while (len > 0) { 296 c = (int)(len < LINESIZE ? len : LINESIZE); 297 if ((c = fread(line, sizeof *line, (size_t)c, ibuf)) == 0) 298 break; 299 len -= c; 300 if (fwrite(line, sizeof *line, (size_t)c, obuf) != (size_t)c) 301 return -1; 302 } 303 if (doign == ignoreall && c > 0 && line[c - 1] != '\n') 304 /* no final blank line */ 305 if ((c = getc(ibuf)) != EOF && putc(c, obuf) == EOF) 306 return -1; 307 return 0; 308 } 309 310 /* 311 * Fix the header by glopping all of the expanded names from 312 * the distribution list into the appropriate fields. 313 */ 314 static void 315 fixhead(struct header *hp, struct name *tolist) 316 { 317 struct name *np; 318 319 hp->h_to = NULL; 320 hp->h_cc = NULL; 321 hp->h_bcc = NULL; 322 for (np = tolist; np != NULL; np = np->n_flink) { 323 if (np->n_type & GDEL) 324 continue; /* Don't copy deleted addresses to the header */ 325 if ((np->n_type & GMASK) == GTO) 326 hp->h_to = 327 cat(hp->h_to, nalloc(np->n_name, np->n_type)); 328 else if ((np->n_type & GMASK) == GCC) 329 hp->h_cc = 330 cat(hp->h_cc, nalloc(np->n_name, np->n_type)); 331 else if ((np->n_type & GMASK) == GBCC) 332 hp->h_bcc = 333 cat(hp->h_bcc, nalloc(np->n_name, np->n_type)); 334 } 335 } 336 337 /* 338 * Format the given header line to not exceed 72 characters. 339 */ 340 static void 341 fmt(const char *str, struct name *np, FILE *fo, int comma) 342 { 343 int col, len; 344 345 comma = comma ? 1 : 0; 346 col = strlen(str); 347 if (col) 348 (void)fputs(str, fo); 349 for (; np != NULL; np = np->n_flink) { 350 if (np->n_flink == NULL) 351 comma = 0; 352 len = strlen(np->n_name); 353 col++; /* for the space */ 354 if (col + len + comma > 72 && col > 4) { 355 (void)fputs("\n ", fo); 356 col = 4; 357 } else 358 (void)putc(' ', fo); 359 (void)fputs(np->n_name, fo); 360 if (comma) 361 (void)putc(',', fo); 362 col += len + comma; 363 } 364 (void)putc('\n', fo); 365 } 366 367 /* 368 * Dump the to, subject, cc header on the 369 * passed file buffer. 370 */ 371 PUBLIC int 372 puthead(struct header *hp, FILE *fo, int w) 373 { 374 int gotcha; 375 376 gotcha = 0; 377 if (hp->h_to != NULL && w & GTO) 378 fmt("To:", hp->h_to, fo, w&GCOMMA), gotcha++; 379 if (hp->h_subject != NULL && w & GSUBJECT) 380 (void)fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++; 381 if (hp->h_smopts != NULL && w & GSMOPTS) 382 (void)fprintf(fo, "(sendmail options: %s)\n", detract(hp->h_smopts, GSMOPTS)), gotcha++; 383 if (hp->h_cc != NULL && w & GCC) 384 fmt("Cc:", hp->h_cc, fo, w&GCOMMA), gotcha++; 385 if (hp->h_bcc != NULL && w & GBCC) 386 fmt("Bcc:", hp->h_bcc, fo, w&GCOMMA), gotcha++; 387 if (hp->h_in_reply_to != NULL && w & GMISC) 388 (void)fprintf(fo, "In-Reply-To: %s\n", hp->h_in_reply_to), gotcha++; 389 if (hp->h_references != NULL && w & GMISC) 390 fmt("References:", hp->h_references, fo, w&GCOMMA), gotcha++; 391 #ifdef MIME_SUPPORT 392 if (w & GMIME && (hp->h_attach || value(ENAME_MIME_ENCODE_MSG))) 393 mime_putheader(fo, hp), gotcha++; 394 #endif 395 if (gotcha && w & GNL) 396 (void)putc('\n', fo); 397 return 0; 398 } 399 400 /* 401 * Prepend a header in front of the collected stuff 402 * and return the new file. 403 */ 404 static FILE * 405 infix(struct header *hp, FILE *fi) 406 { 407 FILE *nfo, *nfi; 408 int c, fd; 409 char tempname[PATHSIZE]; 410 411 (void)snprintf(tempname, sizeof(tempname), 412 "%s/mail.RsXXXXXXXXXX", tmpdir); 413 if ((fd = mkstemp(tempname)) == -1 || 414 (nfo = Fdopen(fd, "w")) == NULL) { 415 if (fd != -1) 416 (void)close(fd); 417 warn("%s", tempname); 418 return fi; 419 } 420 if ((nfi = Fopen(tempname, "r")) == NULL) { 421 warn("%s", tempname); 422 (void)Fclose(nfo); 423 (void)rm(tempname); 424 return fi; 425 } 426 (void)rm(tempname); 427 #ifdef MIME_SUPPORT 428 (void)puthead(hp, nfo, GTO|GSUBJECT|GCC|GBCC|GMISC|GMIME|GNL|GCOMMA); 429 #else 430 (void)puthead(hp, nfo, GTO|GSUBJECT|GCC|GBCC|GMISC|GNL|GCOMMA); 431 #endif 432 433 c = getc(fi); 434 while (c != EOF) { 435 (void)putc(c, nfo); 436 c = getc(fi); 437 } 438 if (ferror(fi)) { 439 warn("read"); 440 rewind(fi); 441 return fi; 442 } 443 (void)fflush(nfo); 444 if (ferror(nfo)) { 445 warn("%s", tempname); 446 (void)Fclose(nfo); 447 (void)Fclose(nfi); 448 rewind(fi); 449 return fi; 450 } 451 (void)Fclose(nfo); 452 (void)Fclose(fi); 453 rewind(nfi); 454 return nfi; 455 } 456 457 /* 458 * Save the outgoing mail on the passed file. 459 */ 460 /*ARGSUSED*/ 461 static int 462 savemail(const char name[], FILE *fi) 463 { 464 FILE *fo; 465 char buf[LINESIZE]; 466 int i; 467 time_t now; 468 mode_t m; 469 470 m = umask(077); 471 fo = Fopen(name, "a"); 472 (void)umask(m); 473 if (fo == NULL) { 474 warn("%s", name); 475 return -1; 476 } 477 (void)time(&now); 478 (void)fprintf(fo, "From %s %s", myname, ctime(&now)); 479 while ((i = fread(buf, 1, sizeof buf, fi)) > 0) 480 (void)fwrite(buf, 1, (size_t)i, fo); 481 (void)putc('\n', fo); 482 (void)fflush(fo); 483 if (ferror(fo)) 484 warn("%s", name); 485 (void)Fclose(fo); 486 rewind(fi); 487 return 0; 488 } 489 490 /* 491 * Mail a message on standard input to the people indicated 492 * in the passed header. (Internal interface). 493 */ 494 PUBLIC void 495 mail1(struct header *hp, int printheaders) 496 { 497 const char *cp; 498 int pid; 499 const char **namelist; 500 struct name *to; 501 FILE *mtf; 502 503 /* 504 * Collect user's mail from standard input. 505 * Get the result as mtf. 506 */ 507 if ((mtf = collect(hp, printheaders)) == NULL) 508 return; 509 if (value(ENAME_INTERACTIVE) != NULL) { 510 if (value(ENAME_ASKCC) != NULL || value(ENAME_ASKBCC) != NULL) { 511 if (value(ENAME_ASKCC) != NULL) 512 (void)grabh(hp, GCC); 513 if (value(ENAME_ASKBCC) != NULL) 514 (void)grabh(hp, GBCC); 515 } else { 516 (void)printf("EOT\n"); 517 (void)fflush(stdout); 518 } 519 } 520 if (fsize(mtf) == 0) { 521 if (value(ENAME_DONTSENDEMPTY) != NULL) 522 goto out; 523 if (hp->h_subject == NULL) 524 (void)printf("No message, no subject; hope that's ok\n"); 525 else 526 (void)printf("Null message body; hope that's ok\n"); 527 } 528 /* 529 * Now, take the user names from the combined 530 * to and cc lists and do all the alias 531 * processing. 532 */ 533 senderr = 0; 534 to = usermap(cat(hp->h_bcc, cat(hp->h_to, hp->h_cc))); 535 if (to == NULL) { 536 (void)printf("No recipients specified\n"); 537 senderr++; 538 } 539 #ifdef MIME_SUPPORT 540 /* 541 * If there are attachments, repackage the mail as a 542 * multi-part MIME message. 543 */ 544 if (hp->h_attach || value(ENAME_MIME_ENCODE_MSG)) 545 mtf = mime_encode(mtf, hp); 546 #endif 547 /* 548 * Look through the recipient list for names with /'s 549 * in them which we write to as files directly. 550 */ 551 to = outof(to, mtf, hp); 552 if (senderr) 553 savedeadletter(mtf); 554 to = elide(to); 555 if (count(to) == 0) 556 goto out; 557 fixhead(hp, to); 558 if ((mtf = infix(hp, mtf)) == NULL) { 559 (void)fprintf(stderr, ". . . message lost, sorry.\n"); 560 return; 561 } 562 if (hp->h_smopts == NULL) { 563 hp->h_smopts = get_smopts(to); 564 if (hp->h_smopts != NULL && 565 hp->h_smopts->n_name[0] != '\0' && 566 value(ENAME_SMOPTS_VERIFY) != NULL) 567 if (grabh(hp, GSMOPTS)) { 568 (void)printf("mail aborted!\n"); 569 savedeadletter(mtf); 570 goto out; 571 } 572 } 573 namelist = unpack(cat(hp->h_smopts, to)); 574 if (debug) { 575 const char **t; 576 577 (void)printf("Sendmail arguments:"); 578 for (t = namelist; *t != NULL; t++) 579 (void)printf(" \"%s\"", *t); 580 (void)printf("\n"); 581 goto out; 582 } 583 if ((cp = value(ENAME_RECORD)) != NULL) 584 (void)savemail(expand(cp), mtf); 585 /* 586 * Fork, set up the temporary mail file as standard 587 * input for "mail", and exec with the user list we generated 588 * far above. 589 */ 590 pid = fork(); 591 if (pid == -1) { 592 warn("fork"); 593 savedeadletter(mtf); 594 goto out; 595 } 596 if (pid == 0) { 597 sigset_t nset; 598 (void)sigemptyset(&nset); 599 (void)sigaddset(&nset, SIGHUP); 600 (void)sigaddset(&nset, SIGINT); 601 (void)sigaddset(&nset, SIGQUIT); 602 (void)sigaddset(&nset, SIGTSTP); 603 (void)sigaddset(&nset, SIGTTIN); 604 (void)sigaddset(&nset, SIGTTOU); 605 prepare_child(&nset, fileno(mtf), -1); 606 if ((cp = value(ENAME_SENDMAIL)) != NULL) 607 cp = expand(cp); 608 else 609 cp = _PATH_SENDMAIL; 610 (void)execv(cp, (char *const *)__UNCONST(namelist)); 611 warn("%s", cp); 612 _exit(1); 613 } 614 if (value(ENAME_VERBOSE) != NULL) 615 (void)wait_child(pid); 616 else 617 free_child(pid); 618 out: 619 (void)Fclose(mtf); 620 } 621 622 /* 623 * Interface between the argument list and the mail1 routine 624 * which does all the dirty work. 625 */ 626 PUBLIC int 627 mail(struct name *to, struct name *cc, struct name *bcc, 628 struct name *smopts, char *subject, struct attachment *attach) 629 { 630 struct header head; 631 632 /* ensure that all header fields are initially NULL */ 633 (void)memset(&head, 0, sizeof(head)); 634 635 head.h_to = to; 636 head.h_subject = subject; 637 head.h_cc = cc; 638 head.h_bcc = bcc; 639 head.h_smopts = smopts; 640 #ifdef MIME_SUPPORT 641 head.h_attach = attach; 642 #endif 643 mail1(&head, 0); 644 return 0; 645 } 646 647 /* 648 * Send mail to a bunch of user names. The interface is through 649 * the mail1 routine below. 650 */ 651 PUBLIC int 652 sendmail(void *v) 653 { 654 char *str = v; 655 struct header head; 656 657 /* ensure that all header fields are initially NULL */ 658 (void)memset(&head, 0, sizeof(head)); 659 660 head.h_to = extract(str, GTO); 661 662 mail1(&head, 0); 663 return 0; 664 } 665