1 /* $NetBSD: smtpd_proxy.c,v 1.1.1.8 2014/07/06 19:27:57 tron Exp $ */ 2 3 /*++ 4 /* NAME 5 /* smtpd_proxy 3 6 /* SUMMARY 7 /* SMTP server pass-through proxy client 8 /* SYNOPSIS 9 /* #include <smtpd.h> 10 /* #include <smtpd_proxy.h> 11 /* 12 /* typedef struct { 13 /* .in +4 14 /* VSTREAM *stream; /* SMTP proxy or replay log */ 15 /* VSTRING *buffer; /* last SMTP proxy response */ 16 /* /* other fields... */ 17 /* .in -4 18 /* } SMTPD_PROXY; 19 /* 20 /* int smtpd_proxy_create(state, flags, service, timeout, 21 /* ehlo_name, mail_from) 22 /* SMTPD_STATE *state; 23 /* int flags; 24 /* const char *service; 25 /* int timeout; 26 /* const char *ehlo_name; 27 /* const char *mail_from; 28 /* 29 /* int proxy->cmd(state, expect, format, ...) 30 /* SMTPD_PROXY *proxy; 31 /* SMTPD_STATE *state; 32 /* int expect; 33 /* const char *format; 34 /* 35 /* void smtpd_proxy_free(state) 36 /* SMTPD_STATE *state; 37 /* 38 /* int smtpd_proxy_parse_opts(param_name, param_val) 39 /* const char *param_name; 40 /* const char *param_val; 41 /* RECORD-LEVEL ROUTINES 42 /* int proxy->rec_put(proxy->stream, rec_type, data, len) 43 /* SMTPD_PROXY *proxy; 44 /* int rec_type; 45 /* const char *data; 46 /* ssize_t len; 47 /* 48 /* int proxy->rec_fprintf(proxy->stream, rec_type, format, ...) 49 /* SMTPD_PROXY *proxy; 50 /* int rec_type; 51 /* cont char *format; 52 /* DESCRIPTION 53 /* The functions in this module implement a pass-through proxy 54 /* client. 55 /* 56 /* In order to minimize the intrusiveness of pass-through 57 /* proxying, 1) the proxy server must support the same MAIL 58 /* FROM/RCPT syntax that Postfix supports, 2) the record-level 59 /* routines for message content proxying have the same interface 60 /* as the routines that are used for non-proxied mail. 61 /* 62 /* smtpd_proxy_create() takes a description of a before-queue 63 /* filter. Depending on flags, it either arranges to buffer 64 /* up commands and message content until the entire message 65 /* is received, or it immediately connects to the proxy service, 66 /* sends EHLO, sends client information with the XFORWARD 67 /* command if possible, sends the MAIL FROM command, and 68 /* receives the reply. 69 /* A non-zero result value means trouble: either the proxy is 70 /* unavailable, or it did not send the expected reply. 71 /* All results are reported via the proxy->buffer field in a 72 /* form that can be sent to the SMTP client. An unexpected 73 /* 2xx or 3xx proxy server response is replaced by a generic 74 /* error response to avoid support problems. 75 /* In case of error, smtpd_proxy_create() updates the 76 /* state->error_mask and state->err fields, and leaves the 77 /* SMTPD_PROXY handle in an unconnected state. Destroy the 78 /* handle after reporting the error reply in the proxy->buffer 79 /* field. 80 /* 81 /* proxy->cmd() formats and either buffers up the command and 82 /* expected response until the entire message is received, or 83 /* it immediately sends the specified command to the proxy 84 /* server, and receives the proxy server reply. 85 /* A non-zero result value means trouble: either the proxy is 86 /* unavailable, or it did not send the expected reply. 87 /* All results are reported via the proxy->buffer field in a 88 /* form that can be sent to the SMTP client. An unexpected 89 /* 2xx or 3xx proxy server response is replaced by a generic 90 /* error response to avoid support problems. 91 /* In case of error, proxy->cmd() updates the state->error_mask 92 /* and state->err fields. 93 /* 94 /* smtpd_proxy_free() destroys a proxy server handle and resets 95 /* the state->proxy field. 96 /* 97 /* smtpd_proxy_parse_opts() parses main.cf processing options. 98 /* 99 /* proxy->rec_put() is a rec_put() clone that either buffers 100 /* up arbitrary message content records until the entire message 101 /* is received, or that immediately sends it to the proxy 102 /* server. 103 /* All data is expected to be in SMTP dot-escaped form. 104 /* All errors are reported as a REC_TYPE_ERROR result value, 105 /* with the state->error_mask, state->err and proxy-buffer 106 /* fields given appropriate values. 107 /* 108 /* proxy->rec_fprintf() is a rec_fprintf() clone that formats 109 /* message content and either buffers up the record until the 110 /* entire message is received, or that immediately sends it 111 /* to the proxy server. 112 /* All data is expected to be in SMTP dot-escaped form. 113 /* All errors are reported as a REC_TYPE_ERROR result value, 114 /* with the state->error_mask, state->err and proxy-buffer 115 /* fields given appropriate values. 116 /* 117 /* Arguments: 118 /* .IP flags 119 /* Zero, or SMTPD_PROXY_FLAG_SPEED_ADJUST to buffer up the entire 120 /* message before contacting a before-queue content filter. 121 /* Note: when this feature is requested, the before-queue 122 /* filter MUST use the same 2xx, 4xx or 5xx reply code for all 123 /* recipients of a multi-recipient message. 124 /* .IP server 125 /* The SMTP proxy server host:port. The host or host: part is optional. 126 /* This argument is not duplicated. 127 /* .IP timeout 128 /* Time limit for connecting to the proxy server and for 129 /* sending and receiving proxy server commands and replies. 130 /* .IP ehlo_name 131 /* The EHLO Hostname that will be sent to the proxy server. 132 /* This argument is not duplicated. 133 /* .IP mail_from 134 /* The MAIL FROM command. This argument is not duplicated. 135 /* .IP state 136 /* SMTP server state. 137 /* .IP expect 138 /* Expected proxy server reply status code range. A warning is logged 139 /* when an unexpected reply is received. Specify one of the following: 140 /* .RS 141 /* .IP SMTPD_PROX_WANT_OK 142 /* The caller expects a reply in the 200 range. 143 /* .IP SMTPD_PROX_WANT_MORE 144 /* The caller expects a reply in the 300 range. 145 /* .IP SMTPD_PROX_WANT_ANY 146 /* The caller has no expectation. Do not warn for unexpected replies. 147 /* .IP SMTPD_PROX_WANT_NONE 148 /* Do not bother waiting for a reply. 149 /* .RE 150 /* .IP format 151 /* A format string. 152 /* .IP stream 153 /* Connection to proxy server. 154 /* .IP data 155 /* Pointer to the content of one message content record. 156 /* .IP len 157 /* The length of a message content record. 158 /* SEE ALSO 159 /* smtpd(8) Postfix smtp server 160 /* DIAGNOSTICS 161 /* Panic: internal API violations. 162 /* 163 /* Fatal errors: memory allocation problem. 164 /* 165 /* Warnings: unexpected response from proxy server, unable 166 /* to connect to proxy server, proxy server read/write error, 167 /* proxy speed-adjust buffer read/write error. 168 /* LICENSE 169 /* .ad 170 /* .fi 171 /* The Secure Mailer license must be distributed with this software. 172 /* AUTHOR(S) 173 /* Wietse Venema 174 /* IBM T.J. Watson Research 175 /* P.O. Box 704 176 /* Yorktown Heights, NY 10598, USA 177 /*--*/ 178 179 /* System library. */ 180 181 #include <sys_defs.h> 182 #include <ctype.h> 183 #include <unistd.h> 184 185 #ifdef STRCASECMP_IN_STRINGS_H 186 #include <strings.h> 187 #endif 188 189 /* Utility library. */ 190 191 #include <msg.h> 192 #include <vstream.h> 193 #include <vstring.h> 194 #include <stringops.h> 195 #include <connect.h> 196 #include <name_code.h> 197 #include <mymalloc.h> 198 199 /* Global library. */ 200 201 #include <mail_error.h> 202 #include <smtp_stream.h> 203 #include <cleanup_user.h> 204 #include <mail_params.h> 205 #include <rec_type.h> 206 #include <mail_proto.h> 207 #include <mail_params.h> /* null_format_string */ 208 #include <xtext.h> 209 #include <record.h> 210 #include <mail_queue.h> 211 212 /* Application-specific. */ 213 214 #include <smtpd.h> 215 #include <smtpd_proxy.h> 216 217 /* 218 * XFORWARD server features, recognized by the pass-through proxy client. 219 */ 220 #define SMTPD_PROXY_XFORWARD_NAME (1<<0) /* client name */ 221 #define SMTPD_PROXY_XFORWARD_ADDR (1<<1) /* client address */ 222 #define SMTPD_PROXY_XFORWARD_PROTO (1<<2) /* protocol */ 223 #define SMTPD_PROXY_XFORWARD_HELO (1<<3) /* client helo */ 224 #define SMTPD_PROXY_XFORWARD_IDENT (1<<4) /* message identifier */ 225 #define SMTPD_PROXY_XFORWARD_DOMAIN (1<<5) /* origin type */ 226 #define SMTPD_PROXY_XFORWARD_PORT (1<<6) /* client port */ 227 228 /* 229 * Spead-matching: we use an unlinked file for transient storage. 230 */ 231 static VSTREAM *smtpd_proxy_replay_stream; 232 233 /* 234 * Forward declarations. 235 */ 236 static void smtpd_proxy_fake_server_reply(SMTPD_STATE *, int); 237 static int smtpd_proxy_rdwr_error(SMTPD_STATE *, int); 238 static int smtpd_proxy_cmd(SMTPD_STATE *, int, const char *,...); 239 static int smtpd_proxy_rec_put(VSTREAM *, int, const char *, ssize_t); 240 241 /* 242 * SLMs. 243 */ 244 #define STR(x) vstring_str(x) 245 #define LEN(x) VSTRING_LEN(x) 246 #define SMTPD_PROXY_CONN_FMT null_format_string 247 #define STREQ(x, y) (strcmp((x), (y)) == 0) 248 249 /* smtpd_proxy_xforward_flush - flush forwarding information */ 250 251 static int smtpd_proxy_xforward_flush(SMTPD_STATE *state, VSTRING *buf) 252 { 253 int ret; 254 255 if (VSTRING_LEN(buf) > 0) { 256 ret = smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, 257 XFORWARD_CMD "%s", STR(buf)); 258 VSTRING_RESET(buf); 259 return (ret); 260 } 261 return (0); 262 } 263 264 /* smtpd_proxy_xforward_send - send forwarding information */ 265 266 static int smtpd_proxy_xforward_send(SMTPD_STATE *state, VSTRING *buf, 267 const char *name, 268 int value_available, 269 const char *value) 270 { 271 size_t new_len; 272 int ret; 273 274 #define CONSTR_LEN(s) (sizeof(s) - 1) 275 #define PAYLOAD_LIMIT (512 - CONSTR_LEN("250 " XFORWARD_CMD "\r\n")) 276 277 if (!value_available) 278 value = XFORWARD_UNAVAILABLE; 279 280 /* 281 * Encode the attribute value. 282 */ 283 if (state->expand_buf == 0) 284 state->expand_buf = vstring_alloc(100); 285 xtext_quote(state->expand_buf, value, ""); 286 287 /* 288 * How much space does this attribute need? SPACE name = value. 289 */ 290 new_len = strlen(name) + strlen(STR(state->expand_buf)) + 2; 291 if (new_len > PAYLOAD_LIMIT) 292 msg_warn("%s command payload %s=%.10s... exceeds SMTP protocol limit", 293 XFORWARD_CMD, name, value); 294 295 /* 296 * Flush the buffer if we need to, and store the attribute. 297 */ 298 if (VSTRING_LEN(buf) > 0 && VSTRING_LEN(buf) + new_len > PAYLOAD_LIMIT) 299 if ((ret = smtpd_proxy_xforward_flush(state, buf)) < 0) 300 return (ret); 301 vstring_sprintf_append(buf, " %s=%s", name, STR(state->expand_buf)); 302 303 return (0); 304 } 305 306 /* smtpd_proxy_connect - open proxy connection */ 307 308 static int smtpd_proxy_connect(SMTPD_STATE *state) 309 { 310 SMTPD_PROXY *proxy = state->proxy; 311 int fd; 312 char *lines; 313 char *words; 314 VSTRING *buf; 315 int bad; 316 char *word; 317 static const NAME_CODE known_xforward_features[] = { 318 XFORWARD_NAME, SMTPD_PROXY_XFORWARD_NAME, 319 XFORWARD_ADDR, SMTPD_PROXY_XFORWARD_ADDR, 320 XFORWARD_PORT, SMTPD_PROXY_XFORWARD_PORT, 321 XFORWARD_PROTO, SMTPD_PROXY_XFORWARD_PROTO, 322 XFORWARD_HELO, SMTPD_PROXY_XFORWARD_HELO, 323 XFORWARD_IDENT, SMTPD_PROXY_XFORWARD_IDENT, 324 XFORWARD_DOMAIN, SMTPD_PROXY_XFORWARD_DOMAIN, 325 0, 0, 326 }; 327 int server_xforward_features; 328 int (*connect_fn) (const char *, int, int); 329 const char *endpoint; 330 331 /* 332 * Find connection method (default inet) 333 */ 334 if (strncasecmp("unix:", proxy->service_name, 5) == 0) { 335 endpoint = proxy->service_name + 5; 336 connect_fn = unix_connect; 337 } else { 338 if (strncasecmp("inet:", proxy->service_name, 5) == 0) 339 endpoint = proxy->service_name + 5; 340 else 341 endpoint = proxy->service_name; 342 connect_fn = inet_connect; 343 } 344 345 /* 346 * Connect to proxy. 347 */ 348 if ((fd = connect_fn(endpoint, BLOCKING, proxy->timeout)) < 0) { 349 msg_warn("connect to proxy filter %s: %m", proxy->service_name); 350 return (smtpd_proxy_rdwr_error(state, 0)); 351 } 352 proxy->service_stream = vstream_fdopen(fd, O_RDWR); 353 /* Needed by our DATA-phase record emulation routines. */ 354 vstream_control(proxy->service_stream, VSTREAM_CTL_CONTEXT, 355 (char *) state, VSTREAM_CTL_END); 356 /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */ 357 if (connect_fn == inet_connect) 358 vstream_tweak_tcp(proxy->service_stream); 359 smtp_timeout_setup(proxy->service_stream, proxy->timeout); 360 361 /* 362 * Get server greeting banner. 363 * 364 * If this fails then we have a problem because the proxy should always 365 * accept our connection. Make up our own response instead of passing 366 * back a negative greeting banner: the proxy open is delayed to the 367 * point that the client expects a MAIL FROM or RCPT TO reply. 368 */ 369 if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, SMTPD_PROXY_CONN_FMT)) { 370 smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); 371 smtpd_proxy_close(state); 372 return (-1); 373 } 374 375 /* 376 * Send our own EHLO command. If this fails then we have a problem 377 * because the proxy should always accept our EHLO command. Make up our 378 * own response instead of passing back a negative EHLO reply: the proxy 379 * open is delayed to the point that the remote SMTP client expects a 380 * MAIL FROM or RCPT TO reply. 381 */ 382 if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "EHLO %s", 383 proxy->ehlo_name)) { 384 smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); 385 smtpd_proxy_close(state); 386 return (-1); 387 } 388 389 /* 390 * Parse the EHLO reply and see if we can forward logging information. 391 */ 392 server_xforward_features = 0; 393 lines = STR(proxy->reply); 394 while ((words = mystrtok(&lines, "\n")) != 0) { 395 if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t")) != 0) { 396 if (strcasecmp(word, XFORWARD_CMD) == 0) 397 while ((word = mystrtok(&words, " \t")) != 0) 398 server_xforward_features |= 399 name_code(known_xforward_features, 400 NAME_CODE_FLAG_NONE, word); 401 } 402 } 403 404 /* 405 * Send XFORWARD attributes. For robustness, explicitly specify what SMTP 406 * session attributes are known and unknown. Make up our own response 407 * instead of passing back a negative XFORWARD reply: the proxy open is 408 * delayed to the point that the remote SMTP client expects a MAIL FROM 409 * or RCPT TO reply. 410 */ 411 if (server_xforward_features) { 412 buf = vstring_alloc(100); 413 bad = 414 (((server_xforward_features & SMTPD_PROXY_XFORWARD_NAME) 415 && smtpd_proxy_xforward_send(state, buf, XFORWARD_NAME, 416 IS_AVAIL_CLIENT_NAME(FORWARD_NAME(state)), 417 FORWARD_NAME(state))) 418 || ((server_xforward_features & SMTPD_PROXY_XFORWARD_ADDR) 419 && smtpd_proxy_xforward_send(state, buf, XFORWARD_ADDR, 420 IS_AVAIL_CLIENT_ADDR(FORWARD_ADDR(state)), 421 FORWARD_ADDR(state))) 422 || ((server_xforward_features & SMTPD_PROXY_XFORWARD_PORT) 423 && smtpd_proxy_xforward_send(state, buf, XFORWARD_PORT, 424 IS_AVAIL_CLIENT_PORT(FORWARD_PORT(state)), 425 FORWARD_PORT(state))) 426 || ((server_xforward_features & SMTPD_PROXY_XFORWARD_HELO) 427 && smtpd_proxy_xforward_send(state, buf, XFORWARD_HELO, 428 IS_AVAIL_CLIENT_HELO(FORWARD_HELO(state)), 429 FORWARD_HELO(state))) 430 || ((server_xforward_features & SMTPD_PROXY_XFORWARD_IDENT) 431 && smtpd_proxy_xforward_send(state, buf, XFORWARD_IDENT, 432 IS_AVAIL_CLIENT_IDENT(FORWARD_IDENT(state)), 433 FORWARD_IDENT(state))) 434 || ((server_xforward_features & SMTPD_PROXY_XFORWARD_PROTO) 435 && smtpd_proxy_xforward_send(state, buf, XFORWARD_PROTO, 436 IS_AVAIL_CLIENT_PROTO(FORWARD_PROTO(state)), 437 FORWARD_PROTO(state))) 438 || ((server_xforward_features & SMTPD_PROXY_XFORWARD_DOMAIN) 439 && smtpd_proxy_xforward_send(state, buf, XFORWARD_DOMAIN, 1, 440 STREQ(FORWARD_DOMAIN(state), MAIL_ATTR_RWR_LOCAL) ? 441 XFORWARD_DOM_LOCAL : XFORWARD_DOM_REMOTE)) 442 || smtpd_proxy_xforward_flush(state, buf)); 443 vstring_free(buf); 444 if (bad) { 445 smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); 446 smtpd_proxy_close(state); 447 return (-1); 448 } 449 } 450 451 /* 452 * Pass-through the remote SMTP client's MAIL FROM command. If this 453 * fails, then we have a problem because the proxy should always accept 454 * any MAIL FROM command that was accepted by us. 455 */ 456 if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "%s", 457 proxy->mail_from) != 0) { 458 /* NOT: smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); */ 459 smtpd_proxy_close(state); 460 return (-1); 461 } 462 return (0); 463 } 464 465 /* smtpd_proxy_fake_server_reply - produce generic error response */ 466 467 static void smtpd_proxy_fake_server_reply(SMTPD_STATE *state, int status) 468 { 469 const CLEANUP_STAT_DETAIL *detail; 470 471 /* 472 * Either we have no server reply (connection refused), or we have an 473 * out-of-protocol server reply, so we make up a generic server error 474 * response instead. 475 */ 476 detail = cleanup_stat_detail(status); 477 vstring_sprintf(state->proxy->reply, 478 "%d %s Error: %s", 479 detail->smtp, detail->dsn, detail->text); 480 } 481 482 /* smtpd_proxy_replay_rdwr_error - report replay log I/O error */ 483 484 static int smtpd_proxy_replay_rdwr_error(SMTPD_STATE *state) 485 { 486 487 /* 488 * Log an appropriate warning message. 489 */ 490 msg_warn("proxy speed-adjust log I/O error: %m"); 491 492 /* 493 * Set the appropriate flags and server reply. 494 */ 495 state->error_mask |= MAIL_ERROR_RESOURCE; 496 /* Update state->err in case we are past the client's DATA command. */ 497 state->err |= CLEANUP_STAT_PROXY; 498 smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); 499 return (-1); 500 } 501 502 /* smtpd_proxy_rdwr_error - report proxy communication error */ 503 504 static int smtpd_proxy_rdwr_error(SMTPD_STATE *state, int err) 505 { 506 const char *myname = "smtpd_proxy_rdwr_error"; 507 SMTPD_PROXY *proxy = state->proxy; 508 509 /* 510 * Sanity check. 511 */ 512 if (err != 0 && err != SMTP_ERR_NONE && proxy == 0) 513 msg_panic("%s: proxy error %d without proxy handle", myname, err); 514 515 /* 516 * Log an appropriate warning message. 517 */ 518 switch (err) { 519 case 0: 520 case SMTP_ERR_NONE: 521 break; 522 case SMTP_ERR_EOF: 523 msg_warn("lost connection with proxy %s", proxy->service_name); 524 break; 525 case SMTP_ERR_TIME: 526 msg_warn("timeout talking to proxy %s", proxy->service_name); 527 break; 528 default: 529 msg_panic("%s: unknown proxy %s error %d", 530 myname, proxy->service_name, err); 531 } 532 533 /* 534 * Set the appropriate flags and server reply. 535 */ 536 state->error_mask |= MAIL_ERROR_SOFTWARE; 537 /* Update state->err in case we are past the client's DATA command. */ 538 state->err |= CLEANUP_STAT_PROXY; 539 smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); 540 return (-1); 541 } 542 543 /* smtpd_proxy_replay_send - replay saved SMTP session from speed-match log */ 544 545 static int smtpd_proxy_replay_send(SMTPD_STATE *state) 546 { 547 const char *myname = "smtpd_proxy_replay_send"; 548 static VSTRING *replay_buf = 0; 549 SMTPD_PROXY *proxy = state->proxy; 550 int rec_type; 551 int expect = SMTPD_PROX_WANT_BAD; 552 553 /* 554 * Sanity check. 555 */ 556 if (smtpd_proxy_replay_stream == 0) 557 msg_panic("%s: no before-queue filter speed-adjust log", myname); 558 559 /* 560 * Errors first. 561 */ 562 if (vstream_ferror(smtpd_proxy_replay_stream) 563 || vstream_feof(smtpd_proxy_replay_stream) 564 || rec_put(smtpd_proxy_replay_stream, REC_TYPE_END, "", 0) != REC_TYPE_END 565 || vstream_fflush(smtpd_proxy_replay_stream)) 566 /* NOT: fsync(vstream_fileno(smtpd_proxy_replay_stream)) */ 567 return (smtpd_proxy_replay_rdwr_error(state)); 568 569 /* 570 * Delayed connection to the before-queue filter. 571 */ 572 if (smtpd_proxy_connect(state) < 0) 573 return (-1); 574 575 /* 576 * Replay the speed-match log. We do sanity check record content, but we 577 * don't implement a protocol state engine here, since we are reading 578 * from a file that we just wrote ourselves. 579 * 580 * This is different than the MailChannels patented solution that 581 * multiplexes a large number of slowed-down inbound connections over a 582 * small number of fast connections to a local MTA. 583 * 584 * - MailChannels receives mail directly from the Internet. It uses one 585 * connection to the local MTA to reject invalid recipients before 586 * receiving the entire email message at reduced bit rates, and then uses 587 * a different connection to quickly deliver the message to the local 588 * MTA. 589 * 590 * - Postfix receives mail directly from the Internet. The Postfix SMTP 591 * server rejects invalid recipients before receiving the entire message 592 * over the Internet, and then delivers the message quickly to a local 593 * SMTP-based content filter. 594 */ 595 if (replay_buf == 0) 596 replay_buf = vstring_alloc(100); 597 if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0) 598 return (smtpd_proxy_replay_rdwr_error(state)); 599 600 for (;;) { 601 switch (rec_type = rec_get(smtpd_proxy_replay_stream, replay_buf, 602 REC_FLAG_NONE)) { 603 604 /* 605 * Message content. 606 */ 607 case REC_TYPE_NORM: 608 case REC_TYPE_CONT: 609 if (smtpd_proxy_rec_put(proxy->service_stream, rec_type, 610 STR(replay_buf), LEN(replay_buf)) < 0) 611 return (-1); 612 break; 613 614 /* 615 * Expected server reply type. 616 */ 617 case REC_TYPE_RCPT: 618 if (!alldig(STR(replay_buf)) 619 || (expect = atoi(STR(replay_buf))) == SMTPD_PROX_WANT_BAD) 620 msg_panic("%s: malformed server reply type: %s", 621 myname, STR(replay_buf)); 622 break; 623 624 /* 625 * Client command, or void. Bail out on the first negative proxy 626 * response. This is OK, because the filter must use the same 627 * reply code for all recipients of a multi-recipient message. 628 */ 629 case REC_TYPE_FROM: 630 if (expect == SMTPD_PROX_WANT_BAD) 631 msg_panic("%s: missing server reply type", myname); 632 if (smtpd_proxy_cmd(state, expect, *STR(replay_buf) ? "%s" : 633 SMTPD_PROXY_CONN_FMT, STR(replay_buf)) < 0) 634 return (-1); 635 expect = SMTPD_PROX_WANT_BAD; 636 break; 637 638 /* 639 * Explicit end marker, instead of implicit EOF. 640 */ 641 case REC_TYPE_END: 642 return (0); 643 644 /* 645 * Errors. 646 */ 647 case REC_TYPE_ERROR: 648 return (smtpd_proxy_replay_rdwr_error(state)); 649 default: 650 msg_panic("%s: unexpected record type; %d", myname, rec_type); 651 } 652 } 653 } 654 655 /* smtpd_proxy_save_cmd - save SMTP command + expected response to replay log */ 656 657 static int smtpd_proxy_save_cmd(SMTPD_STATE *state, int expect, const char *fmt,...) 658 { 659 va_list ap; 660 661 /* 662 * Errors first. 663 */ 664 if (vstream_ferror(smtpd_proxy_replay_stream) 665 || vstream_feof(smtpd_proxy_replay_stream)) 666 return (smtpd_proxy_replay_rdwr_error(state)); 667 668 /* 669 * Save the expected reply first, so that the replayer can safely 670 * overwrite the input buffer with the command. 671 */ 672 rec_fprintf(smtpd_proxy_replay_stream, REC_TYPE_RCPT, "%d", expect); 673 674 /* 675 * The command can be omitted at the start of an SMTP session. This is 676 * not documented as part of the official interface because it is used 677 * only internally to this module. Use an explicit null string in case 678 * the SMTPD_PROXY_CONN_FMT implementation details change. 679 */ 680 if (fmt == SMTPD_PROXY_CONN_FMT) 681 fmt = ""; 682 683 /* 684 * Save the command to the replay log, and send it to the before-queue 685 * filter after we have received the entire message. 686 */ 687 va_start(ap, fmt); 688 rec_vfprintf(smtpd_proxy_replay_stream, REC_TYPE_FROM, fmt, ap); 689 va_end(ap); 690 691 /* 692 * If we just saved the "." command, replay the log. 693 */ 694 return (strcmp(fmt, ".") ? 0 : smtpd_proxy_replay_send(state)); 695 } 696 697 /* smtpd_proxy_cmd - send command to proxy, receive reply */ 698 699 static int smtpd_proxy_cmd(SMTPD_STATE *state, int expect, const char *fmt,...) 700 { 701 SMTPD_PROXY *proxy = state->proxy; 702 va_list ap; 703 char *cp; 704 int last_char; 705 int err = 0; 706 static VSTRING *buffer = 0; 707 708 /* 709 * Errors first. Be prepared for delayed errors from the DATA phase. 710 */ 711 if (vstream_ferror(proxy->service_stream) 712 || vstream_feof(proxy->service_stream) 713 || (err = vstream_setjmp(proxy->service_stream)) != 0) { 714 return (smtpd_proxy_rdwr_error(state, err)); 715 } 716 717 /* 718 * The command can be omitted at the start of an SMTP session. This is 719 * not documented as part of the official interface because it is used 720 * only internally to this module. 721 */ 722 if (fmt != SMTPD_PROXY_CONN_FMT) { 723 724 /* 725 * Format the command. 726 */ 727 va_start(ap, fmt); 728 vstring_vsprintf(proxy->request, fmt, ap); 729 va_end(ap); 730 731 /* 732 * Optionally log the command first, so that we can see in the log 733 * what the program is trying to do. 734 */ 735 if (msg_verbose) 736 msg_info("> %s: %s", proxy->service_name, STR(proxy->request)); 737 738 /* 739 * Send the command to the proxy server. Since we're going to read a 740 * reply immediately, there is no need to flush buffers. 741 */ 742 smtp_fputs(STR(proxy->request), LEN(proxy->request), 743 proxy->service_stream); 744 } 745 746 /* 747 * Early return if we don't want to wait for a server reply (such as 748 * after sending QUIT). 749 */ 750 if (expect == SMTPD_PROX_WANT_NONE) 751 return (0); 752 753 /* 754 * Censor out non-printable characters in server responses and save 755 * complete multi-line responses if possible. 756 * 757 * We can't parse or store input that exceeds var_line_limit, so we just 758 * skip over it to simplify the remainder of the code below. 759 */ 760 VSTRING_RESET(proxy->reply); 761 if (buffer == 0) 762 buffer = vstring_alloc(10); 763 for (;;) { 764 last_char = smtp_get(buffer, proxy->service_stream, var_line_limit, 765 SMTP_GET_FLAG_SKIP); 766 printable(STR(buffer), '?'); 767 if (last_char != '\n') 768 msg_warn("%s: response longer than %d: %.30s...", 769 proxy->service_name, var_line_limit, 770 STR(buffer)); 771 if (msg_verbose) 772 msg_info("< %s: %.100s", proxy->service_name, STR(buffer)); 773 774 /* 775 * Defend against a denial of service attack by limiting the amount 776 * of multi-line text that we are willing to store. 777 */ 778 if (LEN(proxy->reply) < var_line_limit) { 779 if (VSTRING_LEN(proxy->reply)) 780 vstring_strcat(proxy->reply, "\r\n"); 781 vstring_strcat(proxy->reply, STR(buffer)); 782 } 783 784 /* 785 * Parse the response into code and text. Ignore unrecognized 786 * garbage. This means that any character except space (or end of 787 * line) will have the same effect as the '-' line continuation 788 * character. 789 */ 790 for (cp = STR(buffer); *cp && ISDIGIT(*cp); cp++) 791 /* void */ ; 792 if (cp - STR(buffer) == 3) { 793 if (*cp == '-') 794 continue; 795 if (*cp == ' ' || *cp == 0) 796 break; 797 } 798 msg_warn("received garbage from proxy %s: %.100s", 799 proxy->service_name, STR(buffer)); 800 } 801 802 /* 803 * Log a warning in case the proxy does not send the expected response. 804 * Silently accept any response when the client expressed no expectation. 805 * 806 * Starting with Postfix 2.6 we don't pass through unexpected 2xx or 3xx 807 * proxy replies. They are a source of support problems, so we replace 808 * them by generic server error replies. 809 */ 810 if (expect != SMTPD_PROX_WANT_ANY && expect != *STR(proxy->reply)) { 811 msg_warn("proxy %s rejected \"%s\": \"%s\"", 812 proxy->service_name, fmt == SMTPD_PROXY_CONN_FMT ? 813 "connection request" : STR(proxy->request), 814 STR(proxy->reply)); 815 if (*STR(proxy->reply) == SMTPD_PROX_WANT_OK 816 || *STR(proxy->reply) == SMTPD_PROX_WANT_MORE) { 817 smtpd_proxy_rdwr_error(state, 0); 818 } 819 return (-1); 820 } else { 821 return (0); 822 } 823 } 824 825 /* smtpd_proxy_save_rec_put - save message content to replay log */ 826 827 static int smtpd_proxy_save_rec_put(VSTREAM *stream, int rec_type, 828 const char *data, ssize_t len) 829 { 830 const char *myname = "smtpd_proxy_save_rec_put"; 831 int ret; 832 833 #define VSTREAM_TO_SMTPD_STATE(s) ((SMTPD_STATE *) vstream_context(s)) 834 835 /* 836 * Sanity check. 837 */ 838 if (stream == 0) 839 msg_panic("%s: attempt to use closed stream", myname); 840 841 /* 842 * Send one content record. Errors and results must be as with rec_put(). 843 */ 844 if (rec_type == REC_TYPE_NORM || rec_type == REC_TYPE_CONT) 845 ret = rec_put(stream, rec_type, data, len); 846 else 847 msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname); 848 849 /* 850 * Errors last. 851 */ 852 if (ret != rec_type) { 853 (void) smtpd_proxy_replay_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream)); 854 return (REC_TYPE_ERROR); 855 } 856 return (rec_type); 857 } 858 859 /* smtpd_proxy_rec_put - send message content, rec_put() clone */ 860 861 static int smtpd_proxy_rec_put(VSTREAM *stream, int rec_type, 862 const char *data, ssize_t len) 863 { 864 const char *myname = "smtpd_proxy_rec_put"; 865 int err = 0; 866 867 /* 868 * Errors first. 869 */ 870 if (vstream_ferror(stream) || vstream_feof(stream) 871 || (err = vstream_setjmp(stream)) != 0) { 872 (void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err); 873 return (REC_TYPE_ERROR); 874 } 875 876 /* 877 * Send one content record. Errors and results must be as with rec_put(). 878 */ 879 if (rec_type == REC_TYPE_NORM) 880 smtp_fputs(data, len, stream); 881 else if (rec_type == REC_TYPE_CONT) 882 smtp_fwrite(data, len, stream); 883 else 884 msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname); 885 return (rec_type); 886 } 887 888 /* smtpd_proxy_save_rec_fprintf - save message content to replay log */ 889 890 static int smtpd_proxy_save_rec_fprintf(VSTREAM *stream, int rec_type, 891 const char *fmt,...) 892 { 893 const char *myname = "smtpd_proxy_save_rec_fprintf"; 894 va_list ap; 895 int ret; 896 897 /* 898 * Sanity check. 899 */ 900 if (stream == 0) 901 msg_panic("%s: attempt to use closed stream", myname); 902 903 /* 904 * Save one content record. Errors and results must be as with 905 * rec_fprintf(). 906 */ 907 va_start(ap, fmt); 908 if (rec_type == REC_TYPE_NORM) 909 ret = rec_vfprintf(stream, rec_type, fmt, ap); 910 else 911 msg_panic("%s: need REC_TYPE_NORM", myname); 912 va_end(ap); 913 914 /* 915 * Errors last. 916 */ 917 if (ret != rec_type) { 918 (void) smtpd_proxy_replay_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream)); 919 return (REC_TYPE_ERROR); 920 } 921 return (rec_type); 922 } 923 924 /* smtpd_proxy_rec_fprintf - send message content, rec_fprintf() clone */ 925 926 static int smtpd_proxy_rec_fprintf(VSTREAM *stream, int rec_type, 927 const char *fmt,...) 928 { 929 const char *myname = "smtpd_proxy_rec_fprintf"; 930 va_list ap; 931 int err = 0; 932 933 /* 934 * Errors first. 935 */ 936 if (vstream_ferror(stream) || vstream_feof(stream) 937 || (err = vstream_setjmp(stream)) != 0) { 938 (void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err); 939 return (REC_TYPE_ERROR); 940 } 941 942 /* 943 * Send one content record. Errors and results must be as with 944 * rec_fprintf(). 945 */ 946 va_start(ap, fmt); 947 if (rec_type == REC_TYPE_NORM) 948 smtp_vprintf(stream, fmt, ap); 949 else 950 msg_panic("%s: need REC_TYPE_NORM", myname); 951 va_end(ap); 952 return (rec_type); 953 } 954 955 #ifndef NO_TRUNCATE 956 957 /* smtpd_proxy_replay_setup - prepare the replay logfile */ 958 959 static int smtpd_proxy_replay_setup(SMTPD_STATE *state) 960 { 961 const char *myname = "smtpd_proxy_replay_setup"; 962 off_t file_offs; 963 964 /* 965 * Where possible reuse an existing replay logfile, because creating a 966 * file is expensive compared to reading or writing. For security reasons 967 * we must truncate the file before reuse. For performance reasons we 968 * should truncate the file immediately after the end of a mail 969 * transaction. We enforce the security guarantee upon reuse, by 970 * requiring that no I/O happened since the file was truncated. This is 971 * less expensive than truncating the file redundantly. 972 */ 973 if (smtpd_proxy_replay_stream != 0) { 974 /* vstream_ftell() won't invoke the kernel, so all errors are mine. */ 975 if ((file_offs = vstream_ftell(smtpd_proxy_replay_stream)) != 0) 976 msg_panic("%s: bad before-queue filter speed-adjust log offset %lu", 977 myname, (unsigned long) file_offs); 978 vstream_clearerr(smtpd_proxy_replay_stream); 979 if (msg_verbose) 980 msg_info("%s: reuse speed-adjust stream fd=%d", myname, 981 vstream_fileno(smtpd_proxy_replay_stream)); 982 /* Here, smtpd_proxy_replay_stream != 0 */ 983 } 984 985 /* 986 * Create a new replay logfile. 987 */ 988 if (smtpd_proxy_replay_stream == 0) { 989 smtpd_proxy_replay_stream = mail_queue_enter(MAIL_QUEUE_INCOMING, 0, 990 (struct timeval *) 0); 991 if (smtpd_proxy_replay_stream == 0) 992 return (smtpd_proxy_replay_rdwr_error(state)); 993 if (unlink(VSTREAM_PATH(smtpd_proxy_replay_stream)) < 0) 994 msg_warn("remove before-queue filter speed-adjust log %s: %m", 995 VSTREAM_PATH(smtpd_proxy_replay_stream)); 996 if (msg_verbose) 997 msg_info("%s: new speed-adjust stream fd=%d", myname, 998 vstream_fileno(smtpd_proxy_replay_stream)); 999 } 1000 1001 /* 1002 * Needed by our DATA-phase record emulation routines. 1003 */ 1004 vstream_control(smtpd_proxy_replay_stream, VSTREAM_CTL_CONTEXT, 1005 (char *) state, VSTREAM_CTL_END); 1006 return (0); 1007 } 1008 1009 #endif 1010 1011 /* smtpd_proxy_create - set up smtpd proxy handle */ 1012 1013 int smtpd_proxy_create(SMTPD_STATE *state, int flags, const char *service, 1014 int timeout, const char *ehlo_name, 1015 const char *mail_from) 1016 { 1017 SMTPD_PROXY *proxy; 1018 1019 /* 1020 * When an operation has many arguments it is safer to use named 1021 * parameters, and have the compiler enforce the argument count. 1022 */ 1023 #define SMTPD_PROXY_ALLOC(p, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) \ 1024 ((p) = (SMTPD_PROXY *) mymalloc(sizeof(*(p))), (p)->a1, (p)->a2, \ 1025 (p)->a3, (p)->a4, (p)->a5, (p)->a6, (p)->a7, (p)->a8, (p)->a9, \ 1026 (p)->a10, (p)->a11, (p)->a12, (p)) 1027 1028 /* 1029 * Sanity check. 1030 */ 1031 if (state->proxy != 0) 1032 msg_panic("smtpd_proxy_create: handle still exists"); 1033 1034 /* 1035 * Connect to the before-queue filter immediately. 1036 */ 1037 if ((flags & SMTPD_PROXY_FLAG_SPEED_ADJUST) == 0) { 1038 state->proxy = 1039 SMTPD_PROXY_ALLOC(proxy, stream = 0, request = vstring_alloc(10), 1040 reply = vstring_alloc(10), 1041 cmd = smtpd_proxy_cmd, 1042 rec_fprintf = smtpd_proxy_rec_fprintf, 1043 rec_put = smtpd_proxy_rec_put, 1044 flags = flags, service_stream = 0, 1045 service_name = service, timeout = timeout, 1046 ehlo_name = ehlo_name, mail_from = mail_from); 1047 if (smtpd_proxy_connect(state) < 0) { 1048 /* NOT: smtpd_proxy_free(state); we still need proxy->reply. */ 1049 return (-1); 1050 } 1051 proxy->stream = proxy->service_stream; 1052 return (0); 1053 } 1054 1055 /* 1056 * Connect to the before-queue filter after we receive the entire 1057 * message. Open the replay logfile early to simplify code. The file is 1058 * reused for multiple mail transactions, so there is no need to minimize 1059 * its life time. 1060 */ 1061 else { 1062 #ifdef NO_TRUNCATE 1063 msg_panic("smtpd_proxy_create: speed-adjust support is not available"); 1064 #else 1065 if (smtpd_proxy_replay_setup(state) < 0) 1066 return (-1); 1067 state->proxy = 1068 SMTPD_PROXY_ALLOC(proxy, stream = smtpd_proxy_replay_stream, 1069 request = vstring_alloc(10), 1070 reply = vstring_alloc(10), 1071 cmd = smtpd_proxy_save_cmd, 1072 rec_fprintf = smtpd_proxy_save_rec_fprintf, 1073 rec_put = smtpd_proxy_save_rec_put, 1074 flags = flags, service_stream = 0, 1075 service_name = service, timeout = timeout, 1076 ehlo_name = ehlo_name, mail_from = mail_from); 1077 return (0); 1078 #endif 1079 } 1080 } 1081 1082 /* smtpd_proxy_close - close proxy connection without destroying handle */ 1083 1084 void smtpd_proxy_close(SMTPD_STATE *state) 1085 { 1086 SMTPD_PROXY *proxy = state->proxy; 1087 1088 /* 1089 * Specify SMTPD_PROX_WANT_NONE so that the server reply will not clobber 1090 * the END-OF-DATA reply. 1091 */ 1092 if (proxy->service_stream != 0) { 1093 if (vstream_feof(proxy->service_stream) == 0 1094 && vstream_ferror(proxy->service_stream) == 0) 1095 (void) smtpd_proxy_cmd(state, SMTPD_PROX_WANT_NONE, 1096 SMTPD_CMD_QUIT); 1097 (void) vstream_fclose(proxy->service_stream); 1098 if (proxy->stream == proxy->service_stream) 1099 proxy->stream = 0; 1100 proxy->service_stream = 0; 1101 } 1102 } 1103 1104 /* smtpd_proxy_free - destroy smtpd proxy handle */ 1105 1106 void smtpd_proxy_free(SMTPD_STATE *state) 1107 { 1108 SMTPD_PROXY *proxy = state->proxy; 1109 1110 /* 1111 * Clean up. 1112 */ 1113 if (proxy->service_stream != 0) 1114 (void) smtpd_proxy_close(state); 1115 if (proxy->request != 0) 1116 vstring_free(proxy->request); 1117 if (proxy->reply != 0) 1118 vstring_free(proxy->reply); 1119 myfree((char *) proxy); 1120 state->proxy = 0; 1121 1122 /* 1123 * Reuse the replay logfile if possible. For security reasons we must 1124 * truncate the replay logfile before reuse. For performance reasons we 1125 * should truncate the replay logfile immediately after the end of a mail 1126 * transaction. We truncate the file here, and enforce the security 1127 * guarantee by requiring that no I/O happens before the file is reused. 1128 */ 1129 if (smtpd_proxy_replay_stream == 0) 1130 return; 1131 if (vstream_ferror(smtpd_proxy_replay_stream)) { 1132 /* Errors are already reported. */ 1133 (void) vstream_fclose(smtpd_proxy_replay_stream); 1134 smtpd_proxy_replay_stream = 0; 1135 return; 1136 } 1137 /* Flush output from aborted transaction before truncating the file!! */ 1138 if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0) { 1139 msg_warn("seek before-queue filter speed-adjust log: %m"); 1140 (void) vstream_fclose(smtpd_proxy_replay_stream); 1141 smtpd_proxy_replay_stream = 0; 1142 return; 1143 } 1144 if (ftruncate(vstream_fileno(smtpd_proxy_replay_stream), (off_t) 0) < 0) { 1145 msg_warn("truncate before-queue filter speed-adjust log: %m"); 1146 (void) vstream_fclose(smtpd_proxy_replay_stream); 1147 smtpd_proxy_replay_stream = 0; 1148 return; 1149 } 1150 } 1151 1152 /* smtpd_proxy_parse_opts - parse main.cf options */ 1153 1154 int smtpd_proxy_parse_opts(const char *param_name, const char *param_val) 1155 { 1156 static const NAME_MASK proxy_opts_table[] = { 1157 SMTPD_PROXY_NAME_SPEED_ADJUST, SMTPD_PROXY_FLAG_SPEED_ADJUST, 1158 0, 0, 1159 }; 1160 int flags; 1161 1162 /* 1163 * The optional before-filter speed-adjust buffers use disk space. 1164 * However, we don't know if they compete for storage space with the 1165 * after-filter queue, so we can't simply bump up the free space 1166 * requirement to 2.5 * message_size_limit. 1167 */ 1168 flags = name_mask(param_name, proxy_opts_table, param_val); 1169 if (flags & SMTPD_PROXY_FLAG_SPEED_ADJUST) { 1170 #ifdef NO_TRUNCATE 1171 msg_warn("smtpd_proxy %s support is not available", 1172 SMTPD_PROXY_NAME_SPEED_ADJUST); 1173 flags &= ~SMTPD_PROXY_FLAG_SPEED_ADJUST; 1174 #endif 1175 } 1176 return (flags); 1177 } 1178