1 /* $OpenBSD: enqueue.c,v 1.119 2021/06/14 17:58:15 eric Exp $ */ 2 3 /* 4 * Copyright (c) 2005 Henning Brauer <henning@bulabula.org> 5 * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> 6 * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> 7 * 8 * Permission to use, copy, modify, and distribute this software for any 9 * purpose with or without fee is hereby granted, provided that the above 10 * copyright notice and this permission notice appear in all copies. 11 * 12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 17 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 18 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 */ 20 21 #include <ctype.h> 22 #include <err.h> 23 #include <errno.h> 24 #include <pwd.h> 25 #include <stdlib.h> 26 #include <string.h> 27 #include <unistd.h> 28 29 #include "smtpd.h" 30 31 extern struct imsgbuf *ibuf; 32 33 void usage(void); 34 static void build_from(char *, struct passwd *); 35 static int parse_message(FILE *, int, int, FILE *); 36 static void parse_addr(char *, size_t, int); 37 static void parse_addr_terminal(int); 38 static char *qualify_addr(char *); 39 static void rcpt_add(char *); 40 static int open_connection(void); 41 static int get_responses(FILE *, int); 42 static int send_line(FILE *, int, char *, ...); 43 static int enqueue_offline(int, char *[], FILE *, FILE *); 44 static int savedeadletter(struct passwd *, FILE *); 45 46 extern int srv_connected(void); 47 48 enum headerfields { 49 HDR_NONE, 50 HDR_FROM, 51 HDR_TO, 52 HDR_CC, 53 HDR_BCC, 54 HDR_SUBJECT, 55 HDR_DATE, 56 HDR_MSGID, 57 HDR_MIME_VERSION, 58 HDR_CONTENT_TYPE, 59 HDR_CONTENT_DISPOSITION, 60 HDR_CONTENT_TRANSFER_ENCODING, 61 HDR_USER_AGENT 62 }; 63 64 struct { 65 char *word; 66 enum headerfields type; 67 } keywords[] = { 68 { "From:", HDR_FROM }, 69 { "To:", HDR_TO }, 70 { "Cc:", HDR_CC }, 71 { "Bcc:", HDR_BCC }, 72 { "Subject:", HDR_SUBJECT }, 73 { "Date:", HDR_DATE }, 74 { "Message-Id:", HDR_MSGID }, 75 { "MIME-Version:", HDR_MIME_VERSION }, 76 { "Content-Type:", HDR_CONTENT_TYPE }, 77 { "Content-Disposition:", HDR_CONTENT_DISPOSITION }, 78 { "Content-Transfer-Encoding:", HDR_CONTENT_TRANSFER_ENCODING }, 79 { "User-Agent:", HDR_USER_AGENT }, 80 }; 81 82 #define LINESPLIT 990 83 #define SMTP_LINELEN 1000 84 #define TIMEOUTMSG "Timeout\n" 85 86 #define WSP(c) (c == ' ' || c == '\t') 87 88 int verbose = 0; 89 static char host[HOST_NAME_MAX+1]; 90 char *user = NULL; 91 time_t timestamp; 92 93 struct { 94 int fd; 95 char *from; 96 char *fromname; 97 char **rcpts; 98 char *dsn_notify; 99 char *dsn_ret; 100 char *dsn_envid; 101 int rcpt_cnt; 102 int need_linesplit; 103 int saw_date; 104 int saw_msgid; 105 int saw_from; 106 int saw_mime_version; 107 int saw_content_type; 108 int saw_content_disposition; 109 int saw_content_transfer_encoding; 110 int saw_user_agent; 111 int noheader; 112 } msg; 113 114 struct { 115 uint quote; 116 uint comment; 117 uint esc; 118 uint brackets; 119 size_t wpos; 120 char buf[SMTP_LINELEN]; 121 } pstate; 122 123 #define QP_TEST_WRAP(fp, buf, linelen, size) do { \ 124 if (((linelen) += (size)) + 1 > 76) { \ 125 fprintf((fp), "=\r\n"); \ 126 if (buf[0] == '.') \ 127 fprintf((fp), "."); \ 128 (linelen) = (size); \ 129 } \ 130 } while (0) 131 132 /* RFC 2045 section 6.7 */ 133 static void 134 qp_encoded_write(FILE *fp, char *buf) 135 { 136 size_t linelen = 0; 137 138 for (;buf[0] != '\0' && buf[0] != '\n'; buf++) { 139 /* 140 * Point 3: Any TAB (HT) or SPACE characters on an encoded line 141 * MUST thus be followed on that line by a printable character. 142 * 143 * Ergo, only encode if the next character is EOL. 144 */ 145 if (buf[0] == ' ' || buf[0] == '\t') { 146 if (buf[1] == '\n') { 147 QP_TEST_WRAP(fp, buf, linelen, 3); 148 fprintf(fp, "=%2X", *buf & 0xff); 149 } else { 150 QP_TEST_WRAP(fp, buf, linelen, 1); 151 fprintf(fp, "%c", *buf & 0xff); 152 } 153 /* 154 * Point 1, with exclusion of point 2, skip EBCDIC NOTE. 155 * Do this after whitespace check, else they would match here. 156 */ 157 } else if (!((buf[0] >= 33 && buf[0] <= 60) || 158 (buf[0] >= 62 && buf[0] <= 126))) { 159 QP_TEST_WRAP(fp, buf, linelen, 3); 160 fprintf(fp, "=%2X", *buf & 0xff); 161 /* Point 2: 33 through 60 inclusive, and 62 through 126 */ 162 } else { 163 QP_TEST_WRAP(fp, buf, linelen, 1); 164 fprintf(fp, "%c", *buf); 165 } 166 } 167 fprintf(fp, "\r\n"); 168 } 169 170 int 171 enqueue(int argc, char *argv[], FILE *ofp) 172 { 173 int i, ch, tflag = 0; 174 char *fake_from = NULL, *buf = NULL; 175 struct passwd *pw; 176 FILE *fp = NULL, *fout; 177 size_t sz = 0, envid_sz = 0; 178 ssize_t len; 179 char *line; 180 int inheaders = 1; 181 int save_argc; 182 char **save_argv; 183 int no_getlogin = 0; 184 185 memset(&msg, 0, sizeof(msg)); 186 time(×tamp); 187 188 save_argc = argc; 189 save_argv = argv; 190 191 while ((ch = getopt(argc, argv, 192 "A:B:b:E::e:F:f:iJ::L:mN:o:p:qr:R:StvV:x")) != -1) { 193 switch (ch) { 194 case 'f': 195 fake_from = optarg; 196 break; 197 case 'F': 198 msg.fromname = optarg; 199 break; 200 case 'N': 201 msg.dsn_notify = optarg; 202 break; 203 case 'r': 204 fake_from = optarg; 205 break; 206 case 'R': 207 msg.dsn_ret = optarg; 208 break; 209 case 'S': 210 no_getlogin = 1; 211 break; 212 case 't': 213 tflag = 1; 214 break; 215 case 'v': 216 verbose = 1; 217 break; 218 case 'V': 219 msg.dsn_envid = optarg; 220 break; 221 /* all remaining: ignored, sendmail compat */ 222 case 'A': 223 case 'B': 224 case 'b': 225 case 'E': 226 case 'e': 227 case 'i': 228 case 'L': 229 case 'm': 230 case 'o': 231 case 'p': 232 case 'x': 233 break; 234 case 'q': 235 /* XXX: implement "process all now" */ 236 return (EX_SOFTWARE); 237 default: 238 usage(); 239 } 240 } 241 242 argc -= optind; 243 argv += optind; 244 245 if (getmailname(host, sizeof(host)) == -1) 246 errx(EX_NOHOST, "getmailname"); 247 if (no_getlogin) { 248 if ((pw = getpwuid(getuid())) == NULL) 249 user = "anonymous"; 250 if (pw != NULL) 251 user = xstrdup(pw->pw_name); 252 } 253 else { 254 uid_t ruid = getuid(); 255 256 if ((user = getlogin()) != NULL && *user != '\0') { 257 if ((pw = getpwnam(user)) == NULL || 258 (ruid != 0 && ruid != pw->pw_uid)) 259 pw = getpwuid(ruid); 260 } else if ((pw = getpwuid(ruid)) == NULL) { 261 user = "anonymous"; 262 } 263 user = xstrdup(pw ? pw->pw_name : user); 264 } 265 266 build_from(fake_from, pw); 267 268 while (argc > 0) { 269 rcpt_add(argv[0]); 270 argv++; 271 argc--; 272 } 273 274 if ((fp = tmpfile()) == NULL) 275 err(EX_UNAVAILABLE, "tmpfile"); 276 277 msg.noheader = parse_message(stdin, fake_from == NULL, tflag, fp); 278 279 if (msg.rcpt_cnt == 0) 280 errx(EX_SOFTWARE, "no recipients"); 281 282 /* init session */ 283 rewind(fp); 284 285 /* check if working in offline mode */ 286 /* If the server is not running, enqueue the message offline */ 287 288 if (!srv_connected()) { 289 if (pledge("stdio", NULL) == -1) 290 err(1, "pledge"); 291 292 return (enqueue_offline(save_argc, save_argv, fp, ofp)); 293 } 294 295 if ((msg.fd = open_connection()) == -1) 296 errx(EX_UNAVAILABLE, "server too busy"); 297 298 if (pledge("stdio wpath cpath", NULL) == -1) 299 err(1, "pledge"); 300 301 fout = fdopen(msg.fd, "a+"); 302 if (fout == NULL) 303 err(EX_UNAVAILABLE, "fdopen"); 304 305 /* 306 * We need to call get_responses after every command because we don't 307 * support PIPELINING on the server-side yet. 308 */ 309 310 /* banner */ 311 if (!get_responses(fout, 1)) 312 goto fail; 313 314 if (!send_line(fout, verbose, "EHLO localhost\r\n")) 315 goto fail; 316 if (!get_responses(fout, 1)) 317 goto fail; 318 319 if (msg.dsn_envid != NULL) 320 envid_sz = strlen(msg.dsn_envid); 321 322 if (!send_line(fout, verbose, "MAIL FROM:<%s> %s%s %s%s\r\n", 323 msg.from, 324 msg.dsn_ret ? "RET=" : "", 325 msg.dsn_ret ? msg.dsn_ret : "", 326 envid_sz ? "ENVID=" : "", 327 envid_sz ? msg.dsn_envid : "")) 328 goto fail; 329 if (!get_responses(fout, 1)) 330 goto fail; 331 332 for (i = 0; i < msg.rcpt_cnt; i++) { 333 if (!send_line(fout, verbose, "RCPT TO:<%s> %s%s\r\n", 334 msg.rcpts[i], 335 msg.dsn_notify ? "NOTIFY=" : "", 336 msg.dsn_notify ? msg.dsn_notify : "")) 337 goto fail; 338 if (!get_responses(fout, 1)) 339 goto fail; 340 } 341 342 if (!send_line(fout, verbose, "DATA\r\n")) 343 goto fail; 344 if (!get_responses(fout, 1)) 345 goto fail; 346 347 /* add From */ 348 if (!msg.saw_from && !send_line(fout, 0, "From: %s%s<%s>\r\n", 349 msg.fromname ? msg.fromname : "", msg.fromname ? " " : "", 350 msg.from)) 351 goto fail; 352 353 /* add Date */ 354 if (!msg.saw_date && !send_line(fout, 0, "Date: %s\r\n", 355 time_to_text(timestamp))) 356 goto fail; 357 358 if (msg.need_linesplit) { 359 /* we will always need to mime encode for long lines */ 360 if (!msg.saw_mime_version && !send_line(fout, 0, 361 "MIME-Version: 1.0\r\n")) 362 goto fail; 363 if (!msg.saw_content_type && !send_line(fout, 0, 364 "Content-Type: text/plain; charset=unknown-8bit\r\n")) 365 goto fail; 366 if (!msg.saw_content_disposition && !send_line(fout, 0, 367 "Content-Disposition: inline\r\n")) 368 goto fail; 369 if (!msg.saw_content_transfer_encoding && !send_line(fout, 0, 370 "Content-Transfer-Encoding: quoted-printable\r\n")) 371 goto fail; 372 } 373 374 /* add separating newline */ 375 if (msg.noheader) { 376 if (!send_line(fout, 0, "\r\n")) 377 goto fail; 378 inheaders = 0; 379 } 380 381 for (;;) { 382 if ((len = getline(&buf, &sz, fp)) == -1) { 383 if (feof(fp)) 384 break; 385 else 386 err(EX_UNAVAILABLE, "getline"); 387 } 388 389 /* newlines have been normalized on first parsing */ 390 if (buf[len-1] != '\n') 391 errx(EX_SOFTWARE, "expect EOL"); 392 len--; 393 394 if (buf[0] == '.') { 395 if (fputc('.', fout) == EOF) 396 goto fail; 397 } 398 399 line = buf; 400 401 if (inheaders) { 402 if (strncasecmp("from ", line, 5) == 0) 403 continue; 404 if (strncasecmp("return-path: ", line, 13) == 0) 405 continue; 406 } 407 408 if (msg.saw_content_transfer_encoding || msg.noheader || 409 inheaders || !msg.need_linesplit) { 410 if (!send_line(fout, 0, "%.*s\r\n", (int)len, line)) 411 goto fail; 412 if (inheaders && buf[0] == '\n') 413 inheaders = 0; 414 continue; 415 } 416 417 /* we don't have a content transfer encoding, use our default */ 418 qp_encoded_write(fout, line); 419 } 420 free(buf); 421 if (!send_line(fout, verbose, ".\r\n")) 422 goto fail; 423 if (!get_responses(fout, 1)) 424 goto fail; 425 426 if (!send_line(fout, verbose, "QUIT\r\n")) 427 goto fail; 428 if (!get_responses(fout, 1)) 429 goto fail; 430 431 fclose(fp); 432 fclose(fout); 433 434 exit(EX_OK); 435 436 fail: 437 if (pw) 438 savedeadletter(pw, fp); 439 exit(EX_SOFTWARE); 440 } 441 442 static int 443 get_responses(FILE *fin, int n) 444 { 445 char *buf = NULL; 446 size_t sz = 0; 447 ssize_t len; 448 int e, ret = 0; 449 450 fflush(fin); 451 if ((e = ferror(fin))) { 452 warnx("ferror: %d", e); 453 goto err; 454 } 455 456 while (n) { 457 if ((len = getline(&buf, &sz, fin)) == -1) { 458 if (ferror(fin)) { 459 warn("getline"); 460 goto err; 461 } else if (feof(fin)) 462 break; 463 else 464 err(EX_UNAVAILABLE, "getline"); 465 } 466 467 /* account for \r\n linebreaks */ 468 if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') 469 buf[--len - 1] = '\n'; 470 471 if (len < 4) { 472 warnx("bad response"); 473 goto err; 474 } 475 476 if (verbose) 477 printf("<<< %.*s", (int)len, buf); 478 479 if (buf[3] == '-') 480 continue; 481 if (buf[0] != '2' && buf[0] != '3') { 482 warnx("command failed: %.*s", (int)len, buf); 483 goto err; 484 } 485 n--; 486 } 487 488 ret = 1; 489 err: 490 free(buf); 491 return ret; 492 } 493 494 static int 495 send_line(FILE *fp, int v, char *fmt, ...) 496 { 497 int ret = 0; 498 va_list ap; 499 500 va_start(ap, fmt); 501 if (vfprintf(fp, fmt, ap) >= 0) 502 ret = 1; 503 va_end(ap); 504 505 if (ret && v) { 506 printf(">>> "); 507 va_start(ap, fmt); 508 vprintf(fmt, ap); 509 va_end(ap); 510 } 511 512 return (ret); 513 } 514 515 static void 516 build_from(char *fake_from, struct passwd *pw) 517 { 518 char *p; 519 520 if (fake_from == NULL) 521 msg.from = qualify_addr(user); 522 else { 523 if (fake_from[0] == '<') { 524 if (fake_from[strlen(fake_from) - 1] != '>') 525 errx(1, "leading < but no trailing >"); 526 fake_from[strlen(fake_from) - 1] = 0; 527 p = xstrdup(fake_from + 1); 528 529 msg.from = qualify_addr(p); 530 free(p); 531 } else 532 msg.from = qualify_addr(fake_from); 533 } 534 535 if (msg.fromname == NULL && fake_from == NULL && pw != NULL) { 536 int len, apos; 537 538 len = strcspn(pw->pw_gecos, ","); 539 if ((p = memchr(pw->pw_gecos, '&', len))) { 540 apos = p - pw->pw_gecos; 541 if (asprintf(&msg.fromname, "%.*s%s%.*s", 542 apos, pw->pw_gecos, 543 pw->pw_name, 544 len - apos - 1, p + 1) == -1) 545 err(1, NULL); 546 msg.fromname[apos] = toupper((unsigned char)msg.fromname[apos]); 547 } else { 548 if (asprintf(&msg.fromname, "%.*s", len, 549 pw->pw_gecos) == -1) 550 err(1, NULL); 551 } 552 } 553 } 554 555 static int 556 parse_message(FILE *fin, int get_from, int tflag, FILE *fout) 557 { 558 char *buf = NULL; 559 size_t sz = 0; 560 ssize_t len; 561 uint i, cur = HDR_NONE; 562 uint header_seen = 0, header_done = 0; 563 564 memset(&pstate, 0, sizeof(pstate)); 565 for (;;) { 566 if ((len = getline(&buf, &sz, fin)) == -1) { 567 if (feof(fin)) 568 break; 569 else 570 err(EX_UNAVAILABLE, "getline"); 571 } 572 573 /* account for \r\n linebreaks */ 574 if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') 575 buf[--len - 1] = '\n'; 576 577 if (len == 1 && buf[0] == '\n') /* end of header */ 578 header_done = 1; 579 580 if (!WSP(buf[0])) { /* whitespace -> continuation */ 581 if (cur == HDR_FROM) 582 parse_addr_terminal(1); 583 if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC) 584 parse_addr_terminal(0); 585 cur = HDR_NONE; 586 } 587 588 /* not really exact, if we are still in headers */ 589 if (len + (buf[len - 1] == '\n' ? 0 : 1) >= LINESPLIT) 590 msg.need_linesplit = 1; 591 592 for (i = 0; !header_done && cur == HDR_NONE && 593 i < nitems(keywords); i++) 594 if ((size_t)len > strlen(keywords[i].word) && 595 !strncasecmp(buf, keywords[i].word, 596 strlen(keywords[i].word))) 597 cur = keywords[i].type; 598 599 if (cur != HDR_NONE) 600 header_seen = 1; 601 602 if (cur != HDR_BCC) { 603 if (!send_line(fout, 0, "%.*s", (int)len, buf)) 604 err(1, "write error"); 605 if (buf[len - 1] != '\n') { 606 if (fputc('\n', fout) == EOF) 607 err(1, "write error"); 608 } 609 } 610 611 /* 612 * using From: as envelope sender is not sendmail compatible, 613 * but I really want it that way - maybe needs a knob 614 */ 615 if (cur == HDR_FROM) { 616 msg.saw_from++; 617 if (get_from) 618 parse_addr(buf, len, 1); 619 } 620 621 if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)) 622 parse_addr(buf, len, 0); 623 624 if (cur == HDR_DATE) 625 msg.saw_date++; 626 if (cur == HDR_MSGID) 627 msg.saw_msgid++; 628 if (cur == HDR_MIME_VERSION) 629 msg.saw_mime_version = 1; 630 if (cur == HDR_CONTENT_TYPE) 631 msg.saw_content_type = 1; 632 if (cur == HDR_CONTENT_DISPOSITION) 633 msg.saw_content_disposition = 1; 634 if (cur == HDR_CONTENT_TRANSFER_ENCODING) 635 msg.saw_content_transfer_encoding = 1; 636 if (cur == HDR_USER_AGENT) 637 msg.saw_user_agent = 1; 638 } 639 640 free(buf); 641 return (!header_seen); 642 } 643 644 static void 645 parse_addr(char *s, size_t len, int is_from) 646 { 647 size_t pos = 0; 648 int terminal = 0; 649 650 /* unless this is a continuation... */ 651 if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') { 652 /* ... skip over everything before the ':' */ 653 for (; pos < len && s[pos] != ':'; pos++) 654 ; /* nothing */ 655 /* ... and check & reset parser state */ 656 parse_addr_terminal(is_from); 657 } 658 659 /* skip over ':' ',' ';' and whitespace */ 660 for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' || 661 s[pos] == ',' || s[pos] == ';'); pos++) 662 ; /* nothing */ 663 664 for (; pos < len; pos++) { 665 if (!pstate.esc && !pstate.quote && s[pos] == '(') 666 pstate.comment++; 667 if (!pstate.comment && !pstate.esc && s[pos] == '"') 668 pstate.quote = !pstate.quote; 669 670 if (!pstate.comment && !pstate.quote && !pstate.esc) { 671 if (s[pos] == ':') { /* group */ 672 for (pos++; pos < len && WSP(s[pos]); pos++) 673 ; /* nothing */ 674 pstate.wpos = 0; 675 } 676 if (s[pos] == '\n' || s[pos] == '\r') 677 break; 678 if (s[pos] == ',' || s[pos] == ';') { 679 terminal = 1; 680 break; 681 } 682 if (s[pos] == '<') { 683 pstate.brackets = 1; 684 pstate.wpos = 0; 685 } 686 if (pstate.brackets && s[pos] == '>') 687 terminal = 1; 688 } 689 690 if (!pstate.comment && !terminal && (!(!(pstate.quote || 691 pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) { 692 if (pstate.wpos >= sizeof(pstate.buf)) 693 errx(1, "address exceeds buffer size"); 694 pstate.buf[pstate.wpos++] = s[pos]; 695 } 696 697 if (!pstate.quote && pstate.comment && s[pos] == ')') 698 pstate.comment--; 699 700 if (!pstate.esc && !pstate.comment && s[pos] == '\\') 701 pstate.esc = 1; 702 else 703 pstate.esc = 0; 704 } 705 706 if (terminal) 707 parse_addr_terminal(is_from); 708 709 for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++) 710 ; /* nothing */ 711 712 if (pos < len) 713 parse_addr(s + pos, len - pos, is_from); 714 } 715 716 static void 717 parse_addr_terminal(int is_from) 718 { 719 if (pstate.comment || pstate.quote || pstate.esc) 720 errx(1, "syntax error in address"); 721 if (pstate.wpos) { 722 if (pstate.wpos >= sizeof(pstate.buf)) 723 errx(1, "address exceeds buffer size"); 724 pstate.buf[pstate.wpos] = '\0'; 725 if (is_from) 726 msg.from = qualify_addr(pstate.buf); 727 else 728 rcpt_add(pstate.buf); 729 pstate.wpos = 0; 730 } 731 } 732 733 static char * 734 qualify_addr(char *in) 735 { 736 char *out; 737 738 if (strlen(in) > 0 && strchr(in, '@') == NULL) { 739 if (asprintf(&out, "%s@%s", in, host) == -1) 740 err(1, "qualify asprintf"); 741 } else 742 out = xstrdup(in); 743 744 return (out); 745 } 746 747 static void 748 rcpt_add(char *addr) 749 { 750 void *nrcpts; 751 char *p; 752 int n; 753 754 n = 1; 755 p = addr; 756 while ((p = strchr(p, ',')) != NULL) { 757 n++; 758 p++; 759 } 760 761 if ((nrcpts = reallocarray(msg.rcpts, 762 msg.rcpt_cnt + n, sizeof(char *))) == NULL) 763 err(1, "rcpt_add realloc"); 764 msg.rcpts = nrcpts; 765 766 while (n--) { 767 if ((p = strchr(addr, ',')) != NULL) 768 *p++ = '\0'; 769 msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr); 770 if (p == NULL) 771 break; 772 addr = p; 773 } 774 } 775 776 static int 777 open_connection(void) 778 { 779 struct imsg imsg; 780 int fd; 781 int n; 782 783 imsg_compose(ibuf, IMSG_CTL_SMTP_SESSION, IMSG_VERSION, 0, -1, NULL, 0); 784 785 while (ibuf->w.queued) 786 if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN) 787 err(1, "write error"); 788 789 while (1) { 790 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) 791 errx(1, "imsg_read error"); 792 if (n == 0) 793 errx(1, "pipe closed"); 794 795 if ((n = imsg_get(ibuf, &imsg)) == -1) 796 errx(1, "imsg_get error"); 797 if (n == 0) 798 continue; 799 800 switch (imsg.hdr.type) { 801 case IMSG_CTL_OK: 802 break; 803 case IMSG_CTL_FAIL: 804 errx(1, "server disallowed submission request"); 805 default: 806 errx(1, "unexpected imsg reply type"); 807 } 808 809 fd = imsg.fd; 810 imsg_free(&imsg); 811 812 break; 813 } 814 815 return fd; 816 } 817 818 static int 819 enqueue_offline(int argc, char *argv[], FILE *ifile, FILE *ofile) 820 { 821 int i, ch; 822 823 for (i = 1; i < argc; i++) { 824 if (strchr(argv[i], '|') != NULL) { 825 warnx("%s contains illegal character", argv[i]); 826 ftruncate(fileno(ofile), 0); 827 exit(EX_SOFTWARE); 828 } 829 if (fprintf(ofile, "%s%s", i == 1 ? "" : "|", argv[i]) < 0) 830 goto write_error; 831 } 832 833 if (fputc('\n', ofile) == EOF) 834 goto write_error; 835 836 while ((ch = fgetc(ifile)) != EOF) { 837 if (fputc(ch, ofile) == EOF) 838 goto write_error; 839 } 840 841 if (ferror(ifile)) { 842 warn("read error"); 843 ftruncate(fileno(ofile), 0); 844 exit(EX_UNAVAILABLE); 845 } 846 847 if (fclose(ofile) == EOF) 848 goto write_error; 849 850 return (EX_TEMPFAIL); 851 write_error: 852 warn("write error"); 853 ftruncate(fileno(ofile), 0); 854 exit(EX_UNAVAILABLE); 855 } 856 857 static int 858 savedeadletter(struct passwd *pw, FILE *in) 859 { 860 char buffer[PATH_MAX]; 861 FILE *fp; 862 char *buf = NULL; 863 size_t sz = 0; 864 ssize_t len; 865 866 (void)snprintf(buffer, sizeof buffer, "%s/dead.letter", pw->pw_dir); 867 868 if (fseek(in, 0, SEEK_SET) != 0) 869 return 0; 870 871 if ((fp = fopen(buffer, "w")) == NULL) 872 return 0; 873 874 /* add From */ 875 if (!msg.saw_from) 876 fprintf(fp, "From: %s%s<%s>\n", 877 msg.fromname ? msg.fromname : "", 878 msg.fromname ? " " : "", 879 msg.from); 880 881 /* add Date */ 882 if (!msg.saw_date) 883 fprintf(fp, "Date: %s\n", time_to_text(timestamp)); 884 885 if (msg.need_linesplit) { 886 /* we will always need to mime encode for long lines */ 887 if (!msg.saw_mime_version) 888 fprintf(fp, "MIME-Version: 1.0\n"); 889 if (!msg.saw_content_type) 890 fprintf(fp, "Content-Type: text/plain; " 891 "charset=unknown-8bit\n"); 892 if (!msg.saw_content_disposition) 893 fprintf(fp, "Content-Disposition: inline\n"); 894 if (!msg.saw_content_transfer_encoding) 895 fprintf(fp, "Content-Transfer-Encoding: " 896 "quoted-printable\n"); 897 } 898 899 /* add separating newline */ 900 if (msg.noheader) 901 fprintf(fp, "\n"); 902 903 while ((len = getline(&buf, &sz, in)) != -1) { 904 if (buf[len - 1] == '\n') 905 buf[len - 1] = '\0'; 906 fprintf(fp, "%s\n", buf); 907 } 908 909 free(buf); 910 fprintf(fp, "\n"); 911 fclose(fp); 912 return 1; 913 } 914