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