/*++
/* NAME
/* haproxy_srvr 3
/* SUMMARY
/* server-side haproxy protocol support
/* SYNOPSIS
/* #include <haproxy_srvr.h>
/*
/* const char *haproxy_srvr_parse_sa(
/* const char *str,
/* ssize_t *str_len,
/* int *non_proxy,
/* MAI_HOSTADDR_STR *smtp_client_addr,
/* MAI_SERVPORT_STR *smtp_client_port,
/* MAI_HOSTADDR_STR *smtp_server_addr,
/* MAI_SERVPORT_STR *smtp_server_port,
/* struct sockaddr *client_sa,
/* SOCKADDR_SIZE *client_sa_len,
/* struct sockaddr *server_sa,
/* SOCKADDR_SIZE *server_sa_len)
/*
/* const char *haproxy_srvr_receive_sa(
/* int fd,
/* int *non_proxy,
/* MAI_HOSTADDR_STR *smtp_client_addr,
/* MAI_SERVPORT_STR *smtp_client_port,
/* MAI_HOSTADDR_STR *smtp_server_addr,
/* MAI_SERVPORT_STR *smtp_server_port,
/* struct sockaddr *client_sa,
/* SOCKADDR_SIZE *client_sa_len,
/* struct sockaddr *server_sa,
/* SOCKADDR_SIZE *server_sa_len)
/* ABI COMPATIBILITY
/* const char *haproxy_srvr_parse(str, str_len, non_proxy,
/* smtp_client_addr, smtp_client_port,
/* smtp_server_addr, smtp_server_port)
/* const char *str;
/* ssize_t *str_len;
/* int *non_proxy;
/* MAI_HOSTADDR_STR *smtp_client_addr,
/* MAI_SERVPORT_STR *smtp_client_port,
/* MAI_HOSTADDR_STR *smtp_server_addr,
/* MAI_SERVPORT_STR *smtp_server_port;
/*
/* const char *haproxy_srvr_receive(fd, non_proxy,
/* smtp_client_addr, smtp_client_port,
/* smtp_server_addr, smtp_server_port)
/* int fd;
/* int *non_proxy;
/* MAI_HOSTADDR_STR *smtp_client_addr,
/* MAI_SERVPORT_STR *smtp_client_port,
/* MAI_HOSTADDR_STR *smtp_server_addr,
/* MAI_SERVPORT_STR *smtp_server_port;
/* DESCRIPTION
/* haproxy_srvr_parse_sa() parses a haproxy v1 or v2 protocol
/* message. The result is null in case of success, a pointer
/* to text (with the error type) in case of error. If both
/* IPv6 and IPv4 support are enabled, IPV4_IN_IPV6 address
/* form (::ffff:1.2.3.4) is converted to IPV4 form. In case
/* of success, the str_len argument is updated with the number
/* of bytes parsed, and the non_proxy argument is true or false
/* if the haproxy message specifies a non-proxied connection.
/*
/* haproxy_srvr_receive_sa() receives and parses a haproxy protocol
/* handshake. This must be called before any I/O is done on
/* the specified file descriptor. The result is 0 in case of
/* success, -1 in case of error. All errors are logged.
/*
/* The haproxy v2 protocol support is limited to TCP over IPv4,
/* TCP over IPv6, and non-proxied connections. In the latter
/* case, the caller is responsible for any local or remote
/* address/port lookup.
/*
/* The client or server sockaddr and length storage are updated
/* when their pointers are non-null.
/*
/* haproxy_srvr_parse() and haproxy_srvr_receive() provide ABI
/* backwards compatibility, passing null pointers for the sockaddr
/* and length storage arguments.
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Wietse Venema
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
/*
/* Wietse Venema
/* porcupine.org
/*--*/
/* System library. */
#include <sys_defs.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
/* Utility library. */
#include <msg.h>
#include <myaddrinfo.h>
#include <valid_hostname.h>
#include <stringops.h>
#include <mymalloc.h>
#include <inet_proto.h>
#include <split_at.h>
#include <sock_addr.h>
#include <normalize_v4mapped_addr.h>
/* Global library. */
#define _HAPROXY_SRVR_INTERNAL_
#include <haproxy_srvr.h>
static const INET_PROTO_INFO *proto_info;
#define STR_OR_NULL(str) ((str) ? (str) : "(null)" )
/* haproxy_srvr_parse_lit - extract and validate string literal */
static int haproxy_srvr_parse_lit(const char *str,...)
{
va_list ap;
const char *cp;
int result = -1 ;
int count;
if (msg_verbose)
msg_info("haproxy_srvr_parse: %s" , STR_OR_NULL(str));
if (str != 0 ) {
va_start(ap, str);
for (count = 0 ; (cp = va_arg(ap, const char *)) != 0 ; count++) {
if (strcmp(str, cp) == 0 ) {
result = count;
break ;
}
}
va_end(ap);
}
return (result);
}
/* haproxy_srvr_parse_proto - parse and validate the protocol type */
static int haproxy_srvr_parse_proto(const char *str, int *addr_family)
{
if (msg_verbose)
msg_info("haproxy_srvr_parse: proto=%s" , STR_OR_NULL(str));
if (str == 0 )
return (-1 );
#ifdef HAS_IPV6
if (strcasecmp(str, "TCP6" ) == 0 ) {
if (strchr((char *) proto_info->sa_family_list, AF_INET6) != 0 ) {
*addr_family = AF_INET6;
return (0 );
}
} else
#endif
if (strcasecmp(str, "TCP4" ) == 0 ) {
if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0 ) {
*addr_family = AF_INET;
return (0 );
}
}
return (-1 );
}
/* haproxy_srvr_parse_addr - extract and validate IP address */
static int haproxy_srvr_parse_addr(const char *str, MAI_HOSTADDR_STR *addr,
int addr_family,
struct sockaddr *sa,
SOCKADDR_SIZE *sa_len)
{
struct addrinfo *res;
int err;
struct sockaddr_storage ss;
SOCKADDR_SIZE ss_len;
if (msg_verbose)
msg_info("haproxy_srvr_parse: addr=%s proto=%d" ,
STR_OR_NULL(str), addr_family);
if (str == 0 || strlen(str) >= sizeof (MAI_HOSTADDR_STR))
return (-1 );
switch (addr_family) {
#ifdef HAS_IPV6
case AF_INET6:
if (!valid_ipv6_hostaddr(str, DONT_GRIPE))
return (-1 );
break ;
#endif
case AF_INET:
if (!valid_ipv4_hostaddr(str, DONT_GRIPE))
return (-1 );
break ;
default :
msg_panic("haproxy_srvr_parse: unexpected address family: %d" ,
addr_family);
}
/*
* Convert the printable address to canonical form . Don ' t rely on the
* proxy . This requires a conversion to binary form and back , even if a
* caller such as postscreen does not need the binary form .
*/
if ((err = hostaddr_to_sockaddr(str, (char *) 0 , 0 , &res)) != 0 ) {
msg_warn("haproxy_srvr_parse: hostaddr_to_sockaddr(\" %s\") failed: %s" ,
str, MAI_STRERROR(err));
return (-1 );
}
if (sa == 0 ) {
sa = (struct sockaddr *) &ss;
ss_len = sizeof (ss);
sa_len = &ss_len;
} else {
if (sa_len == 0 )
msg_panic("haproxy_srvr_parse: sockaddr length not specified" );
}
if (*sa_len < res->ai_addrlen)
msg_panic("haproxy_srvr_parse: sockaddr size %d too small" ,
(int ) *sa_len);
*sa_len = res->ai_addrlen;
memcpy((void *) sa, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
#ifdef HAS_IPV6
if (sa->sa_family == AF_INET6)
normalize_v4mapped_sockaddr(sa, sa_len);
#endif
if ((err = sockaddr_to_hostaddr(sa, *sa_len,
addr, (MAI_SERVPORT_STR *) 0 , 0 )) != 0 ) {
msg_warn("haproxy_srvr_parse: sockaddr_to_hostaddr() failed: %s" ,
MAI_STRERROR(err));
return (-1 );
}
return (0 );
}
/* haproxy_srvr_parse_port - extract and validate TCP port */
static int haproxy_srvr_parse_port(const char *str, MAI_SERVPORT_STR *port,
struct sockaddr *sa)
{
if (msg_verbose)
msg_info("haproxy_srvr_parse: port=%s" , STR_OR_NULL(str));
if (str == 0 || strlen(str) >= sizeof (MAI_SERVPORT_STR)
|| !valid_hostport(str, DONT_GRIPE)) {
return (-1 );
} else {
memcpy(port->buf, str, strlen(str) + 1 );
if (sa != 0 ) {
switch (sa->sa_family) {
#ifdef HAS_IPV6
case AF_INET6:
SOCK_ADDR_IN6_PORT(sa) = htons(atoi(str));
break ;
#endif
case AF_INET:
SOCK_ADDR_IN_PORT(sa) = htons(atoi(str));
break ;
default :
msg_panic("haproxy_srvr_parse: unexpected address family: %d" ,
sa->sa_family);
}
}
return (0 );
}
}
/* haproxy_srvr_parse_v2_addr_v4 - parse IPv4 info from v2 header */
static int haproxy_srvr_parse_v2_addr_v4(uint32_t sin_addr,
unsigned sin_port,
MAI_HOSTADDR_STR *addr,
MAI_SERVPORT_STR *port,
struct sockaddr *sa,
SOCKADDR_SIZE *sa_len)
{
struct sockaddr_in sin;
SOCKADDR_SIZE sin_len;
/*
* Convert the binary address and port to printable form .
*/
if (sa == 0 ) {
sa = (struct sockaddr *) &sin;
sin_len = sizeof (sin);
sa_len = &sin_len;
} else {
if (sa_len == 0 )
msg_panic("haproxy_srvr_parse: sockaddr length not specified" );
if (*sa_len < sizeof (sin))
msg_panic("haproxy_srvr_parse: sockaddr size %d too small" ,
(int ) *sa_len);
*sa_len = sizeof (sin);
}
memset((void *) sa, 0 , *sa_len);
SOCK_ADDR_IN_FAMILY(sa) = AF_INET;
SOCK_ADDR_IN_ADDR(sa).s_addr = sin_addr;
SOCK_ADDR_IN_PORT(sa) = sin_port;
if (sockaddr_to_hostaddr(sa, *sa_len, addr, port, 0 ) < 0 )
return (-1 );
return (0 );
}
#ifdef HAS_IPV6
/* haproxy_srvr_parse_v2_addr_v6 - parse IPv6 info from v2 header */
static int haproxy_srvr_parse_v2_addr_v6(uint8_t *sin6_addr,
unsigned sin6_port,
MAI_HOSTADDR_STR *addr,
MAI_SERVPORT_STR *port,
struct sockaddr *sa,
SOCKADDR_SIZE *sa_len)
{
struct sockaddr_in6 sin6;
SOCKADDR_SIZE sin6_len;
/*
* Convert the binary address and port to printable form .
*/
if (sa == 0 ) {
sa = (struct sockaddr *) &sin6;
sin6_len = sizeof (sin6);
sa_len = &sin6_len;
} else {
if (sa_len == 0 )
msg_panic("haproxy_srvr_parse: sockaddr length not specified" );
if (*sa_len < sizeof (sin6))
msg_panic("haproxy_srvr_parse: sockaddr size %d too small" ,
(int ) *sa_len);
*sa_len = sizeof (sin6);
}
memset((void *) sa, 0 , *sa_len);
SOCK_ADDR_IN6_FAMILY(sa) = AF_INET6;
memcpy(&SOCK_ADDR_IN6_ADDR(sa), sin6_addr, sizeof (SOCK_ADDR_IN6_ADDR(sa)));
SOCK_ADDR_IN6_PORT(sa) = sin6_port;
normalize_v4mapped_sockaddr(sa, sa_len);
if (sockaddr_to_hostaddr(sa, *sa_len, addr, port, 0 ) < 0 )
return (-1 );
return (0 );
}
#endif
/* haproxy_srvr_parse_v2_hdr - parse v2 header address info */
static const char *haproxy_srvr_parse_v2_hdr(const char *str, ssize_t *str_len,
int *non_proxy,
MAI_HOSTADDR_STR *smtp_client_addr,
MAI_SERVPORT_STR *smtp_client_port,
MAI_HOSTADDR_STR *smtp_server_addr,
MAI_SERVPORT_STR *smtp_server_port,
struct sockaddr *client_sa,
SOCKADDR_SIZE *client_sa_len,
struct sockaddr *server_sa,
SOCKADDR_SIZE *server_sa_len)
{
const char myname[] = "haproxy_srvr_parse_v2_hdr" ;
struct proxy_hdr_v2 *hdr_v2;
if (*str_len < PP2_HEADER_LEN)
return ("short protocol header" );
hdr_v2 = (struct proxy_hdr_v2 *) str;
if (memcmp(hdr_v2->sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN) != 0 )
return ("unrecognized protocol header" );
if ((hdr_v2->ver_cmd & PP2_VERSION_MASK) != PP2_VERSION)
return ("unrecognized protocol version" );
if (*str_len < PP2_HEADER_LEN + ntohs(hdr_v2->len))
return ("short version 2 protocol header" );
switch (hdr_v2->ver_cmd & PP2_CMD_MASK) {
/*
* Proxied connection , use the proxy - provided connection info .
*/
case PP2_CMD_PROXY:
switch (hdr_v2->fam) {
case PP2_FAM_INET | PP2_TRANS_STREAM:{ /* TCP4 */
if (strchr((char *) proto_info->sa_family_list, AF_INET) == 0 )
return ("Postfix IPv4 support is disabled" );
if (ntohs(hdr_v2->len) < PP2_ADDR_LEN_INET)
return ("short address field" );
if (haproxy_srvr_parse_v2_addr_v4(hdr_v2->addr.ip4.src_addr,
hdr_v2->addr.ip4.src_port,
smtp_client_addr, smtp_client_port,
client_sa, client_sa_len) < 0 )
return ("client network address conversion error" );
if (msg_verbose)
msg_info("%s: smtp_client_addr=%s smtp_client_port=%s" ,
myname, smtp_client_addr->buf, smtp_client_port->buf);
if (haproxy_srvr_parse_v2_addr_v4(hdr_v2->addr.ip4.dst_addr,
hdr_v2->addr.ip4.dst_port,
smtp_server_addr,
smtp_server_port,
server_sa,
server_sa_len) < 0 )
return ("server network address conversion error" );
if (msg_verbose)
msg_info("%s: smtp_server_addr=%s smtp_server_port=%s" ,
myname, smtp_server_addr->buf, smtp_server_port->buf);
break ;
}
case PP2_FAM_INET6 | PP2_TRANS_STREAM:{/* TCP6 */
#ifdef HAS_IPV6
if (strchr((char *) proto_info->sa_family_list, AF_INET6) == 0 )
return ("Postfix IPv6 support is disabled" );
if (ntohs(hdr_v2->len) < PP2_ADDR_LEN_INET6)
return ("short address field" );
if (haproxy_srvr_parse_v2_addr_v6(hdr_v2->addr.ip6.src_addr,
hdr_v2->addr.ip6.src_port,
smtp_client_addr,
smtp_client_port,
client_sa,
client_sa_len) < 0 )
return ("client network address conversion error" );
if (msg_verbose)
msg_info("%s: smtp_client_addr=%s smtp_client_port=%s" ,
myname, smtp_client_addr->buf, smtp_client_port->buf);
if (haproxy_srvr_parse_v2_addr_v6(hdr_v2->addr.ip6.dst_addr,
hdr_v2->addr.ip6.dst_port,
smtp_server_addr,
smtp_server_port,
server_sa,
server_sa_len) < 0 )
return ("server network address conversion error" );
if (msg_verbose)
msg_info("%s: smtp_server_addr=%s smtp_server_port=%s" ,
myname, smtp_server_addr->buf, smtp_server_port->buf);
break ;
#else
return ("Postfix IPv6 support is not compiled in" );
#endif
}
default :
return ("unsupported network protocol" );
}
/* For now, skip and ignore TLVs. */
*str_len = PP2_HEADER_LEN + ntohs(hdr_v2->len);
return (0 );
/*
* Non - proxied connection , use the proxy - to - server connection info .
*/
case PP2_CMD_LOCAL:
/* For now, skip and ignore TLVs. */
*non_proxy = 1 ;
*str_len = PP2_HEADER_LEN + ntohs(hdr_v2->len);
return (0 );
default :
return ("bad command in proxy header" );
}
}
/* haproxy_srvr_parse_sa - parse haproxy line */
const char *haproxy_srvr_parse_sa(const char *str, ssize_t *str_len,
int *non_proxy,
MAI_HOSTADDR_STR *smtp_client_addr,
MAI_SERVPORT_STR *smtp_client_port,
MAI_HOSTADDR_STR *smtp_server_addr,
MAI_SERVPORT_STR *smtp_server_port,
struct sockaddr *client_sa,
SOCKADDR_SIZE *client_sa_len,
struct sockaddr *server_sa,
SOCKADDR_SIZE *server_sa_len)
{
const char *err;
if (proto_info == 0 )
proto_info = inet_proto_info();
*non_proxy = 0 ;
/*
* XXX We don ' t accept connections with the " UNKNOWN " protocol type ,
* because those would sidestep address - based access control mechanisms .
*/
/*
* Try version 1 protocol .
*/
if (strncmp(str, "PROXY " , 6 ) == 0 ) {
char *saved_str = mystrndup(str, *str_len);
char *cp = saved_str;
char *beyond_header = split_at(saved_str, '\n' );
int addr_family;
#define NEXT_TOKEN mystrtok(&cp, " \r" )
if (beyond_header == 0 )
err = "missing protocol header terminator" ;
else if (haproxy_srvr_parse_lit(NEXT_TOKEN, "PROXY" , (char *) 0 ) < 0 )
err = "bad or missing protocol header" ;
else if (haproxy_srvr_parse_proto(NEXT_TOKEN, &addr_family) < 0 )
err = "bad or missing protocol type" ;
else if (haproxy_srvr_parse_addr(NEXT_TOKEN, smtp_client_addr,
addr_family, client_sa,
client_sa_len) < 0 )
err = "bad or missing client address" ;
else if (haproxy_srvr_parse_addr(NEXT_TOKEN, smtp_server_addr,
addr_family, server_sa,
server_sa_len) < 0 )
err = "bad or missing server address" ;
else if (haproxy_srvr_parse_port(NEXT_TOKEN, smtp_client_port,
client_sa) < 0 )
err = "bad or missing client port" ;
else if (haproxy_srvr_parse_port(NEXT_TOKEN, smtp_server_port,
server_sa) < 0 )
err = "bad or missing server port" ;
else {
err = 0 ;
*str_len = beyond_header - saved_str;
}
myfree(saved_str);
return (err);
}
/*
* Try version 2 protocol .
*/
else {
return (haproxy_srvr_parse_v2_hdr(str, str_len, non_proxy,
smtp_client_addr, smtp_client_port,
smtp_server_addr, smtp_server_port,
client_sa, client_sa_len,
server_sa, server_sa_len));
}
}
/* haproxy_srvr_parse - ABI compatibility */
#undef haproxy_srvr_parse
const char *haproxy_srvr_parse(const char *str, ssize_t *str_len,
int *non_proxy,
MAI_HOSTADDR_STR *smtp_client_addr,
MAI_SERVPORT_STR *smtp_client_port,
MAI_HOSTADDR_STR *smtp_server_addr,
MAI_SERVPORT_STR *smtp_server_port);
const char *haproxy_srvr_parse(const char *str, ssize_t *str_len,
int *non_proxy,
MAI_HOSTADDR_STR *smtp_client_addr,
MAI_SERVPORT_STR *smtp_client_port,
MAI_HOSTADDR_STR *smtp_server_addr,
MAI_SERVPORT_STR *smtp_server_port)
{
return (haproxy_srvr_parse_sa(str, str_len, non_proxy,
smtp_client_addr, smtp_client_port,
smtp_server_addr, smtp_server_port,
(struct sockaddr *) 0 , (SOCKADDR_SIZE *) 0 ,
(struct sockaddr *) 0 , (SOCKADDR_SIZE *) 0 ));
}
/* haproxy_srvr_receive_sa - receive and parse haproxy protocol handshake */
int haproxy_srvr_receive_sa(int fd, int *non_proxy,
MAI_HOSTADDR_STR *smtp_client_addr,
MAI_SERVPORT_STR *smtp_client_port,
MAI_HOSTADDR_STR *smtp_server_addr,
MAI_SERVPORT_STR *smtp_server_port,
struct sockaddr *client_sa,
SOCKADDR_SIZE *client_sa_len,
struct sockaddr *server_sa,
SOCKADDR_SIZE *server_sa_len)
{
const char *err;
VSTRING *escape_buf;
char read_buf[HAPROXY_HEADER_MAX_LEN + 1 ];
ssize_t read_len;
/*
* We must not read ( 2 ) past the end of the HaProxy handshake . The v2
* protocol assumes that the handshake will never be fragmented ,
* therefore we peek , parse the entire input , then read ( 2 ) only the
* number of bytes parsed .
*/
if ((read_len = recv(fd, read_buf, sizeof (read_buf) - 1 , MSG_PEEK)) <= 0 ) {
msg_warn("haproxy read: EOF" );
return (-1 );
}
/*
* Parse the haproxy handshake , and determine the handshake length .
*/
read_buf[read_len] = 0 ;
if ((err = haproxy_srvr_parse_sa(read_buf, &read_len, non_proxy,
smtp_client_addr, smtp_client_port,
smtp_server_addr, smtp_server_port,
client_sa, client_sa_len,
server_sa, server_sa_len)) != 0 ) {
escape_buf = vstring_alloc(read_len * 2 );
escape(escape_buf, read_buf, read_len);
msg_warn("haproxy read: %s: %s" , err, vstring_str(escape_buf));
vstring_free(escape_buf);
return (-1 );
}
/*
* Try to pop the haproxy handshake off the input queue .
*/
if (recv(fd, read_buf, read_len, 0 ) != read_len) {
msg_warn("haproxy read: %m" );
return (-1 );
}
return (0 );
}
/* haproxy_srvr_receive - ABI compatibility */
#undef haproxy_srvr_receive
int haproxy_srvr_receive(int fd, int *non_proxy,
MAI_HOSTADDR_STR *smtp_client_addr,
MAI_SERVPORT_STR *smtp_client_port,
MAI_HOSTADDR_STR *smtp_server_addr,
MAI_SERVPORT_STR *smtp_server_port);
int haproxy_srvr_receive(int fd, int *non_proxy,
MAI_HOSTADDR_STR *smtp_client_addr,
MAI_SERVPORT_STR *smtp_client_port,
MAI_HOSTADDR_STR *smtp_server_addr,
MAI_SERVPORT_STR *smtp_server_port)
{
return (haproxy_srvr_receive_sa(fd, non_proxy,
smtp_client_addr, smtp_client_port,
smtp_server_addr, smtp_server_port,
(struct sockaddr *) 0 ,
(SOCKADDR_SIZE *) 0 ,
(struct sockaddr *) 0 ,
(SOCKADDR_SIZE *) 0 ));
}
Messung V0.5 in Prozent C=74 H=95 G=84
¤ Dauer der Verarbeitung: 0.3 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland