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