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