1 /* $OpenBSD: enqueue.c,v 1.56 2012/03/17 13:10:03 gilles Exp $ */ 2 3 /* 4 * Copyright (c) 2005 Henning Brauer <henning@bulabula.org> 5 * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 16 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 17 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 #include <sys/param.h> 21 #include <sys/queue.h> 22 #include <sys/socket.h> 23 #include <sys/tree.h> 24 #include <sys/types.h> 25 26 #include <ctype.h> 27 #include <err.h> 28 #include <event.h> 29 #include <imsg.h> 30 #include <inttypes.h> 31 #include <pwd.h> 32 #include <signal.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <time.h> 37 #include <unistd.h> 38 39 #include "smtpd.h" 40 41 extern struct imsgbuf *ibuf; 42 43 void usage(void); 44 static void sighdlr(int); 45 static void build_from(char *, struct passwd *); 46 static int parse_message(FILE *, int, int, FILE *); 47 static void parse_addr(char *, size_t, int); 48 static void parse_addr_terminal(int); 49 static char *qualify_addr(char *); 50 static void rcpt_add(char *); 51 static int open_connection(void); 52 static void get_responses(FILE *, int); 53 54 enum headerfields { 55 HDR_NONE, 56 HDR_FROM, 57 HDR_TO, 58 HDR_CC, 59 HDR_BCC, 60 HDR_SUBJECT, 61 HDR_DATE, 62 HDR_MSGID, 63 HDR_MIME_VERSION, 64 HDR_CONTENT_TYPE, 65 HDR_CONTENT_DISPOSITION, 66 HDR_CONTENT_TRANSFER_ENCODING, 67 HDR_USER_AGENT 68 }; 69 70 struct { 71 char *word; 72 enum headerfields type; 73 } keywords[] = { 74 { "From:", HDR_FROM }, 75 { "To:", HDR_TO }, 76 { "Cc:", HDR_CC }, 77 { "Bcc:", HDR_BCC }, 78 { "Subject:", HDR_SUBJECT }, 79 { "Date:", HDR_DATE }, 80 { "Message-Id:", HDR_MSGID }, 81 { "MIME-Version:", HDR_MIME_VERSION }, 82 { "Content-Type:", HDR_CONTENT_TYPE }, 83 { "Content-Disposition:", HDR_CONTENT_DISPOSITION }, 84 { "Content-Transfer-Encoding:", HDR_CONTENT_TRANSFER_ENCODING }, 85 { "User-Agent:", HDR_USER_AGENT }, 86 }; 87 88 #define LINESPLIT 990 89 #define SMTP_LINELEN 1000 90 #define TIMEOUTMSG "Timeout\n" 91 92 #define WSP(c) (c == ' ' || c == '\t') 93 94 int verbose = 0; 95 char host[MAXHOSTNAMELEN]; 96 char *user = NULL; 97 time_t timestamp; 98 99 struct { 100 int fd; 101 char *from; 102 char *fromname; 103 char **rcpts; 104 int rcpt_cnt; 105 int need_linesplit; 106 int saw_date; 107 int saw_msgid; 108 int saw_from; 109 int saw_mime_version; 110 int saw_content_type; 111 int saw_content_disposition; 112 int saw_content_transfer_encoding; 113 int saw_user_agent; 114 } msg; 115 116 struct { 117 u_int quote; 118 u_int comment; 119 u_int esc; 120 u_int brackets; 121 size_t wpos; 122 char buf[SMTP_LINELEN]; 123 } pstate; 124 125 static void 126 sighdlr(int sig) 127 { 128 if (sig == SIGALRM) { 129 write(STDERR_FILENO, TIMEOUTMSG, sizeof(TIMEOUTMSG)); 130 _exit(2); 131 } 132 } 133 134 static void 135 qp_encoded_write(FILE *fp, char *buf, size_t len) 136 { 137 while (len) { 138 if (*buf == '=') 139 fprintf(fp, "=3D"); 140 else if (*buf == ' ' || *buf == '\t') { 141 char *p = buf; 142 143 while (*p != '\n') { 144 if (*p != ' ' && *p != '\t') 145 break; 146 p++; 147 } 148 if (*p == '\n') 149 fprintf(fp, "=%2X", *buf & 0xff); 150 else 151 fprintf(fp, "%c", *buf & 0xff); 152 } 153 else if (! isprint(*buf) && *buf != '\n') 154 fprintf(fp, "=%2X", *buf & 0xff); 155 else 156 fprintf(fp, "%c", *buf); 157 buf++; 158 len--; 159 } 160 } 161 162 int 163 enqueue(int argc, char *argv[]) 164 { 165 int i, ch, tflag = 0, noheader; 166 char *fake_from = NULL, *buf; 167 struct passwd *pw; 168 FILE *fp, *fout; 169 size_t len; 170 char *line; 171 int dotted; 172 int inheaders = 0; 173 174 bzero(&msg, sizeof(msg)); 175 time(×tamp); 176 177 while ((ch = getopt(argc, argv, 178 "A:B:b:E::e:F:f:iJ::L:mo:p:qtvx")) != -1) { 179 switch (ch) { 180 case 'f': 181 fake_from = optarg; 182 break; 183 case 'F': 184 msg.fromname = optarg; 185 break; 186 case 't': 187 tflag = 1; 188 break; 189 case 'v': 190 verbose = 1; 191 break; 192 /* all remaining: ignored, sendmail compat */ 193 case 'A': 194 case 'B': 195 case 'b': 196 case 'E': 197 case 'e': 198 case 'i': 199 case 'L': 200 case 'm': 201 case 'o': 202 case 'p': 203 case 'x': 204 break; 205 case 'q': 206 /* XXX: implement "process all now" */ 207 return (0); 208 default: 209 usage(); 210 } 211 } 212 213 argc -= optind; 214 argv += optind; 215 216 if (gethostname(host, sizeof(host)) == -1) 217 err(1, "gethostname"); 218 if ((pw = getpwuid(getuid())) == NULL) 219 user = "anonymous"; 220 if (pw != NULL && (user = strdup(pw->pw_name)) == NULL) 221 err(1, "strdup"); 222 223 build_from(fake_from, pw); 224 225 while(argc > 0) { 226 rcpt_add(argv[0]); 227 argv++; 228 argc--; 229 } 230 231 signal(SIGALRM, sighdlr); 232 alarm(300); 233 234 fp = tmpfile(); 235 if (fp == NULL) 236 err(1, "tmpfile"); 237 noheader = parse_message(stdin, fake_from == NULL, tflag, fp); 238 239 if (msg.rcpt_cnt == 0) 240 errx(1, "no recipients"); 241 242 /* init session */ 243 rewind(fp); 244 245 if ((msg.fd = open_connection()) == -1) 246 errx(1, "server too busy"); 247 248 fout = fdopen(msg.fd, "a+"); 249 if (fout == NULL) 250 err(1, "fdopen"); 251 252 /* 253 * We need to call get_responses after every command because we don't 254 * support PIPELINING on the server-side yet. 255 */ 256 257 /* banner */ 258 get_responses(fout, 1); 259 260 fprintf(fout, "EHLO localhost\n"); 261 get_responses(fout, 1); 262 263 fprintf(fout, "MAIL FROM: <%s>\n", msg.from); 264 get_responses(fout, 1); 265 266 for (i = 0; i < msg.rcpt_cnt; i++) { 267 fprintf(fout, "RCPT TO: <%s>\n", msg.rcpts[i]); 268 get_responses(fout, 1); 269 } 270 271 fprintf(fout, "DATA\n"); 272 get_responses(fout, 1); 273 274 /* add From */ 275 if (!msg.saw_from) 276 fprintf(fout, "From: %s%s<%s>\n", 277 msg.fromname ? msg.fromname : "", 278 msg.fromname ? " " : "", 279 msg.from); 280 281 /* add Date */ 282 if (!msg.saw_date) 283 fprintf(fout, "Date: %s\n", time_to_text(timestamp)); 284 285 /* add Message-Id */ 286 if (!msg.saw_msgid) 287 fprintf(fout, "Message-Id: <%"PRIu64".enqueue@%s>\n", 288 generate_uid(), host); 289 290 if (msg.need_linesplit) { 291 /* we will always need to mime encode for long lines */ 292 if (!msg.saw_mime_version) 293 fprintf(fout, "MIME-Version: 1.0\n"); 294 if (!msg.saw_content_type) 295 fprintf(fout, "Content-Type: text/plain; charset=unknown-8bit\n"); 296 if (!msg.saw_content_disposition) 297 fprintf(fout, "Content-Disposition: inline\n"); 298 if (!msg.saw_content_transfer_encoding) 299 fprintf(fout, "Content-Transfer-Encoding: quoted-printable\n"); 300 } 301 if (!msg.saw_user_agent) 302 fprintf(fout, "User-Agent: OpenSMTPD enqueuer (Demoosh)\n"); 303 304 /* add separating newline */ 305 if (noheader) 306 fprintf(fout, "\n"); 307 else 308 inheaders = 1; 309 310 for (;;) { 311 buf = fgetln(fp, &len); 312 if (buf == NULL && ferror(fp)) 313 err(1, "fgetln"); 314 if (buf == NULL && feof(fp)) 315 break; 316 /* newlines have been normalized on first parsing */ 317 if (buf[len-1] != '\n') 318 errx(1, "expect EOL"); 319 320 dotted = 0; 321 if (buf[0] == '.') { 322 fputc('.', fout); 323 dotted = 1; 324 } 325 326 line = buf; 327 328 if (msg.saw_content_transfer_encoding || noheader || inheaders || !msg.need_linesplit) { 329 fprintf(fout, "%.*s", (int)len, line); 330 if (inheaders && buf[0] == '\n') 331 inheaders = 0; 332 continue; 333 } 334 335 /* we don't have a content transfer encoding, use our default */ 336 do { 337 if (len < LINESPLIT) { 338 qp_encoded_write(fout, line, len); 339 break; 340 } 341 else { 342 qp_encoded_write(fout, line, LINESPLIT - 2 - dotted); 343 fprintf(fout, "=\n"); 344 line += LINESPLIT - 2 - dotted; 345 len -= LINESPLIT - 2 - dotted; 346 } 347 } while (len); 348 } 349 fprintf(fout, ".\n"); 350 get_responses(fout, 1); 351 352 fprintf(fout, "QUIT\n"); 353 get_responses(fout, 1); 354 355 fclose(fp); 356 fclose(fout); 357 358 exit(0); 359 } 360 361 static void 362 get_responses(FILE *fin, int n) 363 { 364 char *buf; 365 size_t len; 366 int e; 367 368 fflush(fin); 369 if ((e = ferror(fin))) 370 errx(1, "ferror: %i", e); 371 372 while(n) { 373 buf = fgetln(fin, &len); 374 if (buf == NULL && ferror(fin)) 375 err(1, "fgetln"); 376 if (buf == NULL && feof(fin)) 377 break; 378 if (buf == NULL || len < 1) 379 err(1, "fgetln weird"); 380 381 /* account for \r\n linebreaks */ 382 if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') 383 buf[--len - 1] = '\n'; 384 385 if (len < 4) 386 errx(1, "bad response"); 387 388 if (verbose) 389 printf(">>> %.*s", (int)len, buf); 390 391 if (buf[3] == '-') 392 continue; 393 if (buf[0] != '2' && buf[0] != '3') 394 errx(1, "command failed: %.*s", (int)len, buf); 395 n--; 396 } 397 } 398 399 static void 400 build_from(char *fake_from, struct passwd *pw) 401 { 402 char *p; 403 404 if (fake_from == NULL) 405 msg.from = qualify_addr(user); 406 else { 407 if (fake_from[0] == '<') { 408 if (fake_from[strlen(fake_from) - 1] != '>') 409 errx(1, "leading < but no trailing >"); 410 fake_from[strlen(fake_from) - 1] = 0; 411 if ((p = malloc(strlen(fake_from))) == NULL) 412 err(1, "malloc"); 413 strlcpy(p, fake_from + 1, strlen(fake_from)); 414 415 msg.from = qualify_addr(p); 416 free(p); 417 } else 418 msg.from = qualify_addr(fake_from); 419 } 420 421 if (msg.fromname == NULL && fake_from == NULL && pw != NULL) { 422 int len, apos; 423 424 len = strcspn(pw->pw_gecos, ","); 425 if ((p = memchr(pw->pw_gecos, '&', len))) { 426 apos = p - pw->pw_gecos; 427 if (asprintf(&msg.fromname, "%.*s%s%.*s", 428 apos, pw->pw_gecos, 429 pw->pw_name, 430 len - apos - 1, p + 1) == -1) 431 err(1, NULL); 432 msg.fromname[apos] = toupper(msg.fromname[apos]); 433 } else { 434 if (asprintf(&msg.fromname, "%.*s", len, 435 pw->pw_gecos) == -1) 436 err(1, NULL); 437 } 438 } 439 } 440 441 static int 442 parse_message(FILE *fin, int get_from, int tflag, FILE *fout) 443 { 444 char *buf; 445 size_t len; 446 u_int i, cur = HDR_NONE; 447 u_int header_seen = 0, header_done = 0; 448 449 bzero(&pstate, sizeof(pstate)); 450 for (;;) { 451 buf = fgetln(fin, &len); 452 if (buf == NULL && ferror(fin)) 453 err(1, "fgetln"); 454 if (buf == NULL && feof(fin)) 455 break; 456 if (buf == NULL || len < 1) 457 err(1, "fgetln weird"); 458 459 /* account for \r\n linebreaks */ 460 if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') 461 buf[--len - 1] = '\n'; 462 463 if (len == 1 && buf[0] == '\n') /* end of header */ 464 header_done = 1; 465 466 if (!WSP(buf[0])) { /* whitespace -> continuation */ 467 if (cur == HDR_FROM) 468 parse_addr_terminal(1); 469 if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC) 470 parse_addr_terminal(0); 471 cur = HDR_NONE; 472 } 473 474 /* not really exact, if we are still in headers */ 475 if (len + (buf[len - 1] == '\n' ? 0 : 1) >= LINESPLIT) 476 msg.need_linesplit = 1; 477 478 for (i = 0; !header_done && cur == HDR_NONE && 479 i < nitems(keywords); i++) 480 if (len > strlen(keywords[i].word) && 481 !strncasecmp(buf, keywords[i].word, 482 strlen(keywords[i].word))) 483 cur = keywords[i].type; 484 485 if (cur != HDR_NONE) 486 header_seen = 1; 487 488 if (cur != HDR_BCC) { 489 fprintf(fout, "%.*s", (int)len, buf); 490 if (buf[len - 1] != '\n') 491 fputc('\n', fout); 492 if (ferror(fout)) 493 err(1, "write error"); 494 } 495 496 /* 497 * using From: as envelope sender is not sendmail compatible, 498 * but I really want it that way - maybe needs a knob 499 */ 500 if (cur == HDR_FROM) { 501 msg.saw_from++; 502 if (get_from) 503 parse_addr(buf, len, 1); 504 } 505 506 if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)) 507 parse_addr(buf, len, 0); 508 509 if (cur == HDR_DATE) 510 msg.saw_date++; 511 if (cur == HDR_MSGID) 512 msg.saw_msgid++; 513 if (cur == HDR_MIME_VERSION) 514 msg.saw_mime_version = 1; 515 if (cur == HDR_CONTENT_TYPE) 516 msg.saw_content_type = 1; 517 if (cur == HDR_CONTENT_DISPOSITION) 518 msg.saw_content_disposition = 1; 519 if (cur == HDR_CONTENT_TRANSFER_ENCODING) 520 msg.saw_content_transfer_encoding = 1; 521 if (cur == HDR_USER_AGENT) 522 msg.saw_user_agent = 1; 523 } 524 525 return (!header_seen); 526 } 527 528 static void 529 parse_addr(char *s, size_t len, int is_from) 530 { 531 size_t pos = 0; 532 int terminal = 0; 533 534 /* unless this is a continuation... */ 535 if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') { 536 /* ... skip over everything before the ':' */ 537 for (; pos < len && s[pos] != ':'; pos++) 538 ; /* nothing */ 539 /* ... and check & reset parser state */ 540 parse_addr_terminal(is_from); 541 } 542 543 /* skip over ':' ',' ';' and whitespace */ 544 for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' || 545 s[pos] == ',' || s[pos] == ';'); pos++) 546 ; /* nothing */ 547 548 for (; pos < len; pos++) { 549 if (!pstate.esc && !pstate.quote && s[pos] == '(') 550 pstate.comment++; 551 if (!pstate.comment && !pstate.esc && s[pos] == '"') 552 pstate.quote = !pstate.quote; 553 554 if (!pstate.comment && !pstate.quote && !pstate.esc) { 555 if (s[pos] == ':') { /* group */ 556 for(pos++; pos < len && WSP(s[pos]); pos++) 557 ; /* nothing */ 558 pstate.wpos = 0; 559 } 560 if (s[pos] == '\n' || s[pos] == '\r') 561 break; 562 if (s[pos] == ',' || s[pos] == ';') { 563 terminal = 1; 564 break; 565 } 566 if (s[pos] == '<') { 567 pstate.brackets = 1; 568 pstate.wpos = 0; 569 } 570 if (pstate.brackets && s[pos] == '>') 571 terminal = 1; 572 } 573 574 if (!pstate.comment && !terminal && (!(!(pstate.quote || 575 pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) { 576 if (pstate.wpos >= sizeof(pstate.buf)) 577 errx(1, "address exceeds buffer size"); 578 pstate.buf[pstate.wpos++] = s[pos]; 579 } 580 581 if (!pstate.quote && pstate.comment && s[pos] == ')') 582 pstate.comment--; 583 584 if (!pstate.esc && !pstate.comment && !pstate.quote && 585 s[pos] == '\\') 586 pstate.esc = 1; 587 else 588 pstate.esc = 0; 589 } 590 591 if (terminal) 592 parse_addr_terminal(is_from); 593 594 for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++) 595 ; /* nothing */ 596 597 if (pos < len) 598 parse_addr(s + pos, len - pos, is_from); 599 } 600 601 static void 602 parse_addr_terminal(int is_from) 603 { 604 if (pstate.comment || pstate.quote || pstate.esc) 605 errx(1, "syntax error in address"); 606 if (pstate.wpos) { 607 if (pstate.wpos >= sizeof(pstate.buf)) 608 errx(1, "address exceeds buffer size"); 609 pstate.buf[pstate.wpos] = '\0'; 610 if (is_from) 611 msg.from = qualify_addr(pstate.buf); 612 else 613 rcpt_add(pstate.buf); 614 pstate.wpos = 0; 615 } 616 } 617 618 static char * 619 qualify_addr(char *in) 620 { 621 char *out; 622 623 if (strlen(in) > 0 && strchr(in, '@') == NULL) { 624 if (asprintf(&out, "%s@%s", in, host) == -1) 625 err(1, "qualify asprintf"); 626 } else 627 if ((out = strdup(in)) == NULL) 628 err(1, "qualify strdup"); 629 630 return (out); 631 } 632 633 static void 634 rcpt_add(char *addr) 635 { 636 void *nrcpts; 637 638 if ((nrcpts = realloc(msg.rcpts, 639 sizeof(char *) * (msg.rcpt_cnt + 1))) == NULL) 640 err(1, "rcpt_add realloc"); 641 msg.rcpts = nrcpts; 642 msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr); 643 } 644 645 static int 646 open_connection(void) 647 { 648 struct imsg imsg; 649 int fd; 650 int n; 651 652 imsg_compose(ibuf, IMSG_SMTP_ENQUEUE, 0, 0, -1, NULL, 0); 653 654 while (ibuf->w.queued) 655 if (msgbuf_write(&ibuf->w) < 0) 656 err(1, "write error"); 657 658 while (1) { 659 if ((n = imsg_read(ibuf)) == -1) 660 errx(1, "imsg_read error"); 661 if (n == 0) 662 errx(1, "pipe closed"); 663 664 if ((n = imsg_get(ibuf, &imsg)) == -1) 665 errx(1, "imsg_get error"); 666 if (n == 0) 667 continue; 668 669 switch (imsg.hdr.type) { 670 case IMSG_CTL_OK: 671 break; 672 case IMSG_CTL_FAIL: 673 errx(1, "server disallowed submission request"); 674 default: 675 errx(1, "unexpected imsg reply type"); 676 } 677 678 fd = imsg.fd; 679 imsg_free(&imsg); 680 681 break; 682 } 683 684 return fd; 685 } 686 687 int 688 enqueue_offline(int argc, char *argv[]) 689 { 690 char path[MAXPATHLEN]; 691 FILE *fp; 692 int i, fd, ch; 693 694 if (ckdir(PATH_SPOOL PATH_OFFLINE, 01777, 0, 0, 0) == 0) 695 errx(1, "error in offline directory setup"); 696 697 if (! bsnprintf(path, sizeof(path), "%s%s/%lld.XXXXXXXXXX", PATH_SPOOL, 698 PATH_OFFLINE, (long long int) time(NULL))) 699 err(1, "snprintf"); 700 701 if ((fd = mkstemp(path)) == -1 || (fp = fdopen(fd, "w+")) == NULL) { 702 warn("cannot create temporary file %s", path); 703 if (fd != -1) 704 unlink(path); 705 exit(1); 706 } 707 708 for (i = 1; i < argc; i++) { 709 if (strchr(argv[i], '|') != NULL) { 710 warnx("%s contains illegal character", argv[i]); 711 unlink(path); 712 exit(1); 713 } 714 fprintf(fp, "%s%s", i == 1 ? "" : "|", argv[i]); 715 } 716 717 fprintf(fp, "\n"); 718 719 while ((ch = fgetc(stdin)) != EOF) 720 if (fputc(ch, fp) == EOF) { 721 warn("write error"); 722 unlink(path); 723 exit(1); 724 } 725 726 if (ferror(stdin)) { 727 warn("read error"); 728 unlink(path); 729 exit(1); 730 } 731 732 fclose(fp); 733 734 return (0); 735 } 736