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