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