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