1 1.40 rillig /* $NetBSD: send.c,v 1.40 2025/07/09 16:59:54 rillig Exp $ */ 2 1.6 christos 3 1.1 cgd /* 4 1.4 deraadt * Copyright (c) 1980, 1993 5 1.4 deraadt * The Regents of the University of California. All rights reserved. 6 1.1 cgd * 7 1.1 cgd * Redistribution and use in source and binary forms, with or without 8 1.1 cgd * modification, are permitted provided that the following conditions 9 1.1 cgd * are met: 10 1.1 cgd * 1. Redistributions of source code must retain the above copyright 11 1.1 cgd * notice, this list of conditions and the following disclaimer. 12 1.1 cgd * 2. Redistributions in binary form must reproduce the above copyright 13 1.1 cgd * notice, this list of conditions and the following disclaimer in the 14 1.1 cgd * documentation and/or other materials provided with the distribution. 15 1.21 agc * 3. Neither the name of the University nor the names of its contributors 16 1.1 cgd * may be used to endorse or promote products derived from this software 17 1.1 cgd * without specific prior written permission. 18 1.1 cgd * 19 1.1 cgd * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 1.1 cgd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 1.1 cgd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 1.1 cgd * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 1.1 cgd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 1.1 cgd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 1.1 cgd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 1.1 cgd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 1.1 cgd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 1.1 cgd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 1.1 cgd * SUCH DAMAGE. 30 1.1 cgd */ 31 1.1 cgd 32 1.7 lukem #include <sys/cdefs.h> 33 1.1 cgd #ifndef lint 34 1.6 christos #if 0 35 1.6 christos static char sccsid[] = "@(#)send.c 8.1 (Berkeley) 6/6/93"; 36 1.6 christos #else 37 1.40 rillig __RCSID("$NetBSD: send.c,v 1.40 2025/07/09 16:59:54 rillig Exp $"); 38 1.6 christos #endif 39 1.1 cgd #endif /* not lint */ 40 1.1 cgd 41 1.34 christos #include <assert.h> 42 1.34 christos 43 1.1 cgd #include "rcv.h" 44 1.4 deraadt #include "extern.h" 45 1.26 christos #ifdef MIME_SUPPORT 46 1.26 christos #include "mime.h" 47 1.26 christos #endif 48 1.34 christos #include "sig.h" 49 1.27 christos 50 1.28 christos /* 51 1.28 christos * Mail -- a mail program 52 1.28 christos * 53 1.28 christos * Mail to others. 54 1.28 christos */ 55 1.28 christos 56 1.28 christos /* 57 1.28 christos * compare twwo name structures. Support for get_smopts. 58 1.28 christos */ 59 1.27 christos static int 60 1.27 christos namecmp(struct name *np1, struct name *np2) 61 1.27 christos { 62 1.27 christos for (/*EMPTY*/; np1 && np2; np1 = np1->n_flink, np2 = np2->n_flink) { 63 1.27 christos if (strcmp(np1->n_name, np2->n_name) != 0) 64 1.27 christos return 1; 65 1.27 christos } 66 1.27 christos return np1 || np2; 67 1.27 christos } 68 1.27 christos 69 1.28 christos /* 70 1.28 christos * Get the sendmail options from the smopts list. 71 1.28 christos */ 72 1.27 christos static struct name * 73 1.27 christos get_smopts(struct name *to) 74 1.27 christos { 75 1.27 christos struct smopts_s *sp; 76 1.27 christos struct name *smargs, *np; 77 1.27 christos smargs = NULL; 78 1.27 christos for (np = to; np; np = np->n_flink) { 79 1.27 christos if ((sp = findsmopts(np->n_name, 0)) == NULL) 80 1.27 christos continue; 81 1.27 christos if (smargs == NULL) 82 1.27 christos smargs = sp->s_smopts; 83 1.27 christos else if (namecmp(smargs, sp->s_smopts) != 0) 84 1.27 christos return NULL; 85 1.27 christos } 86 1.27 christos if (smargs && 87 1.27 christos smargs->n_flink == NULL && 88 1.27 christos (smargs->n_name == NULL || smargs->n_name[0] == '\0')) 89 1.27 christos return NULL; 90 1.27 christos return smargs; 91 1.27 christos } 92 1.1 cgd 93 1.1 cgd /* 94 1.28 christos * Output a reasonable looking status field. 95 1.1 cgd */ 96 1.28 christos static void 97 1.28 christos statusput(struct message *mp, FILE *obuf, const char *prefix) 98 1.28 christos { 99 1.28 christos char statout[3]; 100 1.28 christos char *cp = statout; 101 1.28 christos 102 1.28 christos if (mp->m_flag & MREAD) 103 1.28 christos *cp++ = 'R'; 104 1.28 christos if ((mp->m_flag & MNEW) == 0) 105 1.28 christos *cp++ = 'O'; 106 1.28 christos *cp = 0; 107 1.28 christos if (statout[0]) 108 1.28 christos (void)fprintf(obuf, "%sStatus: %s\n", 109 1.28 christos prefix == NULL ? "" : prefix, statout); 110 1.28 christos } 111 1.1 cgd 112 1.1 cgd /* 113 1.1 cgd * Send message described by the passed pointer to the 114 1.1 cgd * passed output buffer. Return -1 on error. 115 1.1 cgd * Adjust the status: field if need be. 116 1.1 cgd * If doign is given, suppress ignored header fields. 117 1.1 cgd * prefix is a string to prepend to each output line. 118 1.1 cgd */ 119 1.28 christos PUBLIC int 120 1.26 christos sendmessage(struct message *mp, FILE *obuf, struct ignoretab *doign, 121 1.26 christos const char *prefix, struct mime_info *mip) 122 1.1 cgd { 123 1.23 christos off_t len; 124 1.7 lukem FILE *ibuf; 125 1.1 cgd char line[LINESIZE]; 126 1.28 christos int isheadflag, infld, ignoring, dostat, firstline; 127 1.29 christos char *cp; 128 1.28 christos size_t prefixlen; 129 1.30 christos size_t linelen; 130 1.34 christos int rval; 131 1.28 christos 132 1.28 christos ignoring = 0; 133 1.28 christos prefixlen = 0; 134 1.34 christos rval = -1; 135 1.1 cgd 136 1.1 cgd /* 137 1.1 cgd * Compute the prefix string, without trailing whitespace 138 1.1 cgd */ 139 1.16 wiz if (prefix != NULL) { 140 1.22 christos const char *dp, *dp2 = NULL; 141 1.22 christos for (dp = prefix; *dp; dp++) 142 1.29 christos if (!is_WSP(*dp)) 143 1.22 christos dp2 = dp; 144 1.22 christos prefixlen = dp2 == 0 ? 0 : dp2 - prefix + 1; 145 1.1 cgd } 146 1.1 cgd ibuf = setinput(mp); 147 1.15 wiz len = mp->m_size; 148 1.15 wiz isheadflag = 1; 149 1.1 cgd dostat = doign == 0 || !isign("status", doign); 150 1.1 cgd infld = 0; 151 1.1 cgd firstline = 1; 152 1.34 christos 153 1.34 christos sig_check(); 154 1.1 cgd /* 155 1.1 cgd * Process headers first 156 1.1 cgd */ 157 1.26 christos #ifdef MIME_SUPPORT 158 1.26 christos if (mip) 159 1.26 christos obuf = mime_decode_header(mip); 160 1.26 christos #endif 161 1.15 wiz while (len > 0 && isheadflag) { 162 1.34 christos sig_check(); 163 1.34 christos if (fgets(line, (int)sizeof(line), ibuf) == NULL) 164 1.1 cgd break; 165 1.30 christos len -= linelen = strlen(line); 166 1.1 cgd if (firstline) { 167 1.29 christos /* 168 1.1 cgd * First line is the From line, so no headers 169 1.1 cgd * there to worry about 170 1.1 cgd */ 171 1.1 cgd firstline = 0; 172 1.30 christos ignoring = doign == ignoreall || doign == bouncetab; 173 1.26 christos #ifdef MIME_SUPPORT 174 1.26 christos /* XXX - ignore multipart boundary lines! */ 175 1.26 christos if (line[0] == '-' && line[1] == '-') 176 1.26 christos ignoring = 1; 177 1.26 christos #endif 178 1.1 cgd } else if (line[0] == '\n') { 179 1.1 cgd /* 180 1.1 cgd * If line is blank, we've reached end of 181 1.1 cgd * headers, so force out status: field 182 1.1 cgd * and note that we are no longer in header 183 1.1 cgd * fields 184 1.1 cgd */ 185 1.1 cgd if (dostat) { 186 1.1 cgd statusput(mp, obuf, prefix); 187 1.1 cgd dostat = 0; 188 1.1 cgd } 189 1.15 wiz isheadflag = 0; 190 1.1 cgd ignoring = doign == ignoreall; 191 1.29 christos } else if (infld && is_WSP(line[0])) { 192 1.1 cgd /* 193 1.1 cgd * If this line is a continuation (via space or tab) 194 1.1 cgd * of a previous header field, just echo it 195 1.1 cgd * (unless the field should be ignored). 196 1.1 cgd * In other words, nothing to do. 197 1.1 cgd */ 198 1.1 cgd } else { 199 1.1 cgd /* 200 1.1 cgd * Pick up the header field if we have one. 201 1.1 cgd */ 202 1.29 christos char *save_cp; 203 1.29 christos for (cp = line; *cp && *cp != ':' && !is_WSP(*cp); cp++) 204 1.28 christos continue; 205 1.29 christos save_cp = cp; 206 1.29 christos cp = skip_WSP(cp); 207 1.29 christos if (*cp++ != ':') { 208 1.1 cgd /* 209 1.1 cgd * Not a header line, force out status: 210 1.1 cgd * This happens in uucp style mail where 211 1.1 cgd * there are no headers at all. 212 1.1 cgd */ 213 1.1 cgd if (dostat) { 214 1.1 cgd statusput(mp, obuf, prefix); 215 1.1 cgd dostat = 0; 216 1.1 cgd } 217 1.1 cgd if (doign != ignoreall) 218 1.1 cgd /* add blank line */ 219 1.18 wiz (void)putc('\n', obuf); 220 1.15 wiz isheadflag = 0; 221 1.1 cgd ignoring = 0; 222 1.1 cgd } else { 223 1.1 cgd /* 224 1.1 cgd * If it is an ignored field and 225 1.1 cgd * we care about such things, skip it. 226 1.1 cgd */ 227 1.29 christos int save_c; 228 1.29 christos save_c = *save_cp; 229 1.29 christos *save_cp = 0; /* temporarily null terminate */ 230 1.1 cgd if (doign && isign(line, doign)) 231 1.1 cgd ignoring = 1; 232 1.1 cgd else if ((line[0] == 's' || line[0] == 'S') && 233 1.1 cgd strcasecmp(line, "status") == 0) { 234 1.1 cgd /* 235 1.1 cgd * If the field is "status," go compute 236 1.1 cgd * and print the real Status: field 237 1.1 cgd */ 238 1.1 cgd if (dostat) { 239 1.1 cgd statusput(mp, obuf, prefix); 240 1.1 cgd dostat = 0; 241 1.1 cgd } 242 1.1 cgd ignoring = 1; 243 1.1 cgd } else { 244 1.1 cgd ignoring = 0; 245 1.1 cgd } 246 1.1 cgd infld = 1; 247 1.30 christos *save_cp = save_c; /* restore the line */ 248 1.1 cgd } 249 1.1 cgd } 250 1.1 cgd if (!ignoring) { 251 1.1 cgd /* 252 1.1 cgd * Strip trailing whitespace from prefix 253 1.1 cgd * if line is blank. 254 1.1 cgd */ 255 1.16 wiz if (prefix != NULL) { 256 1.30 christos if (linelen > 1) 257 1.23 christos (void)fputs(prefix, obuf); 258 1.1 cgd else 259 1.30 christos (void)fwrite(prefix, sizeof(*prefix), 260 1.1 cgd prefixlen, obuf); 261 1.9 ross } 262 1.30 christos (void)fwrite(line, sizeof(*line), linelen, obuf); 263 1.1 cgd if (ferror(obuf)) 264 1.34 christos goto out; 265 1.1 cgd } 266 1.1 cgd } 267 1.34 christos sig_check(); 268 1.34 christos 269 1.1 cgd /* 270 1.1 cgd * Copy out message body 271 1.1 cgd */ 272 1.26 christos #ifdef MIME_SUPPORT 273 1.26 christos if (mip) { 274 1.26 christos obuf = mime_decode_body(mip); 275 1.34 christos sig_check(); 276 1.34 christos if (obuf == NULL) { /* XXX - early out */ 277 1.34 christos rval = 0; 278 1.34 christos goto out; 279 1.34 christos } 280 1.26 christos } 281 1.26 christos #endif 282 1.1 cgd if (doign == ignoreall) 283 1.15 wiz len--; /* skip final blank line */ 284 1.28 christos 285 1.30 christos linelen = 0; /* needed for in case len == 0 */ 286 1.34 christos if (prefix != NULL) { 287 1.15 wiz while (len > 0) { 288 1.34 christos sig_check(); 289 1.34 christos if (fgets(line, (int)sizeof(line), ibuf) == NULL) { 290 1.30 christos linelen = 0; 291 1.1 cgd break; 292 1.1 cgd } 293 1.30 christos len -= linelen = strlen(line); 294 1.1 cgd /* 295 1.1 cgd * Strip trailing whitespace from prefix 296 1.1 cgd * if line is blank. 297 1.1 cgd */ 298 1.30 christos if (linelen > 1) 299 1.23 christos (void)fputs(prefix, obuf); 300 1.1 cgd else 301 1.30 christos (void)fwrite(prefix, sizeof(*prefix), 302 1.1 cgd prefixlen, obuf); 303 1.31 christos (void)fwrite(line, sizeof(*line), linelen, obuf); 304 1.1 cgd if (ferror(obuf)) 305 1.34 christos goto out; 306 1.1 cgd } 307 1.34 christos } 308 1.34 christos else { 309 1.15 wiz while (len > 0) { 310 1.34 christos sig_check(); 311 1.33 lukem linelen = (size_t)len < sizeof(line) ? (size_t)len : sizeof(line); 312 1.34 christos if ((linelen = fread(line, sizeof(*line), linelen, ibuf)) == 0) { 313 1.1 cgd break; 314 1.34 christos } 315 1.30 christos len -= linelen; 316 1.30 christos if (fwrite(line, sizeof(*line), linelen, obuf) != linelen) 317 1.34 christos goto out; 318 1.1 cgd } 319 1.34 christos } 320 1.34 christos sig_check(); 321 1.30 christos if (doign == ignoreall && linelen > 0 && line[linelen - 1] != '\n') { 322 1.30 christos int c; 323 1.34 christos 324 1.1 cgd /* no final blank line */ 325 1.1 cgd if ((c = getc(ibuf)) != EOF && putc(c, obuf) == EOF) 326 1.34 christos goto out; 327 1.30 christos } 328 1.34 christos rval = 0; 329 1.34 christos out: 330 1.34 christos sig_check(); 331 1.34 christos return rval; 332 1.1 cgd } 333 1.1 cgd 334 1.1 cgd /* 335 1.28 christos * Fix the header by glopping all of the expanded names from 336 1.28 christos * the distribution list into the appropriate fields. 337 1.28 christos */ 338 1.28 christos static void 339 1.28 christos fixhead(struct header *hp, struct name *tolist) 340 1.28 christos { 341 1.28 christos struct name *np; 342 1.28 christos 343 1.28 christos hp->h_to = NULL; 344 1.28 christos hp->h_cc = NULL; 345 1.28 christos hp->h_bcc = NULL; 346 1.28 christos for (np = tolist; np != NULL; np = np->n_flink) { 347 1.28 christos if (np->n_type & GDEL) 348 1.28 christos continue; /* Don't copy deleted addresses to the header */ 349 1.28 christos if ((np->n_type & GMASK) == GTO) 350 1.28 christos hp->h_to = 351 1.28 christos cat(hp->h_to, nalloc(np->n_name, np->n_type)); 352 1.28 christos else if ((np->n_type & GMASK) == GCC) 353 1.28 christos hp->h_cc = 354 1.28 christos cat(hp->h_cc, nalloc(np->n_name, np->n_type)); 355 1.28 christos else if ((np->n_type & GMASK) == GBCC) 356 1.28 christos hp->h_bcc = 357 1.28 christos cat(hp->h_bcc, nalloc(np->n_name, np->n_type)); 358 1.28 christos } 359 1.28 christos } 360 1.28 christos 361 1.28 christos /* 362 1.28 christos * Format the given header line to not exceed 72 characters. 363 1.1 cgd */ 364 1.28 christos static void 365 1.34 christos fmt(const char *str, struct name *np, FILE *fo, size_t comma) 366 1.1 cgd { 367 1.34 christos size_t col; 368 1.34 christos size_t len; 369 1.1 cgd 370 1.28 christos comma = comma ? 1 : 0; 371 1.28 christos col = strlen(str); 372 1.28 christos if (col) 373 1.28 christos (void)fputs(str, fo); 374 1.29 christos for (/*EMPTY*/; np != NULL; np = np->n_flink) { 375 1.28 christos if (np->n_flink == NULL) 376 1.28 christos comma = 0; 377 1.28 christos len = strlen(np->n_name); 378 1.28 christos col++; /* for the space */ 379 1.28 christos if (col + len + comma > 72 && col > 4) { 380 1.28 christos (void)fputs("\n ", fo); 381 1.28 christos col = 4; 382 1.28 christos } else 383 1.28 christos (void)putc(' ', fo); 384 1.28 christos (void)fputs(np->n_name, fo); 385 1.28 christos if (comma) 386 1.28 christos (void)putc(',', fo); 387 1.28 christos col += len + comma; 388 1.28 christos } 389 1.28 christos (void)putc('\n', fo); 390 1.1 cgd } 391 1.1 cgd 392 1.1 cgd /* 393 1.34 christos * Formatting for extra_header lines: try to wrap them before 72 394 1.34 christos * characters. 395 1.34 christos * 396 1.34 christos * XXX - should this allow for quoting? 397 1.34 christos */ 398 1.34 christos static void 399 1.34 christos fmt2(const char *str, FILE *fo) 400 1.34 christos { 401 1.34 christos const char *p; 402 1.34 christos size_t col; 403 1.34 christos 404 1.34 christos col = 0; 405 1.34 christos p = strchr(str, ':'); 406 1.34 christos assert(p != NULL); /* this is a coding error */ 407 1.34 christos 408 1.34 christos while (str <= p) { /* output the header name */ 409 1.34 christos (void)putc(*str, fo); 410 1.34 christos str++; 411 1.34 christos col++; 412 1.34 christos } 413 1.34 christos assert(is_WSP(*str)); /* this is a coding error */ 414 1.34 christos 415 1.34 christos (void)putc(' ', fo); /* output a single space after the ':' */ 416 1.34 christos str = skip_WSP(str); 417 1.34 christos 418 1.34 christos while (*str != '\0') { 419 1.34 christos if (p <= str) { 420 1.34 christos p = strpbrk(str, " \t"); 421 1.34 christos if (p == NULL) 422 1.34 christos p = str + strlen(str); 423 1.34 christos if (col + (p - str) > 72 && col > 4) { 424 1.34 christos (void)fputs("\n ", fo); 425 1.34 christos col = 4; 426 1.34 christos str = skip_WSP(str); 427 1.34 christos continue; 428 1.34 christos } 429 1.34 christos } 430 1.34 christos (void)putc(*str, fo); 431 1.34 christos str++; 432 1.34 christos col++; 433 1.34 christos } 434 1.34 christos (void)putc('\n', fo); 435 1.34 christos } 436 1.34 christos 437 1.34 christos /* 438 1.28 christos * Dump the to, subject, cc header on the 439 1.28 christos * passed file buffer. 440 1.1 cgd */ 441 1.28 christos PUBLIC int 442 1.28 christos puthead(struct header *hp, FILE *fo, int w) 443 1.28 christos { 444 1.34 christos struct name *np; 445 1.28 christos int gotcha; 446 1.28 christos 447 1.28 christos gotcha = 0; 448 1.28 christos if (hp->h_to != NULL && w & GTO) 449 1.34 christos fmt("To:", hp->h_to, fo, w & GCOMMA), gotcha++; 450 1.28 christos if (hp->h_subject != NULL && w & GSUBJECT) 451 1.28 christos (void)fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++; 452 1.28 christos if (hp->h_cc != NULL && w & GCC) 453 1.34 christos fmt("Cc:", hp->h_cc, fo, w & GCOMMA), gotcha++; 454 1.28 christos if (hp->h_bcc != NULL && w & GBCC) 455 1.34 christos fmt("Bcc:", hp->h_bcc, fo, w & GCOMMA), gotcha++; 456 1.28 christos if (hp->h_in_reply_to != NULL && w & GMISC) 457 1.28 christos (void)fprintf(fo, "In-Reply-To: %s\n", hp->h_in_reply_to), gotcha++; 458 1.28 christos if (hp->h_references != NULL && w & GMISC) 459 1.34 christos fmt("References:", hp->h_references, fo, w & GCOMMA), gotcha++; 460 1.34 christos if (hp->h_extra != NULL && w & GMISC) { 461 1.34 christos for (np = hp->h_extra; np != NULL; np = np->n_flink) 462 1.34 christos fmt2(np->n_name, fo); 463 1.34 christos gotcha++; 464 1.34 christos } 465 1.34 christos if (hp->h_smopts != NULL && w & GSMOPTS) 466 1.34 christos (void)fprintf(fo, "(sendmail options: %s)\n", detract(hp->h_smopts, GSMOPTS)), gotcha++; 467 1.26 christos #ifdef MIME_SUPPORT 468 1.28 christos if (w & GMIME && (hp->h_attach || value(ENAME_MIME_ENCODE_MSG))) 469 1.28 christos mime_putheader(fo, hp), gotcha++; 470 1.26 christos #endif 471 1.28 christos if (gotcha && w & GNL) 472 1.28 christos (void)putc('\n', fo); 473 1.28 christos return 0; 474 1.28 christos } 475 1.28 christos 476 1.28 christos /* 477 1.28 christos * Prepend a header in front of the collected stuff 478 1.28 christos * and return the new file. 479 1.28 christos */ 480 1.28 christos static FILE * 481 1.28 christos infix(struct header *hp, FILE *fi) 482 1.1 cgd { 483 1.28 christos FILE *nfo, *nfi; 484 1.28 christos int c, fd; 485 1.28 christos char tempname[PATHSIZE]; 486 1.1 cgd 487 1.28 christos (void)snprintf(tempname, sizeof(tempname), 488 1.28 christos "%s/mail.RsXXXXXXXXXX", tmpdir); 489 1.28 christos if ((fd = mkstemp(tempname)) == -1 || 490 1.39 christos (nfo = Fdopen(fd, "wef")) == NULL) { 491 1.28 christos if (fd != -1) 492 1.28 christos (void)close(fd); 493 1.28 christos warn("%s", tempname); 494 1.28 christos return fi; 495 1.28 christos } 496 1.39 christos if ((nfi = Fopen(tempname, "ref")) == NULL) { 497 1.28 christos warn("%s", tempname); 498 1.28 christos (void)Fclose(nfo); 499 1.28 christos (void)rm(tempname); 500 1.28 christos return fi; 501 1.28 christos } 502 1.28 christos (void)rm(tempname); 503 1.34 christos (void)puthead(hp, nfo, 504 1.34 christos GTO | GSUBJECT | GCC | GBCC | GMISC | GNL | GCOMMA 505 1.26 christos #ifdef MIME_SUPPORT 506 1.34 christos | GMIME 507 1.26 christos #endif 508 1.34 christos ); 509 1.28 christos 510 1.28 christos c = getc(fi); 511 1.28 christos while (c != EOF) { 512 1.28 christos (void)putc(c, nfo); 513 1.28 christos c = getc(fi); 514 1.28 christos } 515 1.28 christos if (ferror(fi)) { 516 1.28 christos warn("read"); 517 1.28 christos rewind(fi); 518 1.28 christos return fi; 519 1.28 christos } 520 1.28 christos (void)fflush(nfo); 521 1.28 christos if (ferror(nfo)) { 522 1.28 christos warn("%s", tempname); 523 1.28 christos (void)Fclose(nfo); 524 1.28 christos (void)Fclose(nfi); 525 1.28 christos rewind(fi); 526 1.28 christos return fi; 527 1.28 christos } 528 1.28 christos (void)Fclose(nfo); 529 1.28 christos (void)Fclose(fi); 530 1.28 christos rewind(nfi); 531 1.28 christos return nfi; 532 1.1 cgd } 533 1.1 cgd 534 1.1 cgd /* 535 1.28 christos * Save the outgoing mail on the passed file. 536 1.32 christos * 537 1.32 christos * Take care not to save a valid headline or the mbox file will be 538 1.32 christos * broken. Prefix valid headlines with '>'. 539 1.32 christos * 540 1.32 christos * Note: most servers prefix any line starting with "From " in the 541 1.32 christos * body of the message. We are more restrictive to avoid header/body 542 1.32 christos * issues. 543 1.1 cgd */ 544 1.28 christos /*ARGSUSED*/ 545 1.28 christos static int 546 1.28 christos savemail(const char name[], FILE *fi) 547 1.1 cgd { 548 1.28 christos FILE *fo; 549 1.28 christos time_t now; 550 1.28 christos mode_t m; 551 1.32 christos char *line; 552 1.32 christos size_t linelen; 553 1.32 christos int afterblank; 554 1.1 cgd 555 1.28 christos m = umask(077); 556 1.39 christos fo = Fopen(name, "aef"); 557 1.28 christos (void)umask(m); 558 1.28 christos if (fo == NULL) { 559 1.28 christos warn("%s", name); 560 1.28 christos return -1; 561 1.28 christos } 562 1.28 christos (void)time(&now); 563 1.28 christos (void)fprintf(fo, "From %s %s", myname, ctime(&now)); 564 1.32 christos afterblank = 0; 565 1.32 christos while ((line = fgetln(fi, &linelen)) != NULL) { 566 1.32 christos char c, *cp; 567 1.32 christos cp = line + linelen - 1; 568 1.32 christos if (afterblank && 569 1.32 christos linelen > sizeof("From . Aaa Aaa O0 00:00 0000") && 570 1.32 christos line[0] == 'F' && 571 1.32 christos line[1] == 'r' && 572 1.32 christos line[2] == 'o' && 573 1.32 christos line[3] == 'm' && 574 1.32 christos line[4] == ' ' && 575 1.32 christos *cp == '\n') { 576 1.32 christos c = *cp; 577 1.32 christos *cp = '\0'; 578 1.32 christos if (ishead(line)) 579 1.32 christos (void)fputc('>', fo); 580 1.32 christos *cp = c; 581 1.32 christos } 582 1.32 christos (void)fwrite(line, 1, linelen, fo); 583 1.32 christos afterblank = linelen == 1 && line[0] == '\n'; 584 1.32 christos } 585 1.28 christos (void)putc('\n', fo); 586 1.28 christos (void)fflush(fo); 587 1.28 christos if (ferror(fo)) 588 1.28 christos warn("%s", name); 589 1.28 christos (void)Fclose(fo); 590 1.28 christos rewind(fi); 591 1.28 christos return 0; 592 1.1 cgd } 593 1.1 cgd 594 1.1 cgd /* 595 1.30 christos * Mail a message that is already prepared in a file. 596 1.30 christos */ 597 1.30 christos PUBLIC void 598 1.30 christos mail2(FILE *mtf, const char **namelist) 599 1.30 christos { 600 1.30 christos int pid; 601 1.30 christos const char *cp; 602 1.30 christos 603 1.30 christos if (debug) { 604 1.30 christos const char **t; 605 1.30 christos 606 1.30 christos (void)printf("Sendmail arguments:"); 607 1.30 christos for (t = namelist; *t != NULL; t++) 608 1.30 christos (void)printf(" \"%s\"", *t); 609 1.30 christos (void)printf("\n"); 610 1.30 christos return; 611 1.30 christos } 612 1.30 christos if ((cp = value(ENAME_RECORD)) != NULL) 613 1.30 christos (void)savemail(expand(cp), mtf); 614 1.30 christos /* 615 1.30 christos * Fork, set up the temporary mail file as standard 616 1.30 christos * input for "mail", and exec with the user list we generated 617 1.30 christos * far above. 618 1.30 christos */ 619 1.30 christos pid = fork(); 620 1.30 christos switch (pid) { 621 1.30 christos case -1: 622 1.30 christos warn("fork"); 623 1.30 christos savedeadletter(mtf); 624 1.30 christos return; 625 1.30 christos case 0: 626 1.30 christos { 627 1.30 christos sigset_t nset; 628 1.30 christos (void)sigemptyset(&nset); 629 1.30 christos (void)sigaddset(&nset, SIGHUP); 630 1.30 christos (void)sigaddset(&nset, SIGINT); 631 1.30 christos (void)sigaddset(&nset, SIGQUIT); 632 1.30 christos (void)sigaddset(&nset, SIGTSTP); 633 1.30 christos (void)sigaddset(&nset, SIGTTIN); 634 1.30 christos (void)sigaddset(&nset, SIGTTOU); 635 1.30 christos prepare_child(&nset, fileno(mtf), -1); 636 1.30 christos if ((cp = value(ENAME_SENDMAIL)) != NULL) 637 1.30 christos cp = expand(cp); 638 1.30 christos else 639 1.30 christos cp = _PATH_SENDMAIL; 640 1.30 christos (void)execv(cp, (char *const *)__UNCONST(namelist)); 641 1.30 christos warn("%s", cp); 642 1.30 christos _exit(1); 643 1.30 christos } 644 1.30 christos default: 645 1.30 christos if (value(ENAME_VERBOSE) != NULL) 646 1.30 christos (void)wait_child(pid); 647 1.30 christos else 648 1.30 christos free_child(pid); 649 1.30 christos break; 650 1.30 christos } 651 1.30 christos } 652 1.30 christos 653 1.34 christos static struct name * 654 1.34 christos ncopy(struct name *np) 655 1.34 christos { 656 1.34 christos struct name *rv; 657 1.36 he struct name *lp; 658 1.34 christos struct name *tp; 659 1.34 christos 660 1.36 he lp = NULL; /* XXX gcc -Wuninitialized sh3 */ 661 1.34 christos rv = NULL; 662 1.34 christos for (/*EMPTY*/; np; np = np->n_flink) { 663 1.34 christos tp = nalloc(np->n_name, np->n_type); 664 1.34 christos if (rv == NULL) 665 1.34 christos rv = tp; 666 1.34 christos else { 667 1.34 christos lp->n_flink = tp; 668 1.34 christos tp->n_blink = lp; 669 1.34 christos } 670 1.34 christos lp = tp; 671 1.34 christos } 672 1.34 christos return rv; 673 1.34 christos } 674 1.34 christos 675 1.30 christos /* 676 1.1 cgd * Mail a message on standard input to the people indicated 677 1.1 cgd * in the passed header. (Internal interface). 678 1.1 cgd */ 679 1.28 christos PUBLIC void 680 1.14 wiz mail1(struct header *hp, int printheaders) 681 1.1 cgd { 682 1.22 christos const char **namelist; 683 1.1 cgd struct name *to; 684 1.1 cgd FILE *mtf; 685 1.1 cgd 686 1.1 cgd /* 687 1.1 cgd * Collect user's mail from standard input. 688 1.1 cgd * Get the result as mtf. 689 1.1 cgd */ 690 1.1 cgd if ((mtf = collect(hp, printheaders)) == NULL) 691 1.1 cgd return; 692 1.32 christos 693 1.34 christos /* 694 1.34 christos * Grab any extra header lines. Do this after collect() so 695 1.34 christos * that we can add header lines while collecting. 696 1.34 christos */ 697 1.34 christos hp->h_extra = ncopy(extra_headers); 698 1.34 christos 699 1.28 christos if (value(ENAME_INTERACTIVE) != NULL) { 700 1.28 christos if (value(ENAME_ASKCC) != NULL || value(ENAME_ASKBCC) != NULL) { 701 1.28 christos if (value(ENAME_ASKCC) != NULL) 702 1.23 christos (void)grabh(hp, GCC); 703 1.28 christos if (value(ENAME_ASKBCC) != NULL) 704 1.23 christos (void)grabh(hp, GBCC); 705 1.3 jtc } else { 706 1.23 christos (void)printf("EOT\n"); 707 1.18 wiz (void)fflush(stdout); 708 1.1 cgd } 709 1.9 ross } 710 1.9 ross if (fsize(mtf) == 0) { 711 1.28 christos if (value(ENAME_DONTSENDEMPTY) != NULL) 712 1.12 christos goto out; 713 1.16 wiz if (hp->h_subject == NULL) 714 1.23 christos (void)printf("No message, no subject; hope that's ok\n"); 715 1.1 cgd else 716 1.23 christos (void)printf("Null message body; hope that's ok\n"); 717 1.9 ross } 718 1.1 cgd /* 719 1.1 cgd * Now, take the user names from the combined 720 1.1 cgd * to and cc lists and do all the alias 721 1.1 cgd * processing. 722 1.1 cgd */ 723 1.1 cgd senderr = 0; 724 1.1 cgd to = usermap(cat(hp->h_bcc, cat(hp->h_to, hp->h_cc))); 725 1.17 wiz if (to == NULL) { 726 1.23 christos (void)printf("No recipients specified\n"); 727 1.1 cgd senderr++; 728 1.1 cgd } 729 1.26 christos #ifdef MIME_SUPPORT 730 1.26 christos /* 731 1.26 christos * If there are attachments, repackage the mail as a 732 1.26 christos * multi-part MIME message. 733 1.26 christos */ 734 1.26 christos if (hp->h_attach || value(ENAME_MIME_ENCODE_MSG)) 735 1.26 christos mtf = mime_encode(mtf, hp); 736 1.26 christos #endif 737 1.1 cgd /* 738 1.1 cgd * Look through the recipient list for names with /'s 739 1.1 cgd * in them which we write to as files directly. 740 1.1 cgd */ 741 1.1 cgd to = outof(to, mtf, hp); 742 1.1 cgd if (senderr) 743 1.1 cgd savedeadletter(mtf); 744 1.1 cgd to = elide(to); 745 1.1 cgd if (count(to) == 0) 746 1.1 cgd goto out; 747 1.1 cgd fixhead(hp, to); 748 1.1 cgd if ((mtf = infix(hp, mtf)) == NULL) { 749 1.23 christos (void)fprintf(stderr, ". . . message lost, sorry.\n"); 750 1.1 cgd return; 751 1.1 cgd } 752 1.27 christos if (hp->h_smopts == NULL) { 753 1.27 christos hp->h_smopts = get_smopts(to); 754 1.27 christos if (hp->h_smopts != NULL && 755 1.27 christos hp->h_smopts->n_name[0] != '\0' && 756 1.27 christos value(ENAME_SMOPTS_VERIFY) != NULL) 757 1.27 christos if (grabh(hp, GSMOPTS)) { 758 1.27 christos (void)printf("mail aborted!\n"); 759 1.27 christos savedeadletter(mtf); 760 1.27 christos goto out; 761 1.27 christos } 762 1.27 christos } 763 1.38 christos namelist = unpack(hp->h_smopts, to); 764 1.30 christos mail2(mtf, namelist); 765 1.30 christos out: 766 1.18 wiz (void)Fclose(mtf); 767 1.1 cgd } 768 1.1 cgd 769 1.1 cgd /* 770 1.28 christos * Interface between the argument list and the mail1 routine 771 1.28 christos * which does all the dirty work. 772 1.1 cgd */ 773 1.28 christos PUBLIC int 774 1.28 christos mail(struct name *to, struct name *cc, struct name *bcc, 775 1.28 christos struct name *smopts, char *subject, struct attachment *attach) 776 1.1 cgd { 777 1.28 christos struct header head; 778 1.1 cgd 779 1.28 christos /* ensure that all header fields are initially NULL */ 780 1.28 christos (void)memset(&head, 0, sizeof(head)); 781 1.1 cgd 782 1.28 christos head.h_to = to; 783 1.28 christos head.h_subject = subject; 784 1.28 christos head.h_cc = cc; 785 1.28 christos head.h_bcc = bcc; 786 1.28 christos head.h_smopts = smopts; 787 1.26 christos #ifdef MIME_SUPPORT 788 1.28 christos head.h_attach = attach; 789 1.26 christos #endif 790 1.28 christos mail1(&head, 0); 791 1.28 christos return 0; 792 1.1 cgd } 793 1.1 cgd 794 1.1 cgd /* 795 1.28 christos * Send mail to a bunch of user names. The interface is through 796 1.30 christos * the mail1 routine above. 797 1.1 cgd */ 798 1.28 christos PUBLIC int 799 1.28 christos sendmail(void *v) 800 1.1 cgd { 801 1.28 christos char *str = v; 802 1.28 christos struct header head; 803 1.1 cgd 804 1.28 christos /* ensure that all header fields are initially NULL */ 805 1.28 christos (void)memset(&head, 0, sizeof(head)); 806 1.1 cgd 807 1.28 christos head.h_to = extract(str, GTO); 808 1.1 cgd 809 1.28 christos mail1(&head, 0); 810 1.28 christos return 0; 811 1.1 cgd } 812