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