summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/src/EDITME3
-rw-r--r--src/src/config.h.defaults1
-rw-r--r--src/src/expand.c5
-rw-r--r--src/src/globals.c12
-rw-r--r--src/src/globals.h9
-rw-r--r--src/src/macros.h10
-rw-r--r--src/src/readconf.c3
-rw-r--r--src/src/receive.c7
-rw-r--r--src/src/smtp_in.c334
9 files changed, 383 insertions, 1 deletions
diff --git a/src/src/EDITME b/src/src/EDITME
index 3f818f355..7377af844 100644
--- a/src/src/EDITME
+++ b/src/src/EDITME
@@ -480,6 +480,9 @@ EXIM_MONITOR=eximon.bin
# CFLAGS += -I/usr/local/include
# LDFLAGS += -lhiredis
+# Uncomment the following line to enable Experimental Proxy Protocol
+# EXPERIMENTAL_PROXY=yes
+
###############################################################################
# THESE ARE THINGS YOU MIGHT WANT TO SPECIFY #
diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
index 8c1e799da..f68537a2a 100644
--- a/src/src/config.h.defaults
+++ b/src/src/config.h.defaults
@@ -169,6 +169,7 @@ it's a default value. */
#define EXPERIMENTAL_DMARC
#define EXPERIMENTAL_OCSP
#define EXPERIMENTAL_PRDR
+#define EXPERIMENTAL_PROXY
#define EXPERIMENTAL_REDIS
#define EXPERIMENTAL_SPF
#define EXPERIMENTAL_SRS
diff --git a/src/src/expand.c b/src/src/expand.c
index de9f7b5ad..bf1f82c6f 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -556,6 +556,11 @@ static var_entry var_table[] = {
{ "parent_local_part", vtype_stringptr, &deliver_localpart_parent },
{ "pid", vtype_pid, NULL },
{ "primary_hostname", vtype_stringptr, &primary_hostname },
+#ifdef EXPERIMENTAL_PROXY
+ { "proxy_host", vtype_stringptr, &proxy_host },
+ { "proxy_port", vtype_int, &proxy_port },
+ { "proxy_session", vtype_bool, &proxy_session },
+#endif
{ "prvscheck_address", vtype_stringptr, &prvscheck_address },
{ "prvscheck_keynum", vtype_stringptr, &prvscheck_keynum },
{ "prvscheck_result", vtype_stringptr, &prvscheck_result },
diff --git a/src/src/globals.c b/src/src/globals.c
index 133a7bf74..ec6700df5 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -797,6 +797,9 @@ bit_table log_options[] = {
{ US"lost_incoming_connection", L_lost_incoming_connection },
{ US"outgoing_port", LX_outgoing_port },
{ US"pid", LX_pid },
+#ifdef EXPERIMENTAL_PROXY
+ { US"proxy", LX_proxy },
+#endif
{ US"queue_run", L_queue_run },
{ US"queue_time", LX_queue_time },
{ US"queue_time_overall", LX_queue_time_overall },
@@ -914,6 +917,15 @@ uschar process_info[PROCESS_INFO_SIZE];
int process_info_len = 0;
uschar *process_log_path = NULL;
BOOL prod_requires_admin = TRUE;
+
+#ifdef EXPERIMENTAL_PROXY
+uschar *proxy_host = US"";
+int proxy_port = 0;
+uschar *proxy_required_hosts = US"";
+BOOL proxy_session = FALSE;
+BOOL proxy_session_failed = FALSE;
+#endif
+
uschar *prvscheck_address = NULL;
uschar *prvscheck_keynum = NULL;
uschar *prvscheck_result = NULL;
diff --git a/src/src/globals.h b/src/src/globals.h
index 265f94e60..5661489a7 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -592,6 +592,15 @@ extern uschar process_info[]; /* For SIGUSR1 output */
extern int process_info_len;
extern uschar *process_log_path; /* Alternate path */
extern BOOL prod_requires_admin; /* TRUE if prodding requires admin */
+
+#ifdef EXPERIMENTAL_PROXY
+extern uschar *proxy_host; /* IP of proxy server */
+extern int proxy_port; /* Port of proxy server */
+extern uschar *proxy_required_hosts; /* Hostlist which (require) use proxy protocol */
+extern BOOL proxy_session; /* TRUE if receiving mail from valid proxy */
+extern BOOL proxy_session_failed; /* TRUE if required proxy negotiation failed */
+#endif
+
extern uschar *prvscheck_address; /* Set during prvscheck expansion item */
extern uschar *prvscheck_keynum; /* Set during prvscheck expansion item */
extern uschar *prvscheck_result; /* Set during prvscheck expansion item */
diff --git a/src/src/macros.h b/src/src/macros.h
index a73bb0ba6..9f59f7acd 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -177,6 +177,14 @@ record. */
#define WAIT_NAME_MAX 50
+/* Wait this long before determining that a Proxy Protocol configured
+host isn't speaking the protocol, and so is disallowed. Can be moved to
+runtime configuration if per site settings become needed. */
+#ifdef EXPERIMENTAL_PROXY
+#define PROXY_NEGOTIATION_TIMEOUT_SEC 3
+#define PROXY_NEGOTIATION_TIMEOUT_USEC 0
+#endif
+
/* Fixed option values for all PCRE functions */
#define PCRE_COPT 0 /* compile */
@@ -414,6 +422,7 @@ set all the bits in a multi-word selector. */
#define LX_unknown_in_list 0x81000000
#define LX_8bitmime 0x82000000
#define LX_smtp_mailauth 0x84000000
+#define LX_proxy 0x88000000
#define L_default (L_connection_reject | \
L_delay_delivery | \
@@ -481,6 +490,7 @@ to conflict with system errno values. */
#define ERRNO_RCPT4XX (-44) /* RCPT gave 4xx error */
#define ERRNO_MAIL4XX (-45) /* MAIL gave 4xx error */
#define ERRNO_DATA4XX (-46) /* DATA gave 4xx error */
+#define ERRNO_PROXYFAIL (-47) /* Negotiation failed for proxy configured host */
/* These must be last, so all retry deferments can easily be identified */
diff --git a/src/src/readconf.c b/src/src/readconf.c
index 77c798412..c5200de67 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -332,6 +332,9 @@ static optionlist optionlist_config[] = {
{ "print_topbitchars", opt_bool, &print_topbitchars },
{ "process_log_path", opt_stringptr, &process_log_path },
{ "prod_requires_admin", opt_bool, &prod_requires_admin },
+#ifdef EXPERIMENTAL_PROXY
+ { "proxy_required_hosts", opt_stringptr, &proxy_required_hosts },
+#endif
{ "qualify_domain", opt_stringptr, &qualify_domain_sender },
{ "qualify_recipient", opt_stringptr, &qualify_domain_recipient },
{ "queue_domains", opt_stringptr, &queue_domains },
diff --git a/src/src/receive.c b/src/src/receive.c
index 072fee9f1..9205436d3 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -3755,6 +3755,13 @@ if (sender_host_authenticated != NULL)
if (prdr_requested)
s = string_append(s, &size, &sptr, 1, US" PRDR");
#endif
+#ifdef EXPERIMENTAL_PROXY
+if (proxy_session &&
+ (log_extra_selector & LX_proxy) != 0)
+ {
+ s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_host);
+ }
+#endif
sprintf(CS big_buffer, "%d", msg_size);
s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 4740aa5ff..144ad28a2 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -94,6 +94,10 @@ enum {
QUIT_CMD, HELP_CMD,
+#ifdef EXPERIMENTAL_PROXY
+ PROXY_FAIL_IGNORE_CMD,
+#endif
+
/* These are specials that don't correspond to actual commands */
EOF_CMD, OTHER_CMD, BADARG_CMD, BADCHAR_CMD, BADSYN_CMD,
@@ -549,6 +553,294 @@ exim_exit(EXIT_FAILURE);
+#ifdef EXPERIMENTAL_PROXY
+/*************************************************
+* Check if host is required proxy host *
+*************************************************/
+/* The function determines if inbound host will be a regular smtp host
+or if it is configured that it must use Proxy Protocol.
+
+Arguments: none
+Returns: bool
+*/
+
+static BOOL
+check_proxy_protocol_host()
+{
+int rc;
+/* Cannot configure local connection as a proxy inbound */
+if (sender_host_address == NULL) return proxy_session;
+
+rc = verify_check_this_host(&proxy_required_hosts, NULL, NULL,
+ sender_host_address, NULL);
+if (rc == OK)
+ {
+ DEBUG(D_receive)
+ debug_printf("Detected proxy protocol configured host\n");
+ proxy_session = TRUE;
+ }
+return proxy_session;
+}
+
+
+/*************************************************
+* Flush waiting input string *
+*************************************************/
+static void
+flush_input()
+{
+int rc;
+
+rc = smtp_getc();
+while (rc != '\n') /* End of input string */
+ {
+ rc = smtp_getc();
+ }
+}
+
+
+/*************************************************
+* Setup host for proxy protocol *
+*************************************************/
+/* The function configures the connection based on a header from the
+inbound host to use Proxy Protocol. The specification is very exact
+so exit with an error if do not find the exact required pieces. This
+includes an incorrect number of spaces separating args.
+
+Arguments: none
+Returns: int
+*/
+
+static BOOL
+setup_proxy_protocol_host()
+{
+union {
+ struct {
+ uschar line[108];
+ } v1;
+ struct {
+ uschar sig[12];
+ uschar ver;
+ uschar cmd;
+ uschar fam;
+ uschar len;
+ union {
+ struct { /* TCP/UDP over IPv4, len = 12 */
+ uint32_t src_addr;
+ uint32_t dst_addr;
+ uint16_t src_port;
+ uint16_t dst_port;
+ } ip4;
+ struct { /* TCP/UDP over IPv6, len = 36 */
+ uint8_t src_addr[16];
+ uint8_t dst_addr[16];
+ uint16_t src_port;
+ uint16_t dst_port;
+ } ip6;
+ struct { /* AF_UNIX sockets, len = 216 */
+ uschar src_addr[108];
+ uschar dst_addr[108];
+ } unx;
+ } addr;
+ } v2;
+} hdr;
+
+int size, ret, fd;
+uschar *tmpip;
+const char v2sig[13] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A\x02";
+uschar *iptype; /* To display debug info */
+struct timeval tv;
+
+fd = fileno(smtp_in);
+
+/* Proxy Protocol host must send header within a short time
+(default 3 seconds) or it's considered invalid */
+tv.tv_sec = PROXY_NEGOTIATION_TIMEOUT_SEC;
+tv.tv_usec = PROXY_NEGOTIATION_TIMEOUT_USEC;
+setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,
+ sizeof(struct timeval));
+do
+ {
+ ret = recv(fd, &hdr, sizeof(hdr), MSG_PEEK);
+ }
+ while (ret == -1 && errno == EINTR);
+
+if (ret == -1)
+ return (errno == EAGAIN) ? 0 : ERRNO_PROXYFAIL;
+
+if (ret >= 16 &&
+ memcmp(&hdr.v2, v2sig, 13) == 0)
+ {
+ DEBUG(D_receive) debug_printf("Detected PROXYv2 header\n");
+ size = 16 + hdr.v2.len;
+ if (ret < size)
+ {
+ DEBUG(D_receive) debug_printf("Truncated or too large PROXYv2 header\n");
+ goto proxyfail;
+ }
+ switch (hdr.v2.cmd)
+ {
+ case 0x01: /* PROXY command */
+ switch (hdr.v2.fam)
+ {
+ case 0x11: /* TCPv4 */
+ tmpip = string_sprintf("%s", hdr.v2.addr.ip4.src_addr);
+ if (!string_is_ip_address(tmpip,NULL))
+ return ERRNO_PROXYFAIL;
+ sender_host_address = tmpip;
+ sender_host_port = hdr.v2.addr.ip4.src_port;
+ goto done;
+ case 0x21: /* TCPv6 */
+ tmpip = string_sprintf("%s", hdr.v2.addr.ip6.src_addr);
+ if (!string_is_ip_address(tmpip,NULL))
+ return ERRNO_PROXYFAIL;
+ sender_host_address = tmpip;
+ sender_host_port = hdr.v2.addr.ip6.src_port;
+ goto done;
+ }
+ /* Unsupported protocol, keep local connection address */
+ break;
+ case 0x00: /* LOCAL command */
+ /* Keep local connection address for LOCAL */
+ break;
+ default:
+ DEBUG(D_receive) debug_printf("Unsupported PROXYv2 command\n");
+ goto proxyfail;
+ }
+ }
+else if (ret >= 8 &&
+ memcmp(hdr.v1.line, "PROXY", 5) == 0)
+ {
+ uschar *p = string_copy(hdr.v1.line);
+ uschar *end = memchr(p, '\r', ret - 1);
+ uschar *sp; /* Utility variables follow */
+ int tmp_port;
+ char *endc;
+
+ if (!end || end[1] != '\n')
+ {
+ DEBUG(D_receive) debug_printf("Partial or invalid PROXY header\n");
+ goto proxyfail;
+ }
+ *end = '\0'; /* Terminate the string */
+ size = end + 2 - hdr.v1.line; /* Skip header + CRLF */
+ DEBUG(D_receive) debug_printf("Detected PROXYv1 header\n");
+ /* Step through the string looking for the required fields. Ensure
+ strict adherance to required formatting, exit for any error. */
+ p += 5;
+ if (!isspace(*(p++)))
+ {
+ DEBUG(D_receive) debug_printf("Missing space after PROXY command\n");
+ goto proxyfail;
+ }
+ if (!Ustrncmp(p, CCS"TCP4", 4))
+ iptype = US"IPv4";
+ else if (!Ustrncmp(p,CCS"TCP6", 4))
+ iptype = US"IPv6";
+ else if (!Ustrncmp(p,CCS"UNKNOWN", 7))
+ {
+ iptype = US"Unknown";
+ goto done;
+ }
+ else
+ {
+ DEBUG(D_receive) debug_printf("Invalid TCP type\n");
+ goto proxyfail;
+ }
+
+ p += Ustrlen(iptype);
+ if (!isspace(*(p++)))
+ {
+ DEBUG(D_receive) debug_printf("Missing space after TCP4/6 command\n");
+ goto proxyfail;
+ }
+ /* Find the end of the arg */
+ if ((sp = Ustrchr(p, ' ')) == NULL)
+ {
+ DEBUG(D_receive)
+ debug_printf("Did not find proxied src %s\n", iptype);
+ goto proxyfail;
+ }
+ *sp = '\0';
+ if(!string_is_ip_address(p,NULL))
+ {
+ DEBUG(D_receive)
+ debug_printf("Proxied src arg is not an %s address\n", iptype);
+ goto proxyfail;
+ }
+ proxy_host = sender_host_address;
+ sender_host_address = p;
+ p = sp + 1;
+ if ((sp = Ustrchr(p, ' ')) == NULL)
+ {
+ DEBUG(D_receive)
+ debug_printf("Did not find proxy dest %s\n", iptype);
+ goto proxyfail;
+ }
+ *sp = '\0';
+ if(!string_is_ip_address(p,NULL))
+ {
+ DEBUG(D_receive)
+ debug_printf("Proxy dest arg is not an %s address\n", iptype);
+ goto proxyfail;
+ }
+ /* Should save dest ip somewhere? */
+ p = sp + 1;
+ if ((sp = Ustrchr(p, ' ')) == NULL)
+ {
+ DEBUG(D_receive) debug_printf("Did not find proxied src port\n");
+ goto proxyfail;
+ }
+ *sp = '\0';
+ tmp_port = strtol(CCS p,&endc,10);
+ if (*endc || tmp_port == 0)
+ {
+ DEBUG(D_receive)
+ debug_printf("Proxied src port '%s' not an integer\n", p);
+ goto proxyfail;
+ }
+ proxy_port = sender_host_port;
+ sender_host_port = tmp_port;
+ p = sp + 1;
+ if ((sp = Ustrchr(p, '\0')) == NULL)
+ {
+ DEBUG(D_receive) debug_printf("Did not find proxy dest port\n");
+ goto proxyfail;
+ }
+ tmp_port = strtol(CCS p,&endc,10);
+ if (*endc || tmp_port == 0)
+ {
+ DEBUG(D_receive)
+ debug_printf("Proxy dest port '%s' not an integer\n", p);
+ goto proxyfail;
+ }
+ /* Should save dest port somewhere? */
+ /* Already checked for /r /n above. Good V1 header received. */
+ goto done;
+ }
+else
+ {
+ /* Wrong protocol */
+ DEBUG(D_receive) debug_printf("Wrong proxy protocol specified\n");
+ goto proxyfail;
+ }
+
+proxyfail:
+/* Don't flush any potential buffer contents. Any input should cause a
+synchronization failure or we just don't want to speak SMTP to them */
+return FALSE;
+
+done:
+flush_input();
+DEBUG(D_receive)
+ debug_printf("Valid %s sender from Proxy Protocol header\n",
+ iptype);
+return proxy_session;
+}
+#endif
+
+
+
/*************************************************
* Read one command line *
*************************************************/
@@ -622,6 +914,14 @@ if required. */
for (p = cmd_list; p < cmd_list_end; p++)
{
+ #ifdef EXPERIMENTAL_PROXY
+ /* Only allow QUIT command if Proxy Protocol parsing failed */
+ if (proxy_session && proxy_session_failed)
+ {
+ if (p->cmd != QUIT_CMD)
+ continue;
+ }
+ #endif
if (strncmpic(smtp_cmd_buffer, US p->name, p->len) == 0 &&
(smtp_cmd_buffer[p->len-1] == ':' || /* "mail from:" or "rcpt to:" */
smtp_cmd_buffer[p->len] == 0 ||
@@ -669,6 +969,12 @@ for (p = cmd_list; p < cmd_list_end; p++)
}
}
+#ifdef EXPERIMENTAL_PROXY
+/* Only allow QUIT command if Proxy Protocol parsing failed */
+if (proxy_session && proxy_session_failed)
+ return PROXY_FAIL_IGNORE_CMD;
+#endif
+
/* Enforce synchronization for unknown commands */
if (smtp_inptr < smtp_inend && /* Outstanding input */
@@ -1832,6 +2138,28 @@ if (!sender_host_unknown)
if (smtp_batched_input) return TRUE;
+#ifdef EXPERIMENTAL_PROXY
+/* If valid Proxy Protocol source is connecting, set up session.
+ * Failure will not allow any SMTP function other than QUIT. */
+proxy_session = FALSE;
+proxy_session_failed = FALSE;
+if (check_proxy_protocol_host())
+ {
+ if (setup_proxy_protocol_host() == FALSE)
+ {
+ proxy_session_failed = TRUE;
+ DEBUG(D_receive)
+ debug_printf("Failure to extract proxied host, only QUIT allowed\n");
+ }
+ else
+ {
+ sender_host_name = NULL;
+ (void)host_name_lookup();
+ host_build_sender_fullhost();
+ }
+ }
+#endif
+
/* Run the ACL if it exists */
user_msg = NULL;
@@ -2594,7 +2922,6 @@ smtp_respond(code, len, TRUE, user_msg);
-
/*************************************************
* Initialize for SMTP incoming message *
*************************************************/
@@ -4379,6 +4706,11 @@ while (done <= 0)
done = 1; /* Pretend eof - drops connection */
break;
+ #ifdef EXPERIMENTAL_PROXY
+ case PROXY_FAIL_IGNORE_CMD:
+ smtp_printf("503 Command refused, required Proxy negotiation failed\r\n");
+ break;
+ #endif
default:
if (unknown_command_count++ >= smtp_max_unknown_commands)