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