Home | History | Annotate | Line # | Download | only in smtpd
      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