1 /* $NetBSD: smtpd_proxy.c,v 1.4 2026/05/09 18:49:21 christos 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 more of the following: 120 /* .RS 121 /* .IP SMTPD_PROXY_FLAG_SPEED_ADJUST 122 /* Buffer up the entire message before contacting a before-queue 123 /* content filter. 124 /* Note: when this feature is requested, the before-queue 125 /* filter MUST use the same 2xx, 4xx or 5xx reply code for all 126 /* recipients of a multi-recipient message. 127 /* .IP SMTPD_PROXY_FLAG_REQTLS_HDR 128 /* Add a "Require-TLS-ESMTP: yes" header if one is not already 129 /* present. 130 /* .RE 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 <xtext.h> 215 #include <record.h> 216 #include <mail_queue.h> 217 #include <is_header.h> 218 #include <header_opts.h> 219 220 /* Application-specific. */ 221 222 #include <smtpd.h> 223 #include <smtpd_proxy.h> 224 225 /* 226 * XFORWARD server features, recognized by the pass-through proxy client. 227 */ 228 #define SMTPD_PROXY_XFORWARD_NAME (1<<0) /* client name */ 229 #define SMTPD_PROXY_XFORWARD_ADDR (1<<1) /* client address */ 230 #define SMTPD_PROXY_XFORWARD_PROTO (1<<2) /* protocol */ 231 #define SMTPD_PROXY_XFORWARD_HELO (1<<3) /* client helo */ 232 #define SMTPD_PROXY_XFORWARD_IDENT (1<<4) /* message identifier */ 233 #define SMTPD_PROXY_XFORWARD_DOMAIN (1<<5) /* origin type */ 234 #define SMTPD_PROXY_XFORWARD_PORT (1<<6) /* client port */ 235 236 /* 237 * Spead-matching: we use an unlinked file for transient storage. 238 */ 239 static VSTREAM *smtpd_proxy_replay_stream; 240 241 /* 242 * Forward declarations. 243 */ 244 static void smtpd_proxy_fake_server_reply(SMTPD_STATE *, int); 245 static int smtpd_proxy_rdwr_error(SMTPD_STATE *, int); 246 static int PRINTFLIKE(3, 4) smtpd_proxy_cmd(SMTPD_STATE *, int, const char *,...); 247 static int smtpd_proxy_rec_put(VSTREAM *, int, const char *, ssize_t); 248 249 /* 250 * SLMs. 251 */ 252 #define STR(x) vstring_str(x) 253 #define LEN(x) VSTRING_LEN(x) 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, 362 CA_VSTREAM_CTL_CONTEXT((void *) state), 363 CA_VSTREAM_CTL_END); 364 /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */ 365 if (connect_fn == inet_connect) 366 vstream_tweak_tcp(proxy->service_stream); 367 smtp_timeout_setup(proxy->service_stream, proxy->timeout); 368 369 /* 370 * Get server greeting banner. 371 * 372 * If this fails then we have a problem because the proxy should always 373 * accept our connection. Make up our own response instead of passing 374 * back a negative greeting banner: the proxy open is delayed to the 375 * point that the client expects a MAIL FROM or RCPT TO reply. 376 */ 377 if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "%s", "")) { 378 smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); 379 smtpd_proxy_close(state); 380 return (-1); 381 } 382 383 /* 384 * Send our own EHLO command. If this fails then we have a problem 385 * because the proxy should always accept our EHLO command. Make up our 386 * own response instead of passing back a negative EHLO reply: the proxy 387 * open is delayed to the point that the remote SMTP client expects a 388 * MAIL FROM or RCPT TO reply. 389 */ 390 if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "EHLO %s", 391 proxy->ehlo_name)) { 392 smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); 393 smtpd_proxy_close(state); 394 return (-1); 395 } 396 397 /* 398 * Parse the EHLO reply and see if we can forward logging information. 399 */ 400 server_xforward_features = 0; 401 lines = STR(proxy->reply); 402 while ((words = mystrtok(&lines, "\r\n")) != 0) { 403 if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t")) != 0) { 404 if (strcasecmp(word, XFORWARD_CMD) == 0) 405 while ((word = mystrtok(&words, " \t")) != 0) 406 server_xforward_features |= 407 name_code(known_xforward_features, 408 NAME_CODE_FLAG_NONE, word); 409 } 410 } 411 412 /* 413 * Send XFORWARD attributes. For robustness, explicitly specify what SMTP 414 * session attributes are known and unknown. Make up our own response 415 * instead of passing back a negative XFORWARD reply: the proxy open is 416 * delayed to the point that the remote SMTP client expects a MAIL FROM 417 * or RCPT TO reply. 418 */ 419 if (server_xforward_features) { 420 buf = vstring_alloc(100); 421 bad = 422 (((server_xforward_features & SMTPD_PROXY_XFORWARD_NAME) 423 && smtpd_proxy_xforward_send(state, buf, XFORWARD_NAME, 424 IS_AVAIL_CLIENT_NAME(FORWARD_NAME(state)), 425 FORWARD_NAME(state))) 426 || ((server_xforward_features & SMTPD_PROXY_XFORWARD_ADDR) 427 && smtpd_proxy_xforward_send(state, buf, XFORWARD_ADDR, 428 IS_AVAIL_CLIENT_ADDR(FORWARD_ADDR(state)), 429 FORWARD_ADDR(state))) 430 || ((server_xforward_features & SMTPD_PROXY_XFORWARD_PORT) 431 && smtpd_proxy_xforward_send(state, buf, XFORWARD_PORT, 432 IS_AVAIL_CLIENT_PORT(FORWARD_PORT(state)), 433 FORWARD_PORT(state))) 434 || ((server_xforward_features & SMTPD_PROXY_XFORWARD_HELO) 435 && smtpd_proxy_xforward_send(state, buf, XFORWARD_HELO, 436 IS_AVAIL_CLIENT_HELO(FORWARD_HELO(state)), 437 FORWARD_HELO(state))) 438 || ((server_xforward_features & SMTPD_PROXY_XFORWARD_IDENT) 439 && smtpd_proxy_xforward_send(state, buf, XFORWARD_IDENT, 440 IS_AVAIL_CLIENT_IDENT(FORWARD_IDENT(state)), 441 FORWARD_IDENT(state))) 442 || ((server_xforward_features & SMTPD_PROXY_XFORWARD_PROTO) 443 && smtpd_proxy_xforward_send(state, buf, XFORWARD_PROTO, 444 IS_AVAIL_CLIENT_PROTO(FORWARD_PROTO(state)), 445 FORWARD_PROTO(state))) 446 || ((server_xforward_features & SMTPD_PROXY_XFORWARD_DOMAIN) 447 && smtpd_proxy_xforward_send(state, buf, XFORWARD_DOMAIN, 1, 448 STREQ(FORWARD_DOMAIN(state), MAIL_ATTR_RWR_LOCAL) ? 449 XFORWARD_DOM_LOCAL : XFORWARD_DOM_REMOTE)) 450 || smtpd_proxy_xforward_flush(state, buf)); 451 vstring_free(buf); 452 if (bad) { 453 smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); 454 smtpd_proxy_close(state); 455 return (-1); 456 } 457 } 458 459 /* 460 * Pass-through the remote SMTP client's MAIL FROM command. If this 461 * fails, then we have a problem because the proxy should always accept 462 * any MAIL FROM command that was accepted by us. 463 */ 464 if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "%s", 465 proxy->mail_from) != 0) { 466 /* NOT: smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); */ 467 smtpd_proxy_close(state); 468 return (-1); 469 } 470 return (0); 471 } 472 473 /* smtpd_proxy_fake_server_reply - produce generic error response */ 474 475 static void smtpd_proxy_fake_server_reply(SMTPD_STATE *state, int status) 476 { 477 const CLEANUP_STAT_DETAIL *detail; 478 479 /* 480 * Either we have no server reply (connection refused), or we have an 481 * out-of-protocol server reply, so we make up a generic server error 482 * response instead. 483 */ 484 detail = cleanup_stat_detail(status); 485 vstring_sprintf(state->proxy->reply, 486 "%d %s Error: %s", 487 detail->smtp, detail->dsn, detail->text); 488 } 489 490 /* smtpd_proxy_replay_rdwr_error - report replay log I/O error */ 491 492 static int smtpd_proxy_replay_rdwr_error(SMTPD_STATE *state) 493 { 494 495 /* 496 * Log an appropriate warning message. 497 */ 498 msg_warn("proxy speed-adjust log I/O error: %m"); 499 500 /* 501 * Set the appropriate flags and server reply. 502 */ 503 state->error_mask |= MAIL_ERROR_RESOURCE; 504 /* Update state->err in case we are past the client's DATA command. */ 505 state->err |= CLEANUP_STAT_PROXY; 506 smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); 507 return (-1); 508 } 509 510 /* smtpd_proxy_rdwr_error - report proxy communication error */ 511 512 static int smtpd_proxy_rdwr_error(SMTPD_STATE *state, int err) 513 { 514 const char *myname = "smtpd_proxy_rdwr_error"; 515 SMTPD_PROXY *proxy = state->proxy; 516 517 /* 518 * Sanity check. 519 */ 520 if (err != 0 && err != SMTP_ERR_NONE && proxy == 0) 521 msg_panic("%s: proxy error %d without proxy handle", myname, err); 522 523 /* 524 * Log an appropriate warning message. 525 */ 526 switch (err) { 527 case 0: 528 case SMTP_ERR_NONE: 529 break; 530 case SMTP_ERR_EOF: 531 msg_warn("lost connection with proxy %s", proxy->service_name); 532 break; 533 case SMTP_ERR_TIME: 534 msg_warn("timeout talking to proxy %s", proxy->service_name); 535 break; 536 default: 537 msg_panic("%s: unknown proxy %s error %d", 538 myname, proxy->service_name, err); 539 } 540 541 /* 542 * Set the appropriate flags and server reply. 543 */ 544 state->error_mask |= MAIL_ERROR_SOFTWARE; 545 /* Update state->err in case we are past the client's DATA command. */ 546 state->err |= CLEANUP_STAT_PROXY; 547 smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); 548 return (-1); 549 } 550 551 /* smtpd_proxy_replay_send - replay saved SMTP session from speed-match log */ 552 553 static int smtpd_proxy_replay_send(SMTPD_STATE *state) 554 { 555 const char *myname = "smtpd_proxy_replay_send"; 556 static VSTRING *replay_buf = 0; 557 SMTPD_PROXY *proxy = state->proxy; 558 int rec_type; 559 int expect = SMTPD_PROX_WANT_BAD; 560 561 /* 562 * Sanity check. 563 */ 564 if (smtpd_proxy_replay_stream == 0) 565 msg_panic("%s: no before-queue filter speed-adjust log", myname); 566 567 /* 568 * Errors first. 569 */ 570 if (vstream_ferror(smtpd_proxy_replay_stream) 571 || vstream_feof(smtpd_proxy_replay_stream) 572 || rec_put(smtpd_proxy_replay_stream, REC_TYPE_END, "", 0) != REC_TYPE_END 573 || vstream_fflush(smtpd_proxy_replay_stream)) 574 /* NOT: fsync(vstream_fileno(smtpd_proxy_replay_stream)) */ 575 return (smtpd_proxy_replay_rdwr_error(state)); 576 577 /* 578 * Delayed connection to the before-queue filter. 579 */ 580 if (smtpd_proxy_connect(state) < 0) 581 return (-1); 582 583 /* 584 * Replay the speed-match log. We do sanity check record content, but we 585 * don't implement a protocol state engine here, since we are reading 586 * from a file that we just wrote ourselves. 587 * 588 * This is different than the MailChannels patented solution that 589 * multiplexes a large number of slowed-down inbound connections over a 590 * small number of fast connections to a local MTA. 591 * 592 * - MailChannels receives mail directly from the Internet. It uses one 593 * connection to the local MTA to reject invalid recipients before 594 * receiving the entire email message at reduced bit rates, and then uses 595 * a different connection to quickly deliver the message to the local 596 * MTA. 597 * 598 * - Postfix receives mail directly from the Internet. The Postfix SMTP 599 * server rejects invalid recipients before receiving the entire message 600 * over the Internet, and then delivers the message quickly to a local 601 * SMTP-based content filter. 602 */ 603 if (replay_buf == 0) 604 replay_buf = vstring_alloc(100); 605 if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0) 606 return (smtpd_proxy_replay_rdwr_error(state)); 607 608 for (;;) { 609 switch (rec_type = rec_get(smtpd_proxy_replay_stream, replay_buf, 610 REC_FLAG_NONE)) { 611 612 /* 613 * Message content. 614 */ 615 case REC_TYPE_NORM: 616 case REC_TYPE_CONT: 617 if (smtpd_proxy_rec_put(proxy->service_stream, rec_type, 618 STR(replay_buf), LEN(replay_buf)) < 0) 619 return (-1); 620 break; 621 622 /* 623 * Expected server reply type. 624 */ 625 case REC_TYPE_RCPT: 626 if (!alldig(STR(replay_buf)) 627 || (expect = atoi(STR(replay_buf))) == SMTPD_PROX_WANT_BAD) 628 msg_panic("%s: malformed server reply type: %s", 629 myname, STR(replay_buf)); 630 break; 631 632 /* 633 * Client command, or void. Bail out on the first negative proxy 634 * response. This is OK, because the filter must use the same 635 * reply code for all recipients of a multi-recipient message. 636 */ 637 case REC_TYPE_FROM: 638 if (expect == SMTPD_PROX_WANT_BAD) 639 msg_panic("%s: missing server reply type", myname); 640 if (smtpd_proxy_cmd(state, expect, "%s", STR(replay_buf)) < 0) 641 return (-1); 642 expect = SMTPD_PROX_WANT_BAD; 643 break; 644 645 /* 646 * Explicit end marker, instead of implicit EOF. 647 */ 648 case REC_TYPE_END: 649 return (0); 650 651 /* 652 * Errors. 653 */ 654 case REC_TYPE_ERROR: 655 return (smtpd_proxy_replay_rdwr_error(state)); 656 default: 657 msg_panic("%s: unexpected record type; %d", myname, rec_type); 658 } 659 } 660 } 661 662 /* smtpd_proxy_save_cmd - save SMTP command + expected response to replay log */ 663 664 static int PRINTFLIKE(3, 4) smtpd_proxy_save_cmd(SMTPD_STATE *state, int expect, const char *fmt,...) 665 { 666 va_list ap; 667 668 /* 669 * Errors first. 670 */ 671 if (vstream_ferror(smtpd_proxy_replay_stream) 672 || vstream_feof(smtpd_proxy_replay_stream)) 673 return (smtpd_proxy_replay_rdwr_error(state)); 674 675 /* 676 * Save the expected reply first, so that the replayer can safely 677 * overwrite the input buffer with the command. 678 */ 679 rec_fprintf(smtpd_proxy_replay_stream, REC_TYPE_RCPT, "%d", expect); 680 681 /* 682 * The command can be omitted at the start of an SMTP session. This is 683 * not documented as part of the official interface because it is used 684 * only internally to this module. 685 */ 686 687 /* 688 * Save the command to the replay log, and send it to the before-queue 689 * filter after we have received the entire message. 690 */ 691 va_start(ap, fmt); 692 rec_vfprintf(smtpd_proxy_replay_stream, REC_TYPE_FROM, fmt, ap); 693 va_end(ap); 694 695 /* 696 * If we just saved the "." command, replay the log. 697 */ 698 return (strcmp(fmt, ".") ? 0 : smtpd_proxy_replay_send(state)); 699 } 700 701 /* smtpd_proxy_cmd - send command to proxy, receive reply */ 702 703 static int smtpd_proxy_cmd(SMTPD_STATE *state, int expect, const char *fmt,...) 704 { 705 SMTPD_PROXY *proxy = state->proxy; 706 va_list ap; 707 char *cp; 708 int last_char; 709 int err = 0; 710 static VSTRING *buffer = 0; 711 712 /* 713 * Errors first. Be prepared for delayed errors from the DATA phase. 714 */ 715 if (vstream_ferror(proxy->service_stream) 716 || vstream_feof(proxy->service_stream) 717 || (err = vstream_setjmp(proxy->service_stream)) != 0) { 718 return (smtpd_proxy_rdwr_error(state, err)); 719 } 720 721 /* 722 * Format the command. 723 */ 724 va_start(ap, fmt); 725 vstring_vsprintf(proxy->request, fmt, ap); 726 va_end(ap); 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 (LEN(proxy->request) > 0) { 734 735 /* 736 * Optionally log the command first, so that we can see in the log 737 * what the program is trying to do. 738 */ 739 if (msg_verbose) 740 msg_info("> %s: %s", proxy->service_name, STR(proxy->request)); 741 742 /* 743 * Send the command to the proxy server. Since we're going to read a 744 * reply immediately, there is no need to flush buffers. 745 */ 746 smtp_fputs(STR(proxy->request), LEN(proxy->request), 747 proxy->service_stream); 748 } 749 750 /* 751 * Early return if we don't want to wait for a server reply (such as 752 * after sending QUIT). 753 */ 754 if (expect == SMTPD_PROX_WANT_NONE) 755 return (0); 756 757 /* 758 * Censor out non-printable characters in server responses and save 759 * complete multi-line responses if possible. 760 * 761 * We can't parse or store input that exceeds var_line_limit, so we just 762 * skip over it to simplify the remainder of the code below. 763 */ 764 VSTRING_RESET(proxy->reply); 765 if (buffer == 0) 766 buffer = vstring_alloc(10); 767 for (;;) { 768 last_char = smtp_get(buffer, proxy->service_stream, var_line_limit, 769 SMTP_GET_FLAG_SKIP); 770 printable(STR(buffer), '?'); 771 if (last_char != '\n') 772 msg_warn("%s: response longer than %d: %.30s...", 773 proxy->service_name, var_line_limit, 774 STR(buffer)); 775 if (msg_verbose) 776 msg_info("< %s: %.100s", proxy->service_name, STR(buffer)); 777 778 /* 779 * Defend against a denial of service attack by limiting the amount 780 * of multi-line text that we are willing to store. 781 */ 782 if (LEN(proxy->reply) < var_line_limit) { 783 if (VSTRING_LEN(proxy->reply)) 784 vstring_strcat(proxy->reply, "\r\n"); 785 vstring_strcat(proxy->reply, STR(buffer)); 786 } 787 788 /* 789 * Parse the response into code and text. Ignore unrecognized 790 * garbage. This means that any character except space (or end of 791 * line) will have the same effect as the '-' line continuation 792 * character. 793 */ 794 for (cp = STR(buffer); *cp && ISDIGIT(*cp); cp++) 795 /* void */ ; 796 if (cp - STR(buffer) == 3) { 797 if (*cp == '-') 798 continue; 799 if (*cp == ' ' || *cp == 0) 800 break; 801 } 802 msg_warn("received garbage from proxy %s: %.100s", 803 proxy->service_name, STR(buffer)); 804 } 805 806 /* 807 * Log a warning in case the proxy does not send the expected response. 808 * Silently accept any response when the client expressed no expectation. 809 * 810 * Starting with Postfix 2.6 we don't pass through unexpected 2xx or 3xx 811 * proxy replies. They are a source of support problems, so we replace 812 * them by generic server error replies. 813 */ 814 if (expect != SMTPD_PROX_WANT_ANY && expect != *STR(proxy->reply)) { 815 msg_warn("proxy %s rejected \"%s\": \"%s\"", 816 proxy->service_name, LEN(proxy->request) == 0 ? 817 "connection request" : STR(proxy->request), 818 STR(proxy->reply)); 819 if (*STR(proxy->reply) == SMTPD_PROX_WANT_OK 820 || *STR(proxy->reply) == SMTPD_PROX_WANT_MORE) { 821 smtpd_proxy_rdwr_error(state, 0); 822 } 823 return (-1); 824 } else { 825 return (0); 826 } 827 } 828 829 /* smtpd_proxy_save_rec_put - save message content to replay log */ 830 831 static int smtpd_proxy_save_rec_put(VSTREAM *stream, int rec_type, 832 const char *data, ssize_t len) 833 { 834 const char *myname = "smtpd_proxy_save_rec_put"; 835 int ret; 836 837 #define VSTREAM_TO_SMTPD_STATE(s) ((SMTPD_STATE *) vstream_context(s)) 838 839 /* 840 * Sanity check. 841 */ 842 if (stream == 0) 843 msg_panic("%s: attempt to use closed stream", myname); 844 845 /* 846 * Send one content record. Errors and results must be as with rec_put(). 847 */ 848 if (rec_type == REC_TYPE_NORM || rec_type == REC_TYPE_CONT) 849 ret = rec_put(stream, rec_type, data, len); 850 else 851 msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname); 852 853 /* 854 * Errors last. 855 */ 856 if (ret != rec_type) { 857 (void) smtpd_proxy_replay_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream)); 858 return (REC_TYPE_ERROR); 859 } 860 return (rec_type); 861 } 862 863 /* smtpd_proxy_handle_reqtls - propagate or add REQUIRETLS header */ 864 865 static void smtpd_proxy_handle_reqtls(SMTPD_PROXY *proxy, VSTREAM *stream, 866 const char *data, ssize_t len) 867 { 868 const HEADER_OPTS *hdr_opts; 869 const char *cp; 870 871 if (is_header_buf(data, len)) { 872 if ((hdr_opts = header_opts_find(data)) != 0 873 && hdr_opts->type == HDR_REQTLS_ESMTP) { 874 cp = data + strlen(hdr_opts->name) + 1; 875 while (cp < data + len && ISSPACE(*cp)) 876 cp++; 877 if (data + len == cp + 3 && strncasecmp(cp, "YES", 3) == 0) 878 proxy->reqtls_esmtp_hdr_seen += 1; 879 } 880 } else if (len == 0 || !ISSPACE(data[0])) { 881 if (proxy->reqtls_esmtp_hdr_seen == 0) 882 smtp_fputs("Require-TLS-ESMTP: yes", 883 sizeof("Require-TLS-ESMTP: yes") - 1, stream); 884 proxy->flags &= ~SMTPD_PROXY_FLAG_REQTLS_HDR; 885 } 886 } 887 888 /* smtpd_proxy_rec_put - send message content, rec_put() clone */ 889 890 static int smtpd_proxy_rec_put(VSTREAM *stream, int rec_type, 891 const char *data, ssize_t len) 892 { 893 const char *myname = "smtpd_proxy_rec_put"; 894 int err = 0; 895 896 /* 897 * Errors first. 898 */ 899 if (vstream_ferror(stream) || vstream_feof(stream) 900 || (err = vstream_setjmp(stream)) != 0) { 901 (void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err); 902 return (REC_TYPE_ERROR); 903 } 904 905 /* 906 * Send one content record. Errors and results must be as with rec_put(). 907 */ 908 if (rec_type == REC_TYPE_NORM) { 909 SMTPD_PROXY *proxy = VSTREAM_TO_SMTPD_STATE(stream)->proxy; 910 911 if (proxy->flags & SMTPD_PROXY_FLAG_REQTLS_HDR) 912 smtpd_proxy_handle_reqtls(proxy, stream, data, len); 913 smtp_fputs(data, len, stream); 914 } else if (rec_type == REC_TYPE_CONT) 915 smtp_fwrite(data, len, stream); 916 else 917 msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname); 918 return (rec_type); 919 } 920 921 /* smtpd_proxy_save_rec_fprintf - save message content to replay log */ 922 923 static int smtpd_proxy_save_rec_fprintf(VSTREAM *stream, int rec_type, 924 const char *fmt,...) 925 { 926 const char *myname = "smtpd_proxy_save_rec_fprintf"; 927 va_list ap; 928 int ret; 929 930 /* 931 * Sanity check. 932 */ 933 if (stream == 0) 934 msg_panic("%s: attempt to use closed stream", myname); 935 936 /* 937 * Save one content record. Errors and results must be as with 938 * rec_fprintf(). 939 */ 940 va_start(ap, fmt); 941 if (rec_type == REC_TYPE_NORM) 942 ret = rec_vfprintf(stream, rec_type, fmt, ap); 943 else 944 msg_panic("%s: need REC_TYPE_NORM", myname); 945 va_end(ap); 946 947 /* 948 * Errors last. 949 */ 950 if (ret != rec_type) { 951 (void) smtpd_proxy_replay_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream)); 952 return (REC_TYPE_ERROR); 953 } 954 return (rec_type); 955 } 956 957 /* smtpd_proxy_rec_fprintf - send message content, rec_fprintf() clone */ 958 959 static int smtpd_proxy_rec_fprintf(VSTREAM *stream, int rec_type, 960 const char *fmt,...) 961 { 962 const char *myname = "smtpd_proxy_rec_fprintf"; 963 va_list ap; 964 int err = 0; 965 966 /* 967 * Errors first. 968 */ 969 if (vstream_ferror(stream) || vstream_feof(stream) 970 || (err = vstream_setjmp(stream)) != 0) { 971 (void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err); 972 return (REC_TYPE_ERROR); 973 } 974 975 /* 976 * Send one content record. Errors and results must be as with 977 * rec_fprintf(). 978 */ 979 va_start(ap, fmt); 980 if (rec_type == REC_TYPE_NORM) 981 smtp_vprintf(stream, fmt, ap); 982 else 983 msg_panic("%s: need REC_TYPE_NORM", myname); 984 va_end(ap); 985 return (rec_type); 986 } 987 988 #ifndef NO_TRUNCATE 989 990 /* smtpd_proxy_replay_setup - prepare the replay logfile */ 991 992 static int smtpd_proxy_replay_setup(SMTPD_STATE *state) 993 { 994 const char *myname = "smtpd_proxy_replay_setup"; 995 off_t file_offs; 996 997 /* 998 * Where possible reuse an existing replay logfile, because creating a 999 * file is expensive compared to reading or writing. For security reasons 1000 * we must truncate the file before reuse. For performance reasons we 1001 * should truncate the file immediately after the end of a mail 1002 * transaction. We enforce the security guarantee upon reuse, by 1003 * requiring that no I/O happened since the file was truncated. This is 1004 * less expensive than truncating the file redundantly. 1005 */ 1006 if (smtpd_proxy_replay_stream != 0) { 1007 /* vstream_ftell() won't invoke the kernel, so all errors are mine. */ 1008 if ((file_offs = vstream_ftell(smtpd_proxy_replay_stream)) != 0) 1009 msg_panic("%s: bad before-queue filter speed-adjust log offset %lu", 1010 myname, (unsigned long) file_offs); 1011 vstream_clearerr(smtpd_proxy_replay_stream); 1012 if (msg_verbose) 1013 msg_info("%s: reuse speed-adjust stream fd=%d", myname, 1014 vstream_fileno(smtpd_proxy_replay_stream)); 1015 /* Here, smtpd_proxy_replay_stream != 0 */ 1016 } 1017 1018 /* 1019 * Create a new replay logfile. 1020 */ 1021 if (smtpd_proxy_replay_stream == 0) { 1022 smtpd_proxy_replay_stream = mail_queue_enter(MAIL_QUEUE_INCOMING, 0, 1023 (struct timeval *) 0); 1024 if (smtpd_proxy_replay_stream == 0) 1025 return (smtpd_proxy_replay_rdwr_error(state)); 1026 if (unlink(VSTREAM_PATH(smtpd_proxy_replay_stream)) < 0) 1027 msg_warn("remove before-queue filter speed-adjust log %s: %m", 1028 VSTREAM_PATH(smtpd_proxy_replay_stream)); 1029 if (msg_verbose) 1030 msg_info("%s: new speed-adjust stream fd=%d", myname, 1031 vstream_fileno(smtpd_proxy_replay_stream)); 1032 } 1033 1034 /* 1035 * Needed by our DATA-phase record emulation routines. 1036 */ 1037 vstream_control(smtpd_proxy_replay_stream, 1038 CA_VSTREAM_CTL_CONTEXT((void *) state), 1039 CA_VSTREAM_CTL_END); 1040 return (0); 1041 } 1042 1043 #endif 1044 1045 /* smtpd_proxy_create - set up smtpd proxy handle */ 1046 1047 int smtpd_proxy_create(SMTPD_STATE *state, int flags, const char *service, 1048 int timeout, const char *ehlo_name, 1049 const char *mail_from) 1050 { 1051 SMTPD_PROXY *proxy; 1052 1053 /* 1054 * When an operation has many arguments it is safer to use named 1055 * parameters, and have the compiler enforce the argument count. 1056 */ 1057 #define SMTPD_PROXY_ALLOC(p, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, \ 1058 a12, a13) \ 1059 ((p) = (SMTPD_PROXY *) mymalloc(sizeof(*(p))), (p)->a1, (p)->a2, \ 1060 (p)->a3, (p)->a4, (p)->a5, (p)->a6, (p)->a7, (p)->a8, (p)->a9, \ 1061 (p)->a10, (p)->a11, (p)->a12, (p)->a13, (p)) 1062 1063 /* 1064 * Sanity check. 1065 */ 1066 if (state->proxy != 0) 1067 msg_panic("smtpd_proxy_create: handle still exists"); 1068 1069 /* 1070 * Connect to the before-queue filter immediately. 1071 */ 1072 if ((flags & SMTPD_PROXY_FLAG_SPEED_ADJUST) == 0) { 1073 state->proxy = 1074 SMTPD_PROXY_ALLOC(proxy, stream = 0, request = vstring_alloc(10), 1075 reply = vstring_alloc(10), 1076 cmd = smtpd_proxy_cmd, 1077 rec_fprintf = smtpd_proxy_rec_fprintf, 1078 rec_put = smtpd_proxy_rec_put, 1079 flags = flags, service_stream = 0, 1080 service_name = service, timeout = timeout, 1081 ehlo_name = ehlo_name, mail_from = mail_from, 1082 reqtls_esmtp_hdr_seen = 0); 1083 if (smtpd_proxy_connect(state) < 0) { 1084 /* NOT: smtpd_proxy_free(state); we still need proxy->reply. */ 1085 return (-1); 1086 } 1087 proxy->stream = proxy->service_stream; 1088 return (0); 1089 } 1090 1091 /* 1092 * Connect to the before-queue filter after we receive the entire 1093 * message. Open the replay logfile early to simplify code. The file is 1094 * reused for multiple mail transactions, so there is no need to minimize 1095 * its life time. 1096 */ 1097 else { 1098 #ifdef NO_TRUNCATE 1099 msg_panic("smtpd_proxy_create: speed-adjust support is not available"); 1100 #else 1101 if (smtpd_proxy_replay_setup(state) < 0) 1102 return (-1); 1103 state->proxy = 1104 SMTPD_PROXY_ALLOC(proxy, stream = smtpd_proxy_replay_stream, 1105 request = vstring_alloc(10), 1106 reply = vstring_alloc(10), 1107 cmd = smtpd_proxy_save_cmd, 1108 rec_fprintf = smtpd_proxy_save_rec_fprintf, 1109 rec_put = smtpd_proxy_save_rec_put, 1110 flags = flags, service_stream = 0, 1111 service_name = service, timeout = timeout, 1112 ehlo_name = ehlo_name, mail_from = mail_from, 1113 reqtls_esmtp_hdr_seen = 0); 1114 return (0); 1115 #endif 1116 } 1117 } 1118 1119 /* smtpd_proxy_close - close proxy connection without destroying handle */ 1120 1121 void smtpd_proxy_close(SMTPD_STATE *state) 1122 { 1123 SMTPD_PROXY *proxy = state->proxy; 1124 1125 /* 1126 * Specify SMTPD_PROX_WANT_NONE so that the server reply will not clobber 1127 * the END-OF-DATA reply. 1128 */ 1129 if (proxy->service_stream != 0) { 1130 if (vstream_feof(proxy->service_stream) == 0 1131 && vstream_ferror(proxy->service_stream) == 0) 1132 (void) smtpd_proxy_cmd(state, SMTPD_PROX_WANT_NONE, 1133 SMTPD_CMD_QUIT); 1134 (void) vstream_fclose(proxy->service_stream); 1135 if (proxy->stream == proxy->service_stream) 1136 proxy->stream = 0; 1137 proxy->service_stream = 0; 1138 } 1139 } 1140 1141 /* smtpd_proxy_free - destroy smtpd proxy handle */ 1142 1143 void smtpd_proxy_free(SMTPD_STATE *state) 1144 { 1145 SMTPD_PROXY *proxy = state->proxy; 1146 1147 /* 1148 * Clean up. 1149 */ 1150 if (proxy->service_stream != 0) 1151 (void) smtpd_proxy_close(state); 1152 if (proxy->request != 0) 1153 vstring_free(proxy->request); 1154 if (proxy->reply != 0) 1155 vstring_free(proxy->reply); 1156 myfree((void *) proxy); 1157 state->proxy = 0; 1158 1159 /* 1160 * Reuse the replay logfile if possible. For security reasons we must 1161 * truncate the replay logfile before reuse. For performance reasons we 1162 * should truncate the replay logfile immediately after the end of a mail 1163 * transaction. We truncate the file here, and enforce the security 1164 * guarantee by requiring that no I/O happens before the file is reused. 1165 */ 1166 if (smtpd_proxy_replay_stream == 0) 1167 return; 1168 if (vstream_ferror(smtpd_proxy_replay_stream)) { 1169 /* Errors are already reported. */ 1170 (void) vstream_fclose(smtpd_proxy_replay_stream); 1171 smtpd_proxy_replay_stream = 0; 1172 return; 1173 } 1174 /* Flush output from aborted transaction before truncating the file!! */ 1175 if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0) { 1176 msg_warn("seek before-queue filter speed-adjust log: %m"); 1177 (void) vstream_fclose(smtpd_proxy_replay_stream); 1178 smtpd_proxy_replay_stream = 0; 1179 return; 1180 } 1181 if (ftruncate(vstream_fileno(smtpd_proxy_replay_stream), (off_t) 0) < 0) { 1182 msg_warn("truncate before-queue filter speed-adjust log: %m"); 1183 (void) vstream_fclose(smtpd_proxy_replay_stream); 1184 smtpd_proxy_replay_stream = 0; 1185 return; 1186 } 1187 } 1188 1189 /* smtpd_proxy_parse_opts - parse main.cf options */ 1190 1191 int smtpd_proxy_parse_opts(const char *param_name, const char *param_val) 1192 { 1193 static const NAME_MASK proxy_opts_table[] = { 1194 SMTPD_PROXY_NAME_SPEED_ADJUST, SMTPD_PROXY_FLAG_SPEED_ADJUST, 1195 0, 0, 1196 }; 1197 int flags; 1198 1199 /* 1200 * The optional before-filter speed-adjust buffers use disk space. 1201 * However, we don't know if they compete for storage space with the 1202 * after-filter queue, so we can't simply bump up the free space 1203 * requirement to 2.5 * message_size_limit. 1204 */ 1205 flags = name_mask(param_name, proxy_opts_table, param_val); 1206 if (flags & SMTPD_PROXY_FLAG_SPEED_ADJUST) { 1207 #ifdef NO_TRUNCATE 1208 msg_warn("smtpd_proxy %s support is not available", 1209 SMTPD_PROXY_NAME_SPEED_ADJUST); 1210 flags &= ~SMTPD_PROXY_FLAG_SPEED_ADJUST; 1211 #endif 1212 } 1213 return (flags); 1214 } 1215