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