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