No empty .Rs/.Re
[netbsd-mini2440.git] / external / ibm-public / postfix / dist / src / smtpd / smtpd_proxy.c
blobb8ba017644c9e7dca691cff82b9d36f225f30d45
1 /* $NetBSD$ */
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>
12 /* typedef struct {
13 /* .in +4
14 /* /* other fields... */
15 /* VSTREAM *proxy; /* connection to SMTP proxy */
16 /* VSTRING *proxy_buffer; /* last SMTP proxy response */
17 /* /* other fields... */
18 /* .in -4
19 /* } SMTPD_STATE;
21 /* int smtpd_proxy_open(state, service, timeout, ehlo_name, mail_from)
22 /* SMTPD_STATE *state;
23 /* const char *service;
24 /* int timeout;
25 /* const char *ehlo_name;
26 /* const char *mail_from;
28 /* int smtpd_proxy_cmd(state, expect, format, ...)
29 /* SMTPD_STATE *state;
30 /* int expect;
31 /* cont char *format;
33 /* void smtpd_proxy_close(state)
34 /* SMTPD_STATE *state;
35 /* RECORD-LEVEL ROUTINES
36 /* int smtpd_proxy_rec_put(stream, rec_type, data, len)
37 /* VSTREAM *stream;
38 /* int rec_type;
39 /* const char *data;
40 /* ssize_t len;
42 /* int smtpd_proxy_rec_fprintf(stream, rec_type, format, ...)
43 /* VSTREAM *stream;
44 /* int rec_type;
45 /* cont char *format;
46 /* DESCRIPTION
47 /* The functions in this module implement a pass-through proxy
48 /* client.
50 /* In order to minimize the intrusiveness of pass-through proxying, 1) the
51 /* proxy server must support the same MAIL FROM/RCPT syntax that Postfix
52 /* supports, 2) the record-level routines for message content proxying
53 /* have the same interface as the routines that are used for non-proxied
54 /* mail.
56 /* smtpd_proxy_open() connects to the proxy service, sends EHLO, sends
57 /* client information with the XFORWARD command if possible, sends
58 /* the MAIL FROM command, and receives the reply. A non-zero result means
59 /* trouble: either the proxy is unavailable, or it did not send the
60 /* expected reply.
61 /* The result is reported via the state->proxy_buffer field in a form
62 /* that can be sent to the SMTP client. In case of error, the
63 /* state->error_mask and state->err fields are updated.
64 /* A state->proxy_buffer field is created automatically; this field
65 /* persists beyond the end of a proxy session.
67 /* smtpd_proxy_cmd() formats and sends the specified command to the
68 /* proxy server, and receives the proxy server reply. A non-zero result
69 /* means trouble: either the proxy is unavailable, or it did not send the
70 /* expected reply.
71 /* All results are reported via the state->proxy_buffer field in a form
72 /* that can be sent to the SMTP client. In case of error, the
73 /* state->error_mask and state->err fields are updated.
75 /* smtpd_proxy_close() disconnects from a proxy server and resets
76 /* the state->proxy field. The last proxy server reply or error
77 /* description remains available via state->proxy-reply.
79 /* smtpd_proxy_rec_put() is a rec_put() clone that passes arbitrary
80 /* message content records to the proxy server. The data is expected
81 /* to be in SMTP dot-escaped form. All errors are reported as a
82 /* REC_TYPE_ERROR result value.
84 /* smtpd_proxy_rec_fprintf() is a rec_fprintf() clone that formats
85 /* message content and sends it to the proxy server. Leading dots are
86 /* not escaped. All errors are reported as a REC_TYPE_ERROR result
87 /* value.
89 /* Arguments:
90 /* .IP server
91 /* The SMTP proxy server host:port. The host or host: part is optional.
92 /* .IP timeout
93 /* Time limit for connecting to the proxy server and for
94 /* sending and receiving proxy server commands and replies.
95 /* .IP ehlo_name
96 /* The EHLO Hostname that will be sent to the proxy server.
97 /* .IP mail_from
98 /* The MAIL FROM command.
99 /* .IP state
100 /* SMTP server state.
101 /* .IP expect
102 /* Expected proxy server reply status code range. A warning is logged
103 /* when an unexpected reply is received. Specify one of the following:
104 /* .RS
105 /* .IP SMTPD_PROX_WANT_OK
106 /* The caller expects a reply in the 200 range.
107 /* .IP SMTPD_PROX_WANT_MORE
108 /* The caller expects a reply in the 300 range.
109 /* .IP SMTPD_PROX_WANT_ANY
110 /* The caller has no expectation. Do not warn for unexpected replies.
111 /* .IP SMTPD_PROX_WANT_NONE
112 /* Do not bother waiting for a reply.
113 /* .RE
114 /* .IP format
115 /* A format string.
116 /* .IP stream
117 /* Connection to proxy server.
118 /* .IP data
119 /* Pointer to the content of one message content record.
120 /* .IP len
121 /* The length of a message content record.
122 /* SEE ALSO
123 /* smtpd(8) Postfix smtp server
124 /* DIAGNOSTICS
125 /* Fatal errors: memory allocation problem.
127 /* Warnings: unexpected response from proxy server, unable
128 /* to connect to proxy server, proxy server read/write error.
129 /* LICENSE
130 /* .ad
131 /* .fi
132 /* The Secure Mailer license must be distributed with this software.
133 /* AUTHOR(S)
134 /* Wietse Venema
135 /* IBM T.J. Watson Research
136 /* P.O. Box 704
137 /* Yorktown Heights, NY 10598, USA
138 /*--*/
140 /* System library. */
142 #include <sys_defs.h>
143 #include <ctype.h>
145 #ifdef STRCASECMP_IN_STRINGS_H
146 #include <strings.h>
147 #endif
149 /* Utility library. */
151 #include <msg.h>
152 #include <vstream.h>
153 #include <vstring.h>
154 #include <stringops.h>
155 #include <connect.h>
156 #include <name_code.h>
158 /* Global library. */
160 #include <mail_error.h>
161 #include <smtp_stream.h>
162 #include <cleanup_user.h>
163 #include <mail_params.h>
164 #include <rec_type.h>
165 #include <mail_proto.h>
166 #include <mail_params.h> /* null_format_string */
167 #include <xtext.h>
169 /* Application-specific. */
171 #include <smtpd.h>
172 #include <smtpd_proxy.h>
175 * XFORWARD server features, recognized by the pass-through proxy client.
177 #define SMTPD_PROXY_XFORWARD_NAME (1<<0) /* client name */
178 #define SMTPD_PROXY_XFORWARD_ADDR (1<<1) /* client address */
179 #define SMTPD_PROXY_XFORWARD_PROTO (1<<2) /* protocol */
180 #define SMTPD_PROXY_XFORWARD_HELO (1<<3) /* client helo */
181 #define SMTPD_PROXY_XFORWARD_IDENT (1<<4) /* message identifier */
182 #define SMTPD_PROXY_XFORWARD_DOMAIN (1<<5) /* origin type */
183 #define SMTPD_PROXY_XFORWARD_PORT (1<<6) /* client port */
186 * SLMs.
188 #define STR(x) vstring_str(x)
189 #define LEN(x) VSTRING_LEN(x)
190 #define SMTPD_PROXY_CONNECT null_format_string
191 #define STREQ(x, y) (strcmp((x), (y)) == 0)
193 /* smtpd_xforward_flush - flush forwarding information */
195 static int smtpd_xforward_flush(SMTPD_STATE *state, VSTRING *buf)
197 int ret;
199 if (VSTRING_LEN(buf) > 0) {
200 ret = smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK,
201 XFORWARD_CMD "%s", STR(buf));
202 VSTRING_RESET(buf);
203 return (ret);
205 return (0);
208 /* smtpd_xforward - send forwarding information */
210 static int smtpd_xforward(SMTPD_STATE *state, VSTRING *buf, const char *name,
211 int value_available, const char *value)
213 size_t new_len;
214 int ret;
216 #define CONSTR_LEN(s) (sizeof(s) - 1)
217 #define PAYLOAD_LIMIT (512 - CONSTR_LEN("250 " XFORWARD_CMD "\r\n"))
219 if (!value_available)
220 value = XFORWARD_UNAVAILABLE;
223 * Encode the attribute value.
225 if (state->expand_buf == 0)
226 state->expand_buf = vstring_alloc(100);
227 xtext_quote(state->expand_buf, value, "");
230 * How much space does this attribute need? SPACE name = value.
232 new_len = strlen(name) + strlen(STR(state->expand_buf)) + 2;
233 if (new_len > PAYLOAD_LIMIT)
234 msg_warn("%s command payload %s=%.10s... exceeds SMTP protocol limit",
235 XFORWARD_CMD, name, value);
238 * Flush the buffer if we need to, and store the attribute.
240 if (VSTRING_LEN(buf) > 0 && VSTRING_LEN(buf) + new_len > PAYLOAD_LIMIT)
241 if ((ret = smtpd_xforward_flush(state, buf)) < 0)
242 return (ret);
243 vstring_sprintf_append(buf, " %s=%s", name, STR(state->expand_buf));
245 return (0);
248 /* smtpd_proxy_open - open proxy connection */
250 int smtpd_proxy_open(SMTPD_STATE *state, const char *service,
251 int timeout, const char *ehlo_name,
252 const char *mail_from)
254 int fd;
255 char *lines;
256 char *words;
257 VSTRING *buf;
258 int bad;
259 char *word;
260 static const NAME_CODE xforward_features[] = {
261 XFORWARD_NAME, SMTPD_PROXY_XFORWARD_NAME,
262 XFORWARD_ADDR, SMTPD_PROXY_XFORWARD_ADDR,
263 XFORWARD_PORT, SMTPD_PROXY_XFORWARD_PORT,
264 XFORWARD_PROTO, SMTPD_PROXY_XFORWARD_PROTO,
265 XFORWARD_HELO, SMTPD_PROXY_XFORWARD_HELO,
266 XFORWARD_DOMAIN, SMTPD_PROXY_XFORWARD_DOMAIN,
267 0, 0,
269 const CLEANUP_STAT_DETAIL *detail;
270 int (*connect_fn) (const char *, int, int);
271 const char *endpoint;
274 * This buffer persists beyond the end of a proxy session so we can
275 * inspect the last command's reply.
277 if (state->proxy_buffer == 0)
278 state->proxy_buffer = vstring_alloc(10);
281 * Find connection method (default inet)
283 if (strncasecmp("unix:", service, 5) == 0) {
284 endpoint = service + 5;
285 connect_fn = unix_connect;
286 } else {
287 if (strncasecmp("inet:", service, 5) == 0)
288 endpoint = service + 5;
289 else
290 endpoint = service;
291 connect_fn = inet_connect;
295 * Connect to proxy.
297 if ((fd = connect_fn(endpoint, BLOCKING, timeout)) < 0) {
298 state->error_mask |= MAIL_ERROR_SOFTWARE;
299 state->err |= CLEANUP_STAT_PROXY;
300 msg_warn("connect to proxy service %s: %m", service);
301 detail = cleanup_stat_detail(CLEANUP_STAT_PROXY);
302 vstring_sprintf(state->proxy_buffer,
303 "%d %s Error: %s",
304 detail->smtp, detail->dsn, detail->text);
305 return (-1);
307 state->proxy = vstream_fdopen(fd, O_RDWR);
308 vstream_control(state->proxy, VSTREAM_CTL_PATH, service, VSTREAM_CTL_END);
309 smtp_timeout_setup(state->proxy, timeout);
312 * Get server greeting banner.
314 * If this fails then we have a problem because the proxy should always
315 * accept our connection. Make up our own response instead of passing
316 * back the greeting banner: the proxy open might be delayed to the point
317 * that the client expects a MAIL FROM or RCPT TO reply.
319 if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, SMTPD_PROXY_CONNECT) != 0) {
320 detail = cleanup_stat_detail(CLEANUP_STAT_PROXY);
321 vstring_sprintf(state->proxy_buffer,
322 "%d %s Error: %s",
323 detail->smtp, detail->dsn, detail->text);
324 smtpd_proxy_close(state);
325 return (-1);
329 * Send our own EHLO command. If this fails then we have a problem
330 * because the proxy should always accept our EHLO command. Make up our
331 * own response instead of passing back the EHLO reply: the proxy open
332 * might be delayed to the point that the client expects a MAIL FROM or
333 * RCPT TO reply.
335 if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "EHLO %s", ehlo_name) != 0) {
336 detail = cleanup_stat_detail(CLEANUP_STAT_PROXY);
337 vstring_sprintf(state->proxy_buffer,
338 "%d %s Error: %s",
339 detail->smtp, detail->dsn, detail->text);
340 smtpd_proxy_close(state);
341 return (-1);
345 * Parse the EHLO reply and see if we can forward logging information.
347 state->proxy_xforward_features = 0;
348 lines = STR(state->proxy_buffer);
349 while ((words = mystrtok(&lines, "\n")) != 0) {
350 if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t")) != 0) {
351 if (strcasecmp(word, XFORWARD_CMD) == 0)
352 while ((word = mystrtok(&words, " \t")) != 0)
353 state->proxy_xforward_features |=
354 name_code(xforward_features, NAME_CODE_FLAG_NONE, word);
359 * Send XFORWARD attributes. For robustness, explicitly specify what SMTP
360 * session attributes are known and unknown.
362 if (state->proxy_xforward_features) {
363 buf = vstring_alloc(100);
364 bad = 0;
365 if (state->proxy_xforward_features & SMTPD_PROXY_XFORWARD_NAME)
366 bad = smtpd_xforward(state, buf, XFORWARD_NAME,
367 IS_AVAIL_CLIENT_NAME(FORWARD_NAME(state)),
368 FORWARD_NAME(state));
369 if (bad == 0
370 && (state->proxy_xforward_features & SMTPD_PROXY_XFORWARD_ADDR))
371 bad = smtpd_xforward(state, buf, XFORWARD_ADDR,
372 IS_AVAIL_CLIENT_ADDR(FORWARD_ADDR(state)),
373 FORWARD_ADDR(state));
374 if (bad == 0
375 && (state->proxy_xforward_features & SMTPD_PROXY_XFORWARD_PORT))
376 bad = smtpd_xforward(state, buf, XFORWARD_PORT,
377 IS_AVAIL_CLIENT_PORT(FORWARD_PORT(state)),
378 FORWARD_PORT(state));
379 if (bad == 0
380 && (state->proxy_xforward_features & SMTPD_PROXY_XFORWARD_HELO))
381 bad = smtpd_xforward(state, buf, XFORWARD_HELO,
382 IS_AVAIL_CLIENT_HELO(FORWARD_HELO(state)),
383 FORWARD_HELO(state));
384 if (bad == 0
385 && (state->proxy_xforward_features & SMTPD_PROXY_XFORWARD_PROTO))
386 bad = smtpd_xforward(state, buf, XFORWARD_PROTO,
387 IS_AVAIL_CLIENT_PROTO(FORWARD_PROTO(state)),
388 FORWARD_PROTO(state));
389 if (bad == 0
390 && (state->proxy_xforward_features & SMTPD_PROXY_XFORWARD_DOMAIN))
391 bad = smtpd_xforward(state, buf, XFORWARD_DOMAIN, 1,
392 STREQ(FORWARD_DOMAIN(state), MAIL_ATTR_RWR_LOCAL) ?
393 XFORWARD_DOM_LOCAL : XFORWARD_DOM_REMOTE);
394 if (bad == 0)
395 bad = smtpd_xforward_flush(state, buf);
396 vstring_free(buf);
397 if (bad) {
398 detail = cleanup_stat_detail(CLEANUP_STAT_PROXY);
399 vstring_sprintf(state->proxy_buffer,
400 "%d %s Error: %s",
401 detail->smtp, detail->dsn, detail->text);
402 smtpd_proxy_close(state);
403 return (-1);
408 * Pass-through the client's MAIL FROM command. If this fails, then we
409 * have a problem because the proxy should always accept any MAIL FROM
410 * command that was accepted by us.
412 if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "%s", mail_from) != 0) {
413 smtpd_proxy_close(state);
414 return (-1);
416 return (0);
419 /* smtpd_proxy_rdwr_error - report proxy communication error */
421 static int smtpd_proxy_rdwr_error(VSTREAM *stream, int err)
423 switch (err) {
424 case SMTP_ERR_EOF:
425 msg_warn("lost connection with proxy %s", VSTREAM_PATH(stream));
426 return (err);
427 case SMTP_ERR_TIME:
428 msg_warn("timeout talking to proxy %s", VSTREAM_PATH(stream));
429 return (err);
430 default:
431 msg_panic("smtpd_proxy_rdwr_error: unknown proxy %s stream error %d",
432 VSTREAM_PATH(stream), err);
436 /* smtpd_proxy_cmd_error - report unexpected proxy reply */
438 static void smtpd_proxy_cmd_error(SMTPD_STATE *state, const char *fmt,
439 va_list ap)
441 VSTRING *buf;
444 * The command can be omitted at the start of an SMTP session. A null
445 * format string is not documented as part of the official interface
446 * because it is used only internally to this module.
448 buf = vstring_alloc(100);
449 vstring_vsprintf(buf, fmt == SMTPD_PROXY_CONNECT ?
450 "connection request" : fmt, ap);
451 msg_warn("proxy %s rejected \"%s\": \"%s\"", VSTREAM_PATH(state->proxy),
452 STR(buf), STR(state->proxy_buffer));
453 vstring_free(buf);
456 /* smtpd_proxy_cmd - send command to proxy, receive reply */
458 int smtpd_proxy_cmd(SMTPD_STATE *state, int expect, const char *fmt,...)
460 va_list ap;
461 char *cp;
462 int last_char;
463 int err = 0;
464 static VSTRING *buffer = 0;
465 const CLEANUP_STAT_DETAIL *detail;
468 * Errors first. Be prepared for delayed errors from the DATA phase.
470 if (vstream_ferror(state->proxy)
471 || vstream_feof(state->proxy)
472 || ((err = vstream_setjmp(state->proxy)) != 0
473 && smtpd_proxy_rdwr_error(state->proxy, err))) {
474 state->error_mask |= MAIL_ERROR_SOFTWARE;
475 state->err |= CLEANUP_STAT_PROXY;
476 detail = cleanup_stat_detail(CLEANUP_STAT_PROXY);
477 vstring_sprintf(state->proxy_buffer,
478 "%d %s Error: %s",
479 detail->smtp, detail->dsn, detail->text);
480 return (-1);
484 * The command can be omitted at the start of an SMTP session. This is
485 * not documented as part of the official interface because it is used
486 * only internally to this module.
488 if (fmt != SMTPD_PROXY_CONNECT) {
491 * Format the command.
493 va_start(ap, fmt);
494 vstring_vsprintf(state->proxy_buffer, fmt, ap);
495 va_end(ap);
498 * Optionally log the command first, so that we can see in the log
499 * what the program is trying to do.
501 if (msg_verbose)
502 msg_info("> %s: %s", VSTREAM_PATH(state->proxy),
503 STR(state->proxy_buffer));
506 * Send the command to the proxy server. Since we're going to read a
507 * reply immediately, there is no need to flush buffers.
509 smtp_fputs(STR(state->proxy_buffer), LEN(state->proxy_buffer),
510 state->proxy);
514 * Early return if we don't want to wait for a server reply (such as
515 * after sending QUIT.
517 if (expect == SMTPD_PROX_WANT_NONE)
518 return (0);
521 * Censor out non-printable characters in server responses and save
522 * complete multi-line responses if possible.
524 VSTRING_RESET(state->proxy_buffer);
525 if (buffer == 0)
526 buffer = vstring_alloc(10);
527 for (;;) {
528 last_char = smtp_get(buffer, state->proxy, var_line_limit);
529 printable(STR(buffer), '?');
530 if (last_char != '\n')
531 msg_warn("%s: response longer than %d: %.30s...",
532 VSTREAM_PATH(state->proxy), var_line_limit,
533 STR(buffer));
534 if (msg_verbose)
535 msg_info("< %s: %.100s", VSTREAM_PATH(state->proxy),
536 STR(buffer));
539 * Defend against a denial of service attack by limiting the amount
540 * of multi-line text that we are willing to store.
542 if (LEN(state->proxy_buffer) < var_line_limit) {
543 if (VSTRING_LEN(state->proxy_buffer))
544 VSTRING_ADDCH(state->proxy_buffer, '\n');
545 vstring_strcat(state->proxy_buffer, STR(buffer));
549 * Parse the response into code and text. Ignore unrecognized
550 * garbage. This means that any character except space (or end of
551 * line) will have the same effect as the '-' line continuation
552 * character.
554 for (cp = STR(buffer); *cp && ISDIGIT(*cp); cp++)
555 /* void */ ;
556 if (cp - STR(buffer) == 3) {
557 if (*cp == '-')
558 continue;
559 if (*cp == ' ' || *cp == 0)
560 break;
562 msg_warn("received garbage from proxy %s: %.100s",
563 VSTREAM_PATH(state->proxy), STR(buffer));
567 * Log a warning in case the proxy does not send the expected response.
568 * Silently accept any response when the client expressed no expectation.
570 * Don't pass through misleading 2xx replies. it confuses naive users and
571 * SMTP clients, and creates support problems.
573 if (expect != SMTPD_PROX_WANT_ANY && expect != *STR(state->proxy_buffer)) {
574 va_start(ap, fmt);
575 smtpd_proxy_cmd_error(state, fmt, ap);
576 va_end(ap);
577 if (*STR(state->proxy_buffer) == SMTPD_PROX_WANT_OK
578 || *STR(state->proxy_buffer) == SMTPD_PROX_WANT_MORE) {
579 state->error_mask |= MAIL_ERROR_SOFTWARE;
580 state->err |= CLEANUP_STAT_PROXY;
581 detail = cleanup_stat_detail(CLEANUP_STAT_PROXY);
582 vstring_sprintf(state->proxy_buffer,
583 "%d %s Error: %s",
584 detail->smtp, detail->dsn, detail->text);
586 return (-1);
587 } else {
588 return (0);
592 /* smtpd_proxy_rec_put - send message content, rec_put() clone */
594 int smtpd_proxy_rec_put(VSTREAM *stream, int rec_type,
595 const char *data, ssize_t len)
597 int err;
600 * Errors first.
602 if (vstream_ferror(stream) || vstream_feof(stream))
603 return (REC_TYPE_ERROR);
604 if ((err = vstream_setjmp(stream)) != 0)
605 return (smtpd_proxy_rdwr_error(stream, err), REC_TYPE_ERROR);
608 * Send one content record. Errors and results must be as with rec_put().
610 if (rec_type == REC_TYPE_NORM)
611 smtp_fputs(data, len, stream);
612 else if (rec_type == REC_TYPE_CONT)
613 smtp_fwrite(data, len, stream);
614 else
615 msg_panic("smtpd_proxy_rec_put: need REC_TYPE_NORM or REC_TYPE_CONT");
616 return (rec_type);
619 /* smtpd_proxy_rec_fprintf - send message content, rec_fprintf() clone */
621 int smtpd_proxy_rec_fprintf(VSTREAM *stream, int rec_type,
622 const char *fmt,...)
624 va_list ap;
625 int err;
628 * Errors first.
630 if (vstream_ferror(stream) || vstream_feof(stream))
631 return (REC_TYPE_ERROR);
632 if ((err = vstream_setjmp(stream)) != 0)
633 return (smtpd_proxy_rdwr_error(stream, err), REC_TYPE_ERROR);
636 * Send one content record. Errors and results must be as with
637 * rec_fprintf().
639 va_start(ap, fmt);
640 if (rec_type == REC_TYPE_NORM)
641 smtp_vprintf(stream, fmt, ap);
642 else
643 msg_panic("smtpd_proxy_rec_fprintf: need REC_TYPE_NORM");
644 va_end(ap);
645 return (rec_type);
648 /* smtpd_proxy_close - close proxy connection */
650 void smtpd_proxy_close(SMTPD_STATE *state)
652 (void) vstream_fclose(state->proxy);
653 state->proxy = 0;