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