diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/src/EDITME | 3 | ||||
-rw-r--r-- | src/src/config.h.defaults | 1 | ||||
-rw-r--r-- | src/src/expand.c | 5 | ||||
-rw-r--r-- | src/src/globals.c | 12 | ||||
-rw-r--r-- | src/src/globals.h | 9 | ||||
-rw-r--r-- | src/src/macros.h | 10 | ||||
-rw-r--r-- | src/src/readconf.c | 3 | ||||
-rw-r--r-- | src/src/receive.c | 7 | ||||
-rw-r--r-- | src/src/smtp_in.c | 334 |
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) |