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