Home | History | Annotate | Line # | Download | only in global
      1 /*	$NetBSD: haproxy_srvr.c,v 1.5 2026/05/09 18:49:16 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	haproxy_srvr 3
      6 /* SUMMARY
      7 /*	server-side haproxy protocol support
      8 /* SYNOPSIS
      9 /*	#include <haproxy_srvr.h>
     10 /*
     11 /*	const char *haproxy_srvr_parse_sa(
     12 /*	const char *str,
     13 /*	ssize_t	*str_len,
     14 /*	int	*non_proxy,
     15 /*	MAI_HOSTADDR_STR *smtp_client_addr,
     16 /*	MAI_SERVPORT_STR *smtp_client_port,
     17 /*	MAI_HOSTADDR_STR *smtp_server_addr,
     18 /*	MAI_SERVPORT_STR *smtp_server_port,
     19 /*	struct sockaddr *client_sa,
     20 /*	SOCKADDR_SIZE *client_sa_len,
     21 /*	struct sockaddr *server_sa,
     22 /*	SOCKADDR_SIZE *server_sa_len)
     23 /*
     24 /*	const char *haproxy_srvr_receive_sa(
     25 /*	int	fd,
     26 /*	int	*non_proxy,
     27 /*	MAI_HOSTADDR_STR *smtp_client_addr,
     28 /*	MAI_SERVPORT_STR *smtp_client_port,
     29 /*	MAI_HOSTADDR_STR *smtp_server_addr,
     30 /*	MAI_SERVPORT_STR *smtp_server_port,
     31 /*	struct sockaddr *client_sa,
     32 /*	SOCKADDR_SIZE *client_sa_len,
     33 /*	struct sockaddr *server_sa,
     34 /*	SOCKADDR_SIZE *server_sa_len)
     35 /* ABI COMPATIBILITY
     36 /*	const char *haproxy_srvr_parse(str, str_len, non_proxy,
     37 /*			smtp_client_addr, smtp_client_port,
     38 /*			smtp_server_addr, smtp_server_port)
     39 /*	const char *str;
     40 /*	ssize_t	*str_len;
     41 /*	int	*non_proxy;
     42 /*	MAI_HOSTADDR_STR *smtp_client_addr,
     43 /*	MAI_SERVPORT_STR *smtp_client_port,
     44 /*	MAI_HOSTADDR_STR *smtp_server_addr,
     45 /*	MAI_SERVPORT_STR *smtp_server_port;
     46 /*
     47 /*	const char *haproxy_srvr_receive(fd, non_proxy,
     48 /*			smtp_client_addr, smtp_client_port,
     49 /*			smtp_server_addr, smtp_server_port)
     50 /*	int	fd;
     51 /*	int	*non_proxy;
     52 /*	MAI_HOSTADDR_STR *smtp_client_addr,
     53 /*	MAI_SERVPORT_STR *smtp_client_port,
     54 /*	MAI_HOSTADDR_STR *smtp_server_addr,
     55 /*	MAI_SERVPORT_STR *smtp_server_port;
     56 /* DESCRIPTION
     57 /*	haproxy_srvr_parse_sa() parses a haproxy v1 or v2 protocol
     58 /*	message. The result is null in case of success, a pointer
     59 /*	to text (with the error type) in case of error. If both
     60 /*	IPv6 and IPv4 support are enabled, IPV4_IN_IPV6 address
     61 /*	form (::ffff:1.2.3.4) is converted to IPV4 form. In case
     62 /*	of success, the str_len argument is updated with the number
     63 /*	of bytes parsed, and the non_proxy argument is true or false
     64 /*	if the haproxy message specifies a non-proxied connection.
     65 /*
     66 /*	haproxy_srvr_receive_sa() receives and parses a haproxy protocol
     67 /*	handshake. This must be called before any I/O is done on
     68 /*	the specified file descriptor. The result is 0 in case of
     69 /*	success, -1 in case of error. All errors are logged.
     70 /*
     71 /*	The haproxy v2 protocol support is limited to TCP over IPv4,
     72 /*	TCP over IPv6, and non-proxied connections. In the latter
     73 /*	case, the caller is responsible for any local or remote
     74 /*	address/port lookup.
     75 /*
     76 /*	The client or server sockaddr and length storage are updated
     77 /*	when their pointers are non-null.
     78 /*
     79 /*	haproxy_srvr_parse() and haproxy_srvr_receive() provide ABI
     80 /*	backwards compatibility, passing null pointers for the sockaddr
     81 /*	and length storage arguments.
     82 /* LICENSE
     83 /* .ad
     84 /* .fi
     85 /*	The Secure Mailer license must be distributed with this software.
     86 /* AUTHOR(S)
     87 /*	Wietse Venema
     88 /*	IBM T.J. Watson Research
     89 /*	P.O. Box 704
     90 /*	Yorktown Heights, NY 10598, USA
     91 /*
     92 /*	Wietse Venema
     93 /*	Google, Inc.
     94 /*	111 8th Avenue
     95 /*	New York, NY 10011, USA
     96 /*
     97 /*	Wietse Venema
     98 /*	porcupine.org
     99 /*--*/
    100 
    101 /* System library. */
    102 
    103 #include <sys_defs.h>
    104 #include <stdarg.h>
    105 #include <stdlib.h>
    106 #include <string.h>
    107 
    108 #ifdef STRCASECMP_IN_STRINGS_H
    109 #include <strings.h>
    110 #endif
    111 
    112 /* Utility library. */
    113 
    114 #include <msg.h>
    115 #include <myaddrinfo.h>
    116 #include <valid_hostname.h>
    117 #include <stringops.h>
    118 #include <mymalloc.h>
    119 #include <inet_proto.h>
    120 #include <split_at.h>
    121 #include <sock_addr.h>
    122 #include <normalize_v4mapped_addr.h>
    123 
    124 /* Global library. */
    125 
    126 #define _HAPROXY_SRVR_INTERNAL_
    127 #include <haproxy_srvr.h>
    128 
    129 static const INET_PROTO_INFO *proto_info;
    130 
    131 #define STR_OR_NULL(str) ((str) ? (str) : "(null)")
    132 
    133 /* haproxy_srvr_parse_lit - extract and validate string literal */
    134 
    135 static int haproxy_srvr_parse_lit(const char *str,...)
    136 {
    137     va_list ap;
    138     const char *cp;
    139     int     result = -1;
    140     int     count;
    141 
    142     if (msg_verbose)
    143 	msg_info("haproxy_srvr_parse: %s", STR_OR_NULL(str));
    144 
    145     if (str != 0) {
    146 	va_start(ap, str);
    147 	for (count = 0; (cp = va_arg(ap, const char *)) != 0; count++) {
    148 	    if (strcmp(str, cp) == 0) {
    149 		result = count;
    150 		break;
    151 	    }
    152 	}
    153 	va_end(ap);
    154     }
    155     return (result);
    156 }
    157 
    158 /* haproxy_srvr_parse_proto - parse and validate the protocol type */
    159 
    160 static int haproxy_srvr_parse_proto(const char *str, int *addr_family)
    161 {
    162     if (msg_verbose)
    163 	msg_info("haproxy_srvr_parse: proto=%s", STR_OR_NULL(str));
    164 
    165     if (str == 0)
    166 	return (-1);
    167 #ifdef HAS_IPV6
    168     if (strcasecmp(str, "TCP6") == 0) {
    169 	if (strchr((char *) proto_info->sa_family_list, AF_INET6) != 0) {
    170 	    *addr_family = AF_INET6;
    171 	    return (0);
    172 	}
    173     } else
    174 #endif
    175     if (strcasecmp(str, "TCP4") == 0) {
    176 	if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0) {
    177 	    *addr_family = AF_INET;
    178 	    return (0);
    179 	}
    180     }
    181     return (-1);
    182 }
    183 
    184 /* haproxy_srvr_parse_addr - extract and validate IP address */
    185 
    186 static int haproxy_srvr_parse_addr(const char *str, MAI_HOSTADDR_STR *addr,
    187 				           int addr_family,
    188 				           struct sockaddr *sa,
    189 				           SOCKADDR_SIZE *sa_len)
    190 {
    191     struct addrinfo *res;
    192     int     err;
    193     struct sockaddr_storage ss;
    194     SOCKADDR_SIZE ss_len;
    195 
    196     if (msg_verbose)
    197 	msg_info("haproxy_srvr_parse: addr=%s proto=%d",
    198 		 STR_OR_NULL(str), addr_family);
    199 
    200     if (str == 0 || strlen(str) >= sizeof(MAI_HOSTADDR_STR))
    201 	return (-1);
    202 
    203     switch (addr_family) {
    204 #ifdef HAS_IPV6
    205     case AF_INET6:
    206 	if (!valid_ipv6_hostaddr(str, DONT_GRIPE))
    207 	    return (-1);
    208 	break;
    209 #endif
    210     case AF_INET:
    211 	if (!valid_ipv4_hostaddr(str, DONT_GRIPE))
    212 	    return (-1);
    213 	break;
    214     default:
    215 	msg_panic("haproxy_srvr_parse: unexpected address family: %d",
    216 		  addr_family);
    217     }
    218 
    219     /*
    220      * Convert the printable address to canonical form. Don't rely on the
    221      * proxy. This requires a conversion to binary form and back, even if a
    222      * caller such as postscreen does not need the binary form.
    223      */
    224     if ((err = hostaddr_to_sockaddr(str, (char *) 0, 0, &res)) != 0) {
    225 	msg_warn("haproxy_srvr_parse: hostaddr_to_sockaddr(\"%s\") failed: %s",
    226 		 str, MAI_STRERROR(err));
    227 	return (-1);
    228     }
    229     if (sa == 0) {
    230 	sa = (struct sockaddr *) &ss;
    231 	ss_len = sizeof(ss);
    232 	sa_len = &ss_len;
    233     } else {
    234 	if (sa_len == 0)
    235 	    msg_panic("haproxy_srvr_parse: sockaddr length not specified");
    236     }
    237     if (*sa_len < res->ai_addrlen)
    238 	msg_panic("haproxy_srvr_parse: sockaddr size %d too small",
    239 		  (int) *sa_len);
    240     *sa_len = res->ai_addrlen;
    241     memcpy((void *) sa, res->ai_addr, res->ai_addrlen);
    242     freeaddrinfo(res);
    243 #ifdef HAS_IPV6
    244     if (sa->sa_family == AF_INET6)
    245 	normalize_v4mapped_sockaddr(sa, sa_len);
    246 #endif
    247     if ((err = sockaddr_to_hostaddr(sa, *sa_len,
    248 				    addr, (MAI_SERVPORT_STR *) 0, 0)) != 0) {
    249 	msg_warn("haproxy_srvr_parse: sockaddr_to_hostaddr() failed: %s",
    250 		 MAI_STRERROR(err));
    251 	return (-1);
    252     }
    253     return (0);
    254 }
    255 
    256 /* haproxy_srvr_parse_port - extract and validate TCP port */
    257 
    258 static int haproxy_srvr_parse_port(const char *str, MAI_SERVPORT_STR *port,
    259 				           struct sockaddr *sa)
    260 {
    261     if (msg_verbose)
    262 	msg_info("haproxy_srvr_parse: port=%s", STR_OR_NULL(str));
    263     if (str == 0 || strlen(str) >= sizeof(MAI_SERVPORT_STR)
    264 	|| !valid_hostport(str, DONT_GRIPE)) {
    265 	return (-1);
    266     } else {
    267 	memcpy(port->buf, str, strlen(str) + 1);
    268 	if (sa != 0) {
    269 	    switch (sa->sa_family) {
    270 #ifdef HAS_IPV6
    271 	    case AF_INET6:
    272 		SOCK_ADDR_IN6_PORT(sa) = htons(atoi(str));
    273 		break;
    274 #endif
    275 	    case AF_INET:
    276 		SOCK_ADDR_IN_PORT(sa) = htons(atoi(str));
    277 		break;
    278 	    default:
    279 		msg_panic("haproxy_srvr_parse: unexpected address family: %d",
    280 			  sa->sa_family);
    281 	    }
    282 	}
    283 	return (0);
    284     }
    285 }
    286 
    287 /* haproxy_srvr_parse_v2_addr_v4 - parse IPv4 info from v2 header */
    288 
    289 static int haproxy_srvr_parse_v2_addr_v4(uint32_t sin_addr,
    290 					         unsigned sin_port,
    291 					         MAI_HOSTADDR_STR *addr,
    292 					         MAI_SERVPORT_STR *port,
    293 					         struct sockaddr *sa,
    294 					         SOCKADDR_SIZE *sa_len)
    295 {
    296     struct sockaddr_in sin;
    297     SOCKADDR_SIZE sin_len;
    298 
    299     /*
    300      * Convert the binary address and port to printable form.
    301      */
    302     if (sa == 0) {
    303 	sa = (struct sockaddr *) &sin;
    304 	sin_len = sizeof(sin);
    305 	sa_len = &sin_len;
    306     } else {
    307 	if (sa_len == 0)
    308 	    msg_panic("haproxy_srvr_parse: sockaddr length not specified");
    309 	if (*sa_len < sizeof(sin))
    310 	    msg_panic("haproxy_srvr_parse: sockaddr size %d too small",
    311 		      (int) *sa_len);
    312 	*sa_len = sizeof(sin);
    313     }
    314     memset((void *) sa, 0, *sa_len);
    315     SOCK_ADDR_IN_FAMILY(sa) = AF_INET;
    316     SOCK_ADDR_IN_ADDR(sa).s_addr = sin_addr;
    317     SOCK_ADDR_IN_PORT(sa) = sin_port;
    318     if (sockaddr_to_hostaddr(sa, *sa_len, addr, port, 0) < 0)
    319 	return (-1);
    320     return (0);
    321 }
    322 
    323 #ifdef HAS_IPV6
    324 
    325 /* haproxy_srvr_parse_v2_addr_v6 - parse IPv6 info from v2 header */
    326 
    327 static int haproxy_srvr_parse_v2_addr_v6(uint8_t *sin6_addr,
    328 					         unsigned sin6_port,
    329 					         MAI_HOSTADDR_STR *addr,
    330 					         MAI_SERVPORT_STR *port,
    331 					         struct sockaddr *sa,
    332 					         SOCKADDR_SIZE *sa_len)
    333 {
    334     struct sockaddr_in6 sin6;
    335     SOCKADDR_SIZE sin6_len;
    336 
    337     /*
    338      * Convert the binary address and port to printable form.
    339      */
    340     if (sa == 0) {
    341 	sa = (struct sockaddr *) &sin6;
    342 	sin6_len = sizeof(sin6);
    343 	sa_len = &sin6_len;
    344     } else {
    345 	if (sa_len == 0)
    346 	    msg_panic("haproxy_srvr_parse: sockaddr length not specified");
    347 	if (*sa_len < sizeof(sin6))
    348 	    msg_panic("haproxy_srvr_parse: sockaddr size %d too small",
    349 		      (int) *sa_len);
    350 	*sa_len = sizeof(sin6);
    351     }
    352     memset((void *) sa, 0, *sa_len);
    353     SOCK_ADDR_IN6_FAMILY(sa) = AF_INET6;
    354     memcpy(&SOCK_ADDR_IN6_ADDR(sa), sin6_addr, sizeof(SOCK_ADDR_IN6_ADDR(sa)));
    355     SOCK_ADDR_IN6_PORT(sa) = sin6_port;
    356     normalize_v4mapped_sockaddr(sa, sa_len);
    357     if (sockaddr_to_hostaddr(sa, *sa_len, addr, port, 0) < 0)
    358 	return (-1);
    359     return (0);
    360 }
    361 
    362 #endif
    363 
    364 /* haproxy_srvr_parse_v2_hdr - parse v2 header address info */
    365 
    366 static const char *haproxy_srvr_parse_v2_hdr(const char *str, ssize_t *str_len,
    367 					             int *non_proxy,
    368 				         MAI_HOSTADDR_STR *smtp_client_addr,
    369 				         MAI_SERVPORT_STR *smtp_client_port,
    370 				         MAI_HOSTADDR_STR *smtp_server_addr,
    371 				         MAI_SERVPORT_STR *smtp_server_port,
    372 					         struct sockaddr *client_sa,
    373 				               SOCKADDR_SIZE *client_sa_len,
    374 					         struct sockaddr *server_sa,
    375 				               SOCKADDR_SIZE *server_sa_len)
    376 {
    377     const char myname[] = "haproxy_srvr_parse_v2_hdr";
    378     struct proxy_hdr_v2 *hdr_v2;
    379 
    380     if (*str_len < PP2_HEADER_LEN)
    381 	return ("short protocol header");
    382     hdr_v2 = (struct proxy_hdr_v2 *) str;
    383     if (memcmp(hdr_v2->sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN) != 0)
    384 	return ("unrecognized protocol header");
    385     if ((hdr_v2->ver_cmd & PP2_VERSION_MASK) != PP2_VERSION)
    386 	return ("unrecognized protocol version");
    387     if (*str_len < PP2_HEADER_LEN + ntohs(hdr_v2->len))
    388 	return ("short version 2 protocol header");
    389 
    390     switch (hdr_v2->ver_cmd & PP2_CMD_MASK) {
    391 
    392 	/*
    393 	 * Proxied connection, use the proxy-provided connection info.
    394 	 */
    395     case PP2_CMD_PROXY:
    396 	switch (hdr_v2->fam) {
    397 	case PP2_FAM_INET | PP2_TRANS_STREAM:{	/* TCP4 */
    398 		if (strchr((char *) proto_info->sa_family_list, AF_INET) == 0)
    399 		    return ("Postfix IPv4 support is disabled");
    400 		if (ntohs(hdr_v2->len) < PP2_ADDR_LEN_INET)
    401 		    return ("short address field");
    402 		if (haproxy_srvr_parse_v2_addr_v4(hdr_v2->addr.ip4.src_addr,
    403 						  hdr_v2->addr.ip4.src_port,
    404 					 smtp_client_addr, smtp_client_port,
    405 					      client_sa, client_sa_len) < 0)
    406 		    return ("client network address conversion error");
    407 		if (msg_verbose)
    408 		    msg_info("%s: smtp_client_addr=%s smtp_client_port=%s",
    409 		      myname, smtp_client_addr->buf, smtp_client_port->buf);
    410 		if (haproxy_srvr_parse_v2_addr_v4(hdr_v2->addr.ip4.dst_addr,
    411 						  hdr_v2->addr.ip4.dst_port,
    412 						  smtp_server_addr,
    413 						  smtp_server_port,
    414 						  server_sa,
    415 						  server_sa_len) < 0)
    416 		    return ("server network address conversion error");
    417 		if (msg_verbose)
    418 		    msg_info("%s: smtp_server_addr=%s smtp_server_port=%s",
    419 		      myname, smtp_server_addr->buf, smtp_server_port->buf);
    420 		break;
    421 	    }
    422 	case PP2_FAM_INET6 | PP2_TRANS_STREAM:{/* TCP6 */
    423 #ifdef HAS_IPV6
    424 		if (strchr((char *) proto_info->sa_family_list, AF_INET6) == 0)
    425 		    return ("Postfix IPv6 support is disabled");
    426 		if (ntohs(hdr_v2->len) < PP2_ADDR_LEN_INET6)
    427 		    return ("short address field");
    428 		if (haproxy_srvr_parse_v2_addr_v6(hdr_v2->addr.ip6.src_addr,
    429 						  hdr_v2->addr.ip6.src_port,
    430 						  smtp_client_addr,
    431 						  smtp_client_port,
    432 						  client_sa,
    433 						  client_sa_len) < 0)
    434 		    return ("client network address conversion error");
    435 		if (msg_verbose)
    436 		    msg_info("%s: smtp_client_addr=%s smtp_client_port=%s",
    437 		      myname, smtp_client_addr->buf, smtp_client_port->buf);
    438 		if (haproxy_srvr_parse_v2_addr_v6(hdr_v2->addr.ip6.dst_addr,
    439 						  hdr_v2->addr.ip6.dst_port,
    440 						  smtp_server_addr,
    441 						  smtp_server_port,
    442 						  server_sa,
    443 						  server_sa_len) < 0)
    444 		    return ("server network address conversion error");
    445 		if (msg_verbose)
    446 		    msg_info("%s: smtp_server_addr=%s smtp_server_port=%s",
    447 		      myname, smtp_server_addr->buf, smtp_server_port->buf);
    448 		break;
    449 #else
    450 		return ("Postfix IPv6 support is not compiled in");
    451 #endif
    452 	    }
    453 	default:
    454 	    return ("unsupported network protocol");
    455 	}
    456 	/* For now, skip and ignore TLVs. */
    457 	*str_len = PP2_HEADER_LEN + ntohs(hdr_v2->len);
    458 	return (0);
    459 
    460 	/*
    461 	 * Non-proxied connection, use the proxy-to-server connection info.
    462 	 */
    463     case PP2_CMD_LOCAL:
    464 	/* For now, skip and ignore TLVs. */
    465 	*non_proxy = 1;
    466 	*str_len = PP2_HEADER_LEN + ntohs(hdr_v2->len);
    467 	return (0);
    468     default:
    469 	return ("bad command in proxy header");
    470     }
    471 }
    472 
    473 /* haproxy_srvr_parse_sa - parse haproxy line */
    474 
    475 const char *haproxy_srvr_parse_sa(const char *str, ssize_t *str_len,
    476 				          int *non_proxy,
    477 				          MAI_HOSTADDR_STR *smtp_client_addr,
    478 				          MAI_SERVPORT_STR *smtp_client_port,
    479 				          MAI_HOSTADDR_STR *smtp_server_addr,
    480 				          MAI_SERVPORT_STR *smtp_server_port,
    481 				          struct sockaddr *client_sa,
    482 				          SOCKADDR_SIZE *client_sa_len,
    483 				          struct sockaddr *server_sa,
    484 				          SOCKADDR_SIZE *server_sa_len)
    485 {
    486     const char *err;
    487 
    488     if (proto_info == 0)
    489 	proto_info = inet_proto_info();
    490 
    491     *non_proxy = 0;
    492 
    493     /*
    494      * XXX We don't accept connections with the "UNKNOWN" protocol type,
    495      * because those would sidestep address-based access control mechanisms.
    496      */
    497 
    498     /*
    499      * Try version 1 protocol.
    500      */
    501     if (strncmp(str, "PROXY ", 6) == 0) {
    502 	char   *saved_str = mystrndup(str, *str_len);
    503 	char   *cp = saved_str;
    504 	char   *beyond_header = split_at(saved_str, '\n');
    505 	int     addr_family;
    506 
    507 #define NEXT_TOKEN mystrtok(&cp, " \r")
    508 	if (beyond_header == 0)
    509 	    err = "missing protocol header terminator";
    510 	else if (haproxy_srvr_parse_lit(NEXT_TOKEN, "PROXY", (char *) 0) < 0)
    511 	    err = "bad or missing protocol header";
    512 	else if (haproxy_srvr_parse_proto(NEXT_TOKEN, &addr_family) < 0)
    513 	    err = "bad or missing protocol type";
    514 	else if (haproxy_srvr_parse_addr(NEXT_TOKEN, smtp_client_addr,
    515 					 addr_family, client_sa,
    516 					 client_sa_len) < 0)
    517 	    err = "bad or missing client address";
    518 	else if (haproxy_srvr_parse_addr(NEXT_TOKEN, smtp_server_addr,
    519 					 addr_family, server_sa,
    520 					 server_sa_len) < 0)
    521 	    err = "bad or missing server address";
    522 	else if (haproxy_srvr_parse_port(NEXT_TOKEN, smtp_client_port,
    523 					 client_sa) < 0)
    524 	    err = "bad or missing client port";
    525 	else if (haproxy_srvr_parse_port(NEXT_TOKEN, smtp_server_port,
    526 					 server_sa) < 0)
    527 	    err = "bad or missing server port";
    528 	else {
    529 	    err = 0;
    530 	    *str_len = beyond_header - saved_str;
    531 	}
    532 	myfree(saved_str);
    533 
    534 	return (err);
    535     }
    536 
    537     /*
    538      * Try version 2 protocol.
    539      */
    540     else {
    541 	return (haproxy_srvr_parse_v2_hdr(str, str_len, non_proxy,
    542 					  smtp_client_addr, smtp_client_port,
    543 					  smtp_server_addr, smtp_server_port,
    544 					  client_sa, client_sa_len,
    545 					  server_sa, server_sa_len));
    546     }
    547 }
    548 
    549 /* haproxy_srvr_parse - ABI compatibility */
    550 
    551 #undef haproxy_srvr_parse
    552 
    553 const char *haproxy_srvr_parse(const char *str, ssize_t *str_len,
    554 			               int *non_proxy,
    555 			               MAI_HOSTADDR_STR *smtp_client_addr,
    556 			               MAI_SERVPORT_STR *smtp_client_port,
    557 			               MAI_HOSTADDR_STR *smtp_server_addr,
    558 			               MAI_SERVPORT_STR *smtp_server_port);
    559 
    560 const char *haproxy_srvr_parse(const char *str, ssize_t *str_len,
    561 			               int *non_proxy,
    562 			               MAI_HOSTADDR_STR *smtp_client_addr,
    563 			               MAI_SERVPORT_STR *smtp_client_port,
    564 			               MAI_HOSTADDR_STR *smtp_server_addr,
    565 			               MAI_SERVPORT_STR *smtp_server_port)
    566 {
    567     return (haproxy_srvr_parse_sa(str, str_len, non_proxy,
    568 				  smtp_client_addr, smtp_client_port,
    569 				  smtp_server_addr, smtp_server_port,
    570 				  (struct sockaddr *) 0, (SOCKADDR_SIZE *) 0,
    571 			       (struct sockaddr *) 0, (SOCKADDR_SIZE *) 0));
    572 }
    573 
    574 /* haproxy_srvr_receive_sa - receive and parse haproxy protocol handshake */
    575 
    576 int     haproxy_srvr_receive_sa(int fd, int *non_proxy,
    577 				        MAI_HOSTADDR_STR *smtp_client_addr,
    578 				        MAI_SERVPORT_STR *smtp_client_port,
    579 				        MAI_HOSTADDR_STR *smtp_server_addr,
    580 				        MAI_SERVPORT_STR *smtp_server_port,
    581 				        struct sockaddr *client_sa,
    582 				        SOCKADDR_SIZE *client_sa_len,
    583 				        struct sockaddr *server_sa,
    584 				        SOCKADDR_SIZE *server_sa_len)
    585 {
    586     const char *err;
    587     VSTRING *escape_buf;
    588     char    read_buf[HAPROXY_HEADER_MAX_LEN + 1];
    589     ssize_t read_len;
    590 
    591     /*
    592      * We must not read(2) past the end of the HaProxy handshake. The v2
    593      * protocol assumes that the handshake will never be fragmented,
    594      * therefore we peek, parse the entire input, then read(2) only the
    595      * number of bytes parsed.
    596      */
    597     if ((read_len = recv(fd, read_buf, sizeof(read_buf) - 1, MSG_PEEK)) <= 0) {
    598 	msg_warn("haproxy read: EOF");
    599 	return (-1);
    600     }
    601 
    602     /*
    603      * Parse the haproxy handshake, and determine the handshake length.
    604      */
    605     read_buf[read_len] = 0;
    606 
    607     if ((err = haproxy_srvr_parse_sa(read_buf, &read_len, non_proxy,
    608 				     smtp_client_addr, smtp_client_port,
    609 				     smtp_server_addr, smtp_server_port,
    610 				     client_sa, client_sa_len,
    611 				     server_sa, server_sa_len)) != 0) {
    612 	escape_buf = vstring_alloc(read_len * 2);
    613 	escape(escape_buf, read_buf, read_len);
    614 	msg_warn("haproxy read: %s: %s", err, vstring_str(escape_buf));
    615 	vstring_free(escape_buf);
    616 	return (-1);
    617     }
    618 
    619     /*
    620      * Try to pop the haproxy handshake off the input queue.
    621      */
    622     if (recv(fd, read_buf, read_len, 0) != read_len) {
    623 	msg_warn("haproxy read: %m");
    624 	return (-1);
    625     }
    626     return (0);
    627 }
    628 
    629 /* haproxy_srvr_receive - ABI compatibility */
    630 
    631 #undef haproxy_srvr_receive
    632 
    633 int     haproxy_srvr_receive(int fd, int *non_proxy,
    634 			             MAI_HOSTADDR_STR *smtp_client_addr,
    635 			             MAI_SERVPORT_STR *smtp_client_port,
    636 			             MAI_HOSTADDR_STR *smtp_server_addr,
    637 			             MAI_SERVPORT_STR *smtp_server_port);
    638 
    639 int     haproxy_srvr_receive(int fd, int *non_proxy,
    640 			             MAI_HOSTADDR_STR *smtp_client_addr,
    641 			             MAI_SERVPORT_STR *smtp_client_port,
    642 			             MAI_HOSTADDR_STR *smtp_server_addr,
    643 			             MAI_SERVPORT_STR *smtp_server_port)
    644 {
    645     return (haproxy_srvr_receive_sa(fd, non_proxy,
    646 				    smtp_client_addr, smtp_client_port,
    647 				    smtp_server_addr, smtp_server_port,
    648 				    (struct sockaddr *) 0,
    649 				    (SOCKADDR_SIZE *) 0,
    650 				    (struct sockaddr *) 0,
    651 				    (SOCKADDR_SIZE *) 0));
    652 }
    653