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