xref: /netbsd-src/external/ibm-public/postfix/dist/src/smtpd/smtpd_proxy.c (revision cb861154c176d3dcc8ff846f449e3c16a5f5edb5)
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