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