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