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