1 /* 2 * Copyright (c) 1980, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 4. Neither the name of the University nor the names of its contributors 14 * may be used to endorse or promote products derived from this software 15 * without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #ifndef lint 31 #if 0 32 static char sccsid[] = "@(#)send.c 8.1 (Berkeley) 6/6/93"; 33 #endif 34 #endif /* not lint */ 35 #include <sys/cdefs.h> 36 __FBSDID("$FreeBSD$"); 37 38 #include "rcv.h" 39 #include "extern.h" 40 41 /* 42 * Mail -- a mail program 43 * 44 * Mail to others. 45 */ 46 47 /* 48 * Send message described by the passed pointer to the 49 * passed output buffer. Return -1 on error. 50 * Adjust the status: field if need be. 51 * If doign is given, suppress ignored header fields. 52 * prefix is a string to prepend to each output line. 53 */ 54 int 55 sendmessage(mp, obuf, doign, prefix) 56 struct message *mp; 57 FILE *obuf; 58 struct ignoretab *doign; 59 char *prefix; 60 { 61 long count; 62 FILE *ibuf; 63 char *cp, *cp2, line[LINESIZE]; 64 int ishead, infld, ignoring, dostat, firstline; 65 int c, length, prefixlen; 66 67 /* 68 * Compute the prefix string, without trailing whitespace 69 */ 70 if (prefix != NULL) { 71 cp2 = 0; 72 for (cp = prefix; *cp != '\0'; cp++) 73 if (*cp != ' ' && *cp != '\t') 74 cp2 = cp; 75 prefixlen = cp2 == NULL ? 0 : cp2 - prefix + 1; 76 } 77 ibuf = setinput(mp); 78 count = mp->m_size; 79 ishead = 1; 80 dostat = doign == 0 || !isign("status", doign); 81 infld = 0; 82 firstline = 1; 83 /* 84 * Process headers first 85 */ 86 while (count > 0 && ishead) { 87 if (fgets(line, sizeof(line), ibuf) == NULL) 88 break; 89 count -= length = strlen(line); 90 if (firstline) { 91 /* 92 * First line is the From line, so no headers 93 * there to worry about 94 */ 95 firstline = 0; 96 ignoring = doign == ignoreall; 97 } else if (line[0] == '\n') { 98 /* 99 * If line is blank, we've reached end of 100 * headers, so force out status: field 101 * and note that we are no longer in header 102 * fields 103 */ 104 if (dostat) { 105 statusput(mp, obuf, prefix); 106 dostat = 0; 107 } 108 ishead = 0; 109 ignoring = doign == ignoreall; 110 } else if (infld && (line[0] == ' ' || line[0] == '\t')) { 111 /* 112 * If this line is a continuation (via space or tab) 113 * of a previous header field, just echo it 114 * (unless the field should be ignored). 115 * In other words, nothing to do. 116 */ 117 } else { 118 /* 119 * Pick up the header field if we have one. 120 */ 121 for (cp = line; (c = *cp++) != '\0' && c != ':' && 122 !isspace((unsigned char)c);) 123 ; 124 cp2 = --cp; 125 while (isspace((unsigned char)*cp++)) 126 ; 127 if (cp[-1] != ':') { 128 /* 129 * Not a header line, force out status: 130 * This happens in uucp style mail where 131 * there are no headers at all. 132 */ 133 if (dostat) { 134 statusput(mp, obuf, prefix); 135 dostat = 0; 136 } 137 if (doign != ignoreall) 138 /* add blank line */ 139 (void)putc('\n', obuf); 140 ishead = 0; 141 ignoring = 0; 142 } else { 143 /* 144 * If it is an ignored field and 145 * we care about such things, skip it. 146 */ 147 *cp2 = '\0'; /* temporarily null terminate */ 148 if (doign && isign(line, doign)) 149 ignoring = 1; 150 else if ((line[0] == 's' || line[0] == 'S') && 151 strcasecmp(line, "status") == 0) { 152 /* 153 * If the field is "status," go compute 154 * and print the real Status: field 155 */ 156 if (dostat) { 157 statusput(mp, obuf, prefix); 158 dostat = 0; 159 } 160 ignoring = 1; 161 } else { 162 ignoring = 0; 163 *cp2 = c; /* restore */ 164 } 165 infld = 1; 166 } 167 } 168 if (!ignoring) { 169 /* 170 * Strip trailing whitespace from prefix 171 * if line is blank. 172 */ 173 if (prefix != NULL) { 174 if (length > 1) 175 fputs(prefix, obuf); 176 else 177 (void)fwrite(prefix, sizeof(*prefix), 178 prefixlen, obuf); 179 } 180 (void)fwrite(line, sizeof(*line), length, obuf); 181 if (ferror(obuf)) 182 return (-1); 183 } 184 } 185 /* 186 * Copy out message body 187 */ 188 if (doign == ignoreall) 189 count--; /* skip final blank line */ 190 if (prefix != NULL) 191 while (count > 0) { 192 if (fgets(line, sizeof(line), ibuf) == NULL) { 193 c = 0; 194 break; 195 } 196 count -= c = strlen(line); 197 /* 198 * Strip trailing whitespace from prefix 199 * if line is blank. 200 */ 201 if (c > 1) 202 fputs(prefix, obuf); 203 else 204 (void)fwrite(prefix, sizeof(*prefix), 205 prefixlen, obuf); 206 (void)fwrite(line, sizeof(*line), c, obuf); 207 if (ferror(obuf)) 208 return (-1); 209 } 210 else 211 while (count > 0) { 212 c = count < LINESIZE ? count : LINESIZE; 213 if ((c = fread(line, sizeof(*line), c, ibuf)) <= 0) 214 break; 215 count -= c; 216 if (fwrite(line, sizeof(*line), c, obuf) != c) 217 return (-1); 218 } 219 if (doign == ignoreall && c > 0 && line[c - 1] != '\n') 220 /* no final blank line */ 221 if ((c = getc(ibuf)) != EOF && putc(c, obuf) == EOF) 222 return (-1); 223 return (0); 224 } 225 226 /* 227 * Output a reasonable looking status field. 228 */ 229 void 230 statusput(mp, obuf, prefix) 231 struct message *mp; 232 FILE *obuf; 233 char *prefix; 234 { 235 char statout[3]; 236 char *cp = statout; 237 238 if (mp->m_flag & MREAD) 239 *cp++ = 'R'; 240 if ((mp->m_flag & MNEW) == 0) 241 *cp++ = 'O'; 242 *cp = '\0'; 243 if (statout[0] != '\0') 244 fprintf(obuf, "%sStatus: %s\n", 245 prefix == NULL ? "" : prefix, statout); 246 } 247 248 /* 249 * Interface between the argument list and the mail1 routine 250 * which does all the dirty work. 251 */ 252 int 253 mail(to, cc, bcc, smopts, subject, replyto) 254 struct name *to, *cc, *bcc, *smopts; 255 char *subject, *replyto; 256 { 257 struct header head; 258 259 head.h_to = to; 260 head.h_subject = subject; 261 head.h_cc = cc; 262 head.h_bcc = bcc; 263 head.h_smopts = smopts; 264 head.h_replyto = replyto; 265 head.h_inreplyto = NULL; 266 mail1(&head, 0); 267 return (0); 268 } 269 270 271 /* 272 * Send mail to a bunch of user names. The interface is through 273 * the mail routine below. 274 */ 275 int 276 sendmail(str) 277 char *str; 278 { 279 struct header head; 280 281 head.h_to = extract(str, GTO); 282 head.h_subject = NULL; 283 head.h_cc = NULL; 284 head.h_bcc = NULL; 285 head.h_smopts = NULL; 286 head.h_replyto = value("REPLYTO"); 287 head.h_inreplyto = NULL; 288 mail1(&head, 0); 289 return (0); 290 } 291 292 /* 293 * Mail a message on standard input to the people indicated 294 * in the passed header. (Internal interface). 295 */ 296 void 297 mail1(hp, printheaders) 298 struct header *hp; 299 int printheaders; 300 { 301 char *cp; 302 char *nbuf; 303 int pid; 304 char **namelist; 305 struct name *to, *nsto; 306 FILE *mtf; 307 308 /* 309 * Collect user's mail from standard input. 310 * Get the result as mtf. 311 */ 312 if ((mtf = collect(hp, printheaders)) == NULL) 313 return; 314 if (value("interactive") != NULL) { 315 if (value("askcc") != NULL || value("askbcc") != NULL) { 316 if (value("askcc") != NULL) 317 grabh(hp, GCC); 318 if (value("askbcc") != NULL) 319 grabh(hp, GBCC); 320 } else { 321 printf("EOT\n"); 322 (void)fflush(stdout); 323 } 324 } 325 if (fsize(mtf) == 0) { 326 if (value("dontsendempty") != NULL) 327 goto out; 328 if (hp->h_subject == NULL) 329 printf("No message, no subject; hope that's ok\n"); 330 else 331 printf("Null message body; hope that's ok\n"); 332 } 333 /* 334 * Now, take the user names from the combined 335 * to and cc lists and do all the alias 336 * processing. 337 */ 338 senderr = 0; 339 to = usermap(cat(hp->h_bcc, cat(hp->h_to, hp->h_cc))); 340 if (to == NULL) { 341 printf("No recipients specified\n"); 342 senderr++; 343 } 344 /* 345 * Look through the recipient list for names with /'s 346 * in them which we write to as files directly. 347 */ 348 to = outof(to, mtf, hp); 349 if (senderr) 350 savedeadletter(mtf); 351 to = elide(to); 352 if (count(to) == 0) 353 goto out; 354 if (value("recordrecip") != NULL) { 355 /* 356 * Before fixing the header, save old To:. 357 * We do this because elide above has sorted To: list, and 358 * we would like to save message in a file named by the first 359 * recipient the user has entered, not the one being the first 360 * after sorting happened. 361 */ 362 if ((nsto = malloc(sizeof(struct name))) == NULL) 363 err(1, "Out of memory"); 364 bcopy(hp->h_to, nsto, sizeof(struct name)); 365 } 366 fixhead(hp, to); 367 if ((mtf = infix(hp, mtf)) == NULL) { 368 fprintf(stderr, ". . . message lost, sorry.\n"); 369 return; 370 } 371 namelist = unpack(cat(hp->h_smopts, to)); 372 if (debug) { 373 char **t; 374 375 printf("Sendmail arguments:"); 376 for (t = namelist; *t != NULL; t++) 377 printf(" \"%s\"", *t); 378 printf("\n"); 379 goto out; 380 } 381 if (value("recordrecip") != NULL) { 382 /* 383 * Extract first recipient username from saved To: and use it 384 * as a filename. 385 */ 386 if ((nbuf = malloc(strlen(detract(nsto, 0)) + 1)) == NULL) 387 err(1, "Out of memory"); 388 if ((cp = yanklogin(detract(nsto, 0), nbuf)) != NULL) 389 (void)savemail(expand(nbuf), mtf); 390 free(nbuf); 391 free(nsto); 392 } else if ((cp = value("record")) != NULL) 393 (void)savemail(expand(cp), mtf); 394 /* 395 * Fork, set up the temporary mail file as standard 396 * input for "mail", and exec with the user list we generated 397 * far above. 398 */ 399 pid = fork(); 400 if (pid == -1) { 401 warn("fork"); 402 savedeadletter(mtf); 403 goto out; 404 } 405 if (pid == 0) { 406 sigset_t nset; 407 (void)sigemptyset(&nset); 408 (void)sigaddset(&nset, SIGHUP); 409 (void)sigaddset(&nset, SIGINT); 410 (void)sigaddset(&nset, SIGQUIT); 411 (void)sigaddset(&nset, SIGTSTP); 412 (void)sigaddset(&nset, SIGTTIN); 413 (void)sigaddset(&nset, SIGTTOU); 414 prepare_child(&nset, fileno(mtf), -1); 415 if ((cp = value("sendmail")) != NULL) 416 cp = expand(cp); 417 else 418 cp = _PATH_SENDMAIL; 419 execv(cp, namelist); 420 warn("%s", cp); 421 _exit(1); 422 } 423 if (value("verbose") != NULL) 424 (void)wait_child(pid); 425 else 426 free_child(pid); 427 out: 428 (void)Fclose(mtf); 429 } 430 431 /* 432 * Fix the header by glopping all of the expanded names from 433 * the distribution list into the appropriate fields. 434 */ 435 void 436 fixhead(hp, tolist) 437 struct header *hp; 438 struct name *tolist; 439 { 440 struct name *np; 441 442 hp->h_to = NULL; 443 hp->h_cc = NULL; 444 hp->h_bcc = NULL; 445 for (np = tolist; np != NULL; np = np->n_flink) { 446 /* Don't copy deleted addresses to the header */ 447 if (np->n_type & GDEL) 448 continue; 449 if ((np->n_type & GMASK) == GTO) 450 hp->h_to = 451 cat(hp->h_to, nalloc(np->n_name, np->n_type)); 452 else if ((np->n_type & GMASK) == GCC) 453 hp->h_cc = 454 cat(hp->h_cc, nalloc(np->n_name, np->n_type)); 455 else if ((np->n_type & GMASK) == GBCC) 456 hp->h_bcc = 457 cat(hp->h_bcc, nalloc(np->n_name, np->n_type)); 458 } 459 } 460 461 /* 462 * Prepend a header in front of the collected stuff 463 * and return the new file. 464 */ 465 FILE * 466 infix(hp, fi) 467 struct header *hp; 468 FILE *fi; 469 { 470 FILE *nfo, *nfi; 471 int c, fd; 472 char tempname[PATHSIZE]; 473 474 (void)snprintf(tempname, sizeof(tempname), 475 "%s/mail.RsXXXXXXXXXX", tmpdir); 476 if ((fd = mkstemp(tempname)) == -1 || 477 (nfo = Fdopen(fd, "w")) == NULL) { 478 warn("%s", tempname); 479 return (fi); 480 } 481 if ((nfi = Fopen(tempname, "r")) == NULL) { 482 warn("%s", tempname); 483 (void)Fclose(nfo); 484 (void)rm(tempname); 485 return (fi); 486 } 487 (void)rm(tempname); 488 (void)puthead(hp, nfo, 489 GTO|GSUBJECT|GCC|GBCC|GREPLYTO|GINREPLYTO|GNL|GCOMMA); 490 c = getc(fi); 491 while (c != EOF) { 492 (void)putc(c, nfo); 493 c = getc(fi); 494 } 495 if (ferror(fi)) { 496 warnx("read"); 497 rewind(fi); 498 return (fi); 499 } 500 (void)fflush(nfo); 501 if (ferror(nfo)) { 502 warn("%s", tempname); 503 (void)Fclose(nfo); 504 (void)Fclose(nfi); 505 rewind(fi); 506 return (fi); 507 } 508 (void)Fclose(nfo); 509 (void)Fclose(fi); 510 rewind(nfi); 511 return (nfi); 512 } 513 514 /* 515 * Dump the to, subject, cc header on the 516 * passed file buffer. 517 */ 518 int 519 puthead(hp, fo, w) 520 struct header *hp; 521 FILE *fo; 522 int w; 523 { 524 int gotcha; 525 526 gotcha = 0; 527 if (hp->h_to != NULL && w & GTO) 528 fmt("To:", hp->h_to, fo, w&GCOMMA), gotcha++; 529 if (hp->h_subject != NULL && w & GSUBJECT) 530 fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++; 531 if (hp->h_cc != NULL && w & GCC) 532 fmt("Cc:", hp->h_cc, fo, w&GCOMMA), gotcha++; 533 if (hp->h_bcc != NULL && w & GBCC) 534 fmt("Bcc:", hp->h_bcc, fo, w&GCOMMA), gotcha++; 535 if (hp->h_replyto != NULL && w & GREPLYTO) 536 fprintf(fo, "Reply-To: %s\n", hp->h_replyto), gotcha++; 537 if (hp->h_inreplyto != NULL && w & GINREPLYTO) 538 fprintf(fo, "In-Reply-To: <%s>\n", hp->h_inreplyto), gotcha++; 539 if (gotcha && w & GNL) 540 (void)putc('\n', fo); 541 return (0); 542 } 543 544 /* 545 * Format the given header line to not exceed 72 characters. 546 */ 547 void 548 fmt(str, np, fo, comma) 549 const char *str; 550 struct name *np; 551 FILE *fo; 552 int comma; 553 { 554 int col, len; 555 556 comma = comma ? 1 : 0; 557 col = strlen(str); 558 if (col) 559 fputs(str, fo); 560 for (; np != NULL; np = np->n_flink) { 561 if (np->n_flink == NULL) 562 comma = 0; 563 len = strlen(np->n_name); 564 col++; /* for the space */ 565 if (col + len + comma > 72 && col > 4) { 566 fprintf(fo, "\n "); 567 col = 4; 568 } else 569 fprintf(fo, " "); 570 fputs(np->n_name, fo); 571 if (comma) 572 fprintf(fo, ","); 573 col += len + comma; 574 } 575 fprintf(fo, "\n"); 576 } 577 578 /* 579 * Save the outgoing mail on the passed file. 580 */ 581 582 /*ARGSUSED*/ 583 int 584 savemail(name, fi) 585 char name[]; 586 FILE *fi; 587 { 588 FILE *fo; 589 char buf[BUFSIZ]; 590 int i; 591 time_t now; 592 593 if ((fo = Fopen(name, "a")) == NULL) { 594 warn("%s", name); 595 return (-1); 596 } 597 (void)time(&now); 598 fprintf(fo, "From %s %s", myname, ctime(&now)); 599 while ((i = fread(buf, 1, sizeof(buf), fi)) > 0) 600 (void)fwrite(buf, 1, i, fo); 601 fprintf(fo, "\n"); 602 (void)fflush(fo); 603 if (ferror(fo)) 604 warn("%s", name); 605 (void)Fclose(fo); 606 rewind(fi); 607 return (0); 608 } 609