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