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