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