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