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