5
/* SMTP server pass-through proxy client
8
/* #include <smtpd_proxy.h>
12
/* /* other fields... */
13
/* VSTREAM *proxy; /* connection to SMTP proxy */
14
/* VSTRING *proxy_buffer; /* last SMTP proxy response */
15
/* /* other fields... */
19
/* int smtpd_proxy_open(state, service, timeout, ehlo_name, mail_from)
20
/* SMTPD_STATE *state;
21
/* const char *service;
23
/* const char *ehlo_name;
24
/* const char *mail_from;
26
/* int smtpd_proxy_cmd(state, expect, format, ...)
27
/* SMTPD_STATE *state;
31
/* void smtpd_proxy_close(state)
32
/* SMTPD_STATE *state;
33
/* RECORD-LEVEL ROUTINES
34
/* int smtpd_proxy_rec_put(stream, rec_type, data, len)
40
/* int smtpd_proxy_rec_fprintf(stream, rec_type, format, ...)
45
/* The functions in this module implement a pass-through proxy
48
/* In order to minimize the intrusiveness of pass-through proxying, 1) the
49
/* proxy server must support the same MAIL FROM/RCPT syntax that Postfix
50
/* supports, 2) the record-level routines for message content proxying
51
/* have the same interface as the routines that are used for non-proxied
54
/* smtpd_proxy_open() connects to the proxy service, sends EHLO, sends
55
/* client information with the XFORWARD command if possible, sends
56
/* the MAIL FROM command, and receives the reply. A non-zero result means
57
/* trouble: either the proxy is unavailable, or it did not send the
59
/* The result is reported via the state->proxy_buffer field in a form
60
/* that can be sent to the SMTP client. In case of error, the
61
/* state->error_mask and state->err fields are updated.
62
/* A state->proxy_buffer field is created automatically; this field
63
/* persists beyond the end of a proxy session.
65
/* smtpd_proxy_cmd() formats and sends the specified command to the
66
/* proxy server, and receives the proxy server reply. A non-zero result
67
/* means trouble: either the proxy is unavailable, or it did not send the
69
/* All results are reported via the state->proxy_buffer field in a form
70
/* that can be sent to the SMTP client. In case of error, the
71
/* state->error_mask and state->err fields are updated.
73
/* smtpd_proxy_close() disconnects from a proxy server and resets
74
/* the state->proxy field. The last proxy server reply or error
75
/* description remains available via state->proxy-reply.
77
/* smtpd_proxy_rec_put() is a rec_put() clone that passes arbitrary
78
/* message content records to the proxy server. The data is expected
79
/* to be in SMTP dot-escaped form. All errors are reported as a
80
/* REC_TYPE_ERROR result value.
82
/* smtpd_proxy_rec_fprintf() is a rec_fprintf() clone that formats
83
/* message content and sends it to the proxy server. Leading dots are
84
/* not escaped. All errors are reported as a REC_TYPE_ERROR result
89
/* The SMTP proxy server host:port. The host or host: part is optional.
91
/* Time limit for connecting to the proxy server and for
92
/* sending and receiving proxy server commands and replies.
94
/* The EHLO Hostname that will be sent to the proxy server.
96
/* The MAIL FROM command.
100
/* Expected proxy server reply status code range. A warning is logged
101
/* when an unexpected reply is received. Specify one of the following:
103
/* .IP SMTPD_PROX_WANT_OK
104
/* The caller expects a reply in the 200 range.
105
/* .IP SMTPD_PROX_WANT_MORE
106
/* The caller expects a reply in the 300 range.
107
/* .IP SMTPD_PROX_WANT_ANY
108
/* The caller has no expectation. Do not warn for unexpected replies.
109
/* .IP SMTPD_PROX_WANT_NONE
110
/* Do not bother waiting for a reply.
115
/* Connection to proxy server.
117
/* Pointer to the content of one message content record.
119
/* The length of a message content record.
121
/* smtpd(8) Postfix smtp server
123
/* Fatal errors: memory allocation problem.
125
/* Warnings: unexpected response from proxy server, unable
126
/* to connect to proxy server, proxy server read/write error.
130
/* The Secure Mailer license must be distributed with this software.
133
/* IBM T.J. Watson Research
135
/* Yorktown Heights, NY 10598, USA
138
/* System library. */
140
#include <sys_defs.h>
143
/* Utility library. */
148
#include <stringops.h>
150
#include <name_code.h>
152
/* Global library. */
154
#include <mail_error.h>
155
#include <smtp_stream.h>
156
#include <cleanup_user.h>
157
#include <mail_params.h>
158
#include <rec_type.h>
159
#include <mail_proto.h>
161
/* Application-specific. */
164
#include <smtpd_proxy.h>
167
* XFORWARD server features, recognized by the pass-through proxy client.
169
#define SMTPD_PROXY_XFORWARD_NAME (1<<0) /* client name */
170
#define SMTPD_PROXY_XFORWARD_ADDR (1<<1) /* client address */
171
#define SMTPD_PROXY_XFORWARD_PROTO (1<<2) /* protocol */
172
#define SMTPD_PROXY_XFORWARD_HELO (1<<3) /* client helo */
173
#define SMTPD_PROXY_XFORWARD_IDENT (1<<4) /* message identifier */
178
#define STR(x) vstring_str(x)
179
#define LEN(x) VSTRING_LEN(x)
180
#define SMTPD_PROXY_CONNECT ((char *) 0)
182
/* smtpd_xforward_flush - flush forwarding information */
184
static int smtpd_xforward_flush(SMTPD_STATE *state, VSTRING *buf)
188
if (VSTRING_LEN(buf) > 0) {
189
ret = smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK,
190
XFORWARD_CMD "%s", STR(buf));
197
/* smtpd_xforward - send forwarding information */
199
static int smtpd_xforward(SMTPD_STATE *state, VSTRING *buf, const char *name,
200
int value_available, const char *value)
205
#define CONSTR_LEN(s) (sizeof(s) - 1)
206
#define PAYLOAD_LIMIT (512 - CONSTR_LEN("250 " XFORWARD_CMD "\r\n"))
209
* How much space does this attribute need?
211
if (!value_available)
212
value = XFORWARD_UNAVAILABLE;
213
new_len = strlen(name) + strlen(value) + 2; /* SPACE name = value */
214
if (new_len > PAYLOAD_LIMIT)
215
msg_warn("%s command payload %s=%.10s... exceeds SMTP protocol limit",
216
XFORWARD_CMD, name, value);
219
* Flush the buffer if we need to, and store the attribute.
221
if (VSTRING_LEN(buf) > 0 && VSTRING_LEN(buf) + new_len > PAYLOAD_LIMIT)
222
if ((ret = smtpd_xforward_flush(state, buf)) < 0)
224
vstring_sprintf_append(buf, " %s=%s", name, value);
229
/* smtpd_proxy_open - open proxy connection */
231
int smtpd_proxy_open(SMTPD_STATE *state, const char *service,
232
int timeout, const char *ehlo_name,
233
const char *mail_from)
241
static NAME_CODE xforward_features[] = {
242
XFORWARD_NAME, SMTPD_PROXY_XFORWARD_NAME,
243
XFORWARD_ADDR, SMTPD_PROXY_XFORWARD_ADDR,
244
XFORWARD_PROTO, SMTPD_PROXY_XFORWARD_PROTO,
245
XFORWARD_HELO, SMTPD_PROXY_XFORWARD_HELO,
250
* This buffer persists beyond the end of a proxy session so we can
251
* inspect the last command's reply.
253
if (state->proxy_buffer == 0)
254
state->proxy_buffer = vstring_alloc(10);
259
if ((fd = inet_connect(service, BLOCKING, timeout)) < 0) {
260
state->error_mask |= MAIL_ERROR_SOFTWARE;
261
state->err |= CLEANUP_STAT_PROXY;
262
msg_warn("connect to proxy service %s: %m", service);
263
vstring_sprintf(state->proxy_buffer,
264
"451 Error: queue file write error");
267
state->proxy = vstream_fdopen(fd, O_RDWR);
268
vstream_control(state->proxy, VSTREAM_CTL_PATH, service, VSTREAM_CTL_END);
269
smtp_timeout_setup(state->proxy, timeout);
272
* Get server greeting banner.
274
* If this fails then we have a problem because the proxy should always
275
* accept our connection. Make up our own response instead of passing
276
* back the greeting banner: the proxy open might be delayed to the point
277
* that the client expects a MAIL FROM or RCPT TO reply.
279
if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, SMTPD_PROXY_CONNECT) != 0) {
280
vstring_sprintf(state->proxy_buffer,
281
"451 Error: queue file write error");
282
smtpd_proxy_close(state);
287
* Send our own EHLO command. If this fails then we have a problem
288
* because the proxy should always accept our EHLO command. Make up our
289
* own response instead of passing back the EHLO reply: the proxy open
290
* might be delayed to the point that the client expects a MAIL FROM or
293
if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "EHLO %s", ehlo_name) != 0) {
294
vstring_sprintf(state->proxy_buffer,
295
"451 Error: queue file write error");
296
smtpd_proxy_close(state);
301
* Parse the EHLO reply and see if we can forward logging information.
303
state->proxy_xforward_features = 0;
304
lines = STR(state->proxy_buffer);
305
while ((words = mystrtok(&lines, "\n")) != 0) {
306
if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t")) != 0) {
307
if (strcasecmp(word, XFORWARD_CMD) == 0)
308
while ((word = mystrtok(&words, " \t")) != 0)
309
state->proxy_xforward_features |=
310
name_code(xforward_features, NAME_CODE_FLAG_NONE, word);
315
* Send XFORWARD attributes. For robustness, explicitly specify what SMTP
316
* session attributes are known and unknown.
318
if (state->proxy_xforward_features) {
319
buf = vstring_alloc(100);
321
if ((state->proxy_xforward_features & SMTPD_PROXY_XFORWARD_NAME)
322
&& !(bad = smtpd_xforward(state, buf, XFORWARD_NAME,
323
IS_AVAIL_CLIENT_NAME(FORWARD_NAME(state)),
324
FORWARD_NAME(state)))
325
&& (state->proxy_xforward_features & SMTPD_PROXY_XFORWARD_ADDR)
326
&& !(bad = smtpd_xforward(state, buf, XFORWARD_ADDR,
327
IS_AVAIL_CLIENT_ADDR(FORWARD_ADDR(state)),
328
FORWARD_ADDR(state)))
329
&& (state->proxy_xforward_features & SMTPD_PROXY_XFORWARD_HELO)
330
&& !(bad = smtpd_xforward(state, buf, XFORWARD_HELO,
331
IS_AVAIL_CLIENT_HELO(FORWARD_HELO(state)),
332
FORWARD_HELO(state)))
333
&& (state->proxy_xforward_features & SMTPD_PROXY_XFORWARD_PROTO)
334
&& !(bad = smtpd_xforward(state, buf, XFORWARD_PROTO,
335
IS_AVAIL_CLIENT_PROTO(FORWARD_PROTO(state)),
336
FORWARD_PROTO(state))))
337
bad = smtpd_xforward_flush(state, buf);
340
vstring_sprintf(state->proxy_buffer,
341
"451 Error: queue file write error");
342
smtpd_proxy_close(state);
348
* Pass-through the client's MAIL FROM command. If this fails, then we
349
* have a problem because the proxy should always accept any MAIL FROM
350
* command that was accepted by us.
352
if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "%s", mail_from) != 0) {
353
smtpd_proxy_close(state);
359
/* smtpd_proxy_rdwr_error - report proxy communication error */
361
static int smtpd_proxy_rdwr_error(VSTREAM *stream, int err)
365
msg_warn("lost connection with proxy %s", VSTREAM_PATH(stream));
368
msg_warn("timeout talking to proxy %s", VSTREAM_PATH(stream));
371
msg_panic("smtpd_proxy_rdwr_error: unknown proxy %s stream error %d",
372
VSTREAM_PATH(stream), err);
376
/* smtpd_proxy_cmd_error - report unexpected proxy reply */
378
static void smtpd_proxy_cmd_error(SMTPD_STATE *state, const char *fmt,
384
* The command can be omitted at the start of an SMTP session. A null
385
* format string is not documented as part of the official interface
386
* because it is used only internally to this module.
388
buf = vstring_alloc(100);
389
vstring_vsprintf(buf, fmt == SMTPD_PROXY_CONNECT ?
390
"connection request" : fmt, ap);
391
msg_warn("proxy %s rejected \"%s\": \"%s\"", VSTREAM_PATH(state->proxy),
392
STR(buf), STR(state->proxy_buffer));
396
/* smtpd_proxy_cmd - send command to proxy, receive reply */
398
int smtpd_proxy_cmd(SMTPD_STATE *state, int expect, const char *fmt,...)
404
static VSTRING *buffer = 0;
407
* Errors first. Be prepared for delayed errors from the DATA phase.
409
if (vstream_ftimeout(state->proxy)
410
|| vstream_ferror(state->proxy)
411
|| vstream_feof(state->proxy)
412
|| ((err = vstream_setjmp(state->proxy) != 0)
413
&& smtpd_proxy_rdwr_error(state->proxy, err))) {
414
state->error_mask |= MAIL_ERROR_SOFTWARE;
415
state->err |= CLEANUP_STAT_PROXY;
416
vstring_sprintf(state->proxy_buffer,
417
"451 Error: queue file write error");
422
* The command can be omitted at the start of an SMTP session. This is
423
* not documented as part of the official interface because it is used
424
* only internally to this module.
426
if (fmt != SMTPD_PROXY_CONNECT) {
429
* Format the command.
432
vstring_vsprintf(state->proxy_buffer, fmt, ap);
436
* Optionally log the command first, so that we can see in the log
437
* what the program is trying to do.
440
msg_info("> %s: %s", VSTREAM_PATH(state->proxy),
441
STR(state->proxy_buffer));
444
* Send the command to the proxy server. Since we're going to read a
445
* reply immediately, there is no need to flush buffers.
447
smtp_fputs(STR(state->proxy_buffer), LEN(state->proxy_buffer),
452
* Early return if we don't want to wait for a server reply (such as
453
* after sending QUIT.
455
if (expect == SMTPD_PROX_WANT_NONE)
459
* Censor out non-printable characters in server responses and save
460
* complete multi-line responses if possible.
462
VSTRING_RESET(state->proxy_buffer);
464
buffer = vstring_alloc(10);
466
last_char = smtp_get(buffer, state->proxy, var_line_limit);
467
printable(STR(buffer), '?');
468
if (last_char != '\n')
469
msg_warn("%s: response longer than %d: %.30s...",
470
VSTREAM_PATH(state->proxy), var_line_limit,
473
msg_info("< %s: %.100s", VSTREAM_PATH(state->proxy),
477
* Defend against a denial of service attack by limiting the amount
478
* of multi-line text that we are willing to store.
480
if (LEN(state->proxy_buffer) < var_line_limit) {
481
if (VSTRING_LEN(state->proxy_buffer))
482
VSTRING_ADDCH(state->proxy_buffer, '\n');
483
vstring_strcat(state->proxy_buffer, STR(buffer));
487
* Parse the response into code and text. Ignore unrecognized
488
* garbage. This means that any character except space (or end of
489
* line) will have the same effect as the '-' line continuation
492
for (cp = STR(buffer); *cp && ISDIGIT(*cp); cp++)
494
if (cp - STR(buffer) == 3) {
497
if (*cp == ' ' || *cp == 0)
500
msg_warn("received garbage from proxy %s: %.100s",
501
VSTREAM_PATH(state->proxy), STR(buffer));
505
* Log a warning in case the proxy does not send the expected response.
506
* Silently accept any response when the client expressed no expectation.
508
if (expect != SMTPD_PROX_WANT_ANY && expect != *STR(state->proxy_buffer)) {
510
smtpd_proxy_cmd_error(state, fmt, ap);
518
/* smtpd_proxy_rec_put - send message content, rec_put() clone */
520
int smtpd_proxy_rec_put(VSTREAM *stream, int rec_type,
521
const char *data, int len)
528
if (vstream_ftimeout(stream) || vstream_ferror(stream)
529
|| vstream_feof(stream))
530
return (REC_TYPE_ERROR);
531
if ((err = vstream_setjmp(stream)) != 0)
532
return (smtpd_proxy_rdwr_error(stream, err), REC_TYPE_ERROR);
535
* Send one content record. Errors and results must be as with rec_put().
537
if (rec_type == REC_TYPE_NORM)
538
smtp_fputs(data, len, stream);
539
else if (rec_type == REC_TYPE_CONT)
540
smtp_fwrite(data, len, stream);
542
msg_panic("smtpd_proxy_rec_put: need REC_TYPE_NORM or REC_TYPE_CONT");
546
/* smtpd_proxy_rec_fprintf - send message content, rec_fprintf() clone */
548
int smtpd_proxy_rec_fprintf(VSTREAM *stream, int rec_type,
557
if (vstream_ftimeout(stream) || vstream_ferror(stream)
558
|| vstream_feof(stream))
559
return (REC_TYPE_ERROR);
560
if ((err = vstream_setjmp(stream)) != 0)
561
return (smtpd_proxy_rdwr_error(stream, err), REC_TYPE_ERROR);
564
* Send one content record. Errors and results must be as with
568
if (rec_type == REC_TYPE_NORM)
569
smtp_vprintf(stream, fmt, ap);
571
msg_panic("smtpd_proxy_rec_fprintf: need REC_TYPE_NORM");
576
/* smtpd_proxy_close - close proxy connection */
578
void smtpd_proxy_close(SMTPD_STATE *state)
580
(void) vstream_fclose(state->proxy);