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