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