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