I've attached my first pass at adding proxy protocol support to slapd. I
haven't updated any documentation/man pages yet, I'll start taking a
look at that while you all eviscerate my code and let me know what needs
to be fixed before merging :).

I'd like to backport this to OPENLDAP_REL_ENG_2_4 if/when it's accepted,
hopefully that will be ok.

Thanks...
>From b35e7b6ec736f4ad0ed6c1774482b34aed4eac33 Mon Sep 17 00:00:00 2001
From: "Paul B. Henson" <hen...@acm.org>
Date: Fri, 4 Dec 2020 16:59:48 -0800
Subject: [PATCH] Add support for HAProxy proxy protocol v2

---
 include/ldap_pvt.h           |   3 +
 libraries/libldap/ldap-int.h |   4 +
 libraries/libldap/url.c      |  42 +++++++--
 servers/lloadd/daemon.c      |   2 +
 servers/lloadd/lload.h       |   1 +
 servers/slapd/Makefile.in    |   4 +-
 servers/slapd/daemon.c       |  11 +++
 servers/slapd/proxyp.c       | 159 +++++++++++++++++++++++++++++++++++
 servers/slapd/proxyp.h       |  21 +++++
 servers/slapd/slap.h         |   1 +
 10 files changed, 239 insertions(+), 9 deletions(-)
 create mode 100644 servers/slapd/proxyp.c
 create mode 100644 servers/slapd/proxyp.h

diff --git a/include/ldap_pvt.h b/include/ldap_pvt.h
index 01220d00a..54f9a13d4 100644
--- a/include/ldap_pvt.h
+++ b/include/ldap_pvt.h
@@ -32,6 +32,9 @@ ldap_pvt_url_scheme2proto LDAP_P((
 LDAP_F ( int )
 ldap_pvt_url_scheme2tls LDAP_P((
 	const char * ));
+LDAP_F ( int )
+ldap_pvt_url_scheme2proxied LDAP_P((
+	const char * ));
 
 LDAP_F ( int )
 ldap_pvt_url_scheme_port LDAP_P((
diff --git a/libraries/libldap/ldap-int.h b/libraries/libldap/ldap-int.h
index db019f4b7..c8adbc1c6 100644
--- a/libraries/libldap/ldap-int.h
+++ b/libraries/libldap/ldap-int.h
@@ -123,8 +123,12 @@ LDAP_BEGIN_DECL
 
 #define LDAP_URL_PREFIX         "ldap://";
 #define LDAP_URL_PREFIX_LEN     STRLENOF(LDAP_URL_PREFIX)
+#define PLDAP_URL_PREFIX        "pldap://";
+#define PLDAP_URL_PREFIX_LEN    STRLENOF(PLDAP_URL_PREFIX)
 #define LDAPS_URL_PREFIX	"ldaps://"
 #define LDAPS_URL_PREFIX_LEN	STRLENOF(LDAPS_URL_PREFIX)
+#define PLDAPS_URL_PREFIX	"pldaps://"
+#define PLDAPS_URL_PREFIX_LEN	STRLENOF(PLDAPS_URL_PREFIX)
 #define LDAPI_URL_PREFIX	"ldapi://"
 #define LDAPI_URL_PREFIX_LEN	STRLENOF(LDAPI_URL_PREFIX)
 #ifdef LDAP_CONNECTIONLESS
diff --git a/libraries/libldap/url.c b/libraries/libldap/url.c
index a84935e78..858613c13 100644
--- a/libraries/libldap/url.c
+++ b/libraries/libldap/url.c
@@ -20,7 +20,7 @@
 
 /*
  *  LDAP URLs look like this:
- *    ldap[is]://host[:port][/[dn[?[attributes][?[scope][?[filter][?exts]]]]]]
+ *    [p]ldap[is]://host[:port][/[dn[?[attributes][?[scope][?[filter][?exts]]]]]]
  *
  *  where:
  *   attributes is a comma separated list
@@ -59,7 +59,7 @@ int ldap_pvt_url_scheme2proto( const char *scheme )
 		return -1;
 	}
 
-	if( strcmp("ldap", scheme) == 0 ) {
+	if( strcmp("ldap", scheme) == 0 || strcmp("pldap", scheme) == 0 ) {
 		return LDAP_PROTO_TCP;
 	}
 
@@ -67,7 +67,7 @@ int ldap_pvt_url_scheme2proto( const char *scheme )
 		return LDAP_PROTO_IPC;
 	}
 
-	if( strcmp("ldaps", scheme) == 0 ) {
+	if( strcmp("ldaps", scheme) == 0 || strcmp("pldaps", scheme) == 0 ) {
 		return LDAP_PROTO_TCP;
 	}
 #ifdef LDAP_CONNECTIONLESS
@@ -86,7 +86,7 @@ int ldap_pvt_url_scheme_port( const char *scheme, int port )
 	if( port ) return port;
 	if( scheme == NULL ) return port;
 
-	if( strcmp("ldap", scheme) == 0 ) {
+	if( strcmp("ldap", scheme) == 0 || strcmp("ldap", scheme) == 0 ) {
 		return LDAP_PORT;
 	}
 
@@ -94,7 +94,7 @@ int ldap_pvt_url_scheme_port( const char *scheme, int port )
 		return -1;
 	}
 
-	if( strcmp("ldaps", scheme) == 0 ) {
+	if( strcmp("ldaps", scheme) == 0 || strcmp("pldaps", scheme) == 0 ) {
 		return LDAPS_PORT;
 	}
 
@@ -116,7 +116,19 @@ ldap_pvt_url_scheme2tls( const char *scheme )
 		return -1;
 	}
 
-	return strcmp("ldaps", scheme) == 0;
+	return strcmp("ldaps", scheme) == 0 || strcmp("pldaps", scheme) == 0;
+}
+
+int
+ldap_pvt_url_scheme2proxied( const char *scheme )
+{
+	assert( scheme != NULL );
+
+	if( scheme == NULL ) {
+		return -1;
+	}
+
+	return strcmp("pldap", scheme) == 0 || strcmp("pldaps", scheme) == 0;
 }
 
 int
@@ -150,7 +162,7 @@ ldap_is_ldaps_url( LDAP_CONST char *url )
 		return 0;
 	}
 
-	return strcmp(scheme, "ldaps") == 0;
+	return strcmp(scheme, "ldaps") == 0 || strcmp(scheme, "pldaps");
 }
 
 int
@@ -228,6 +240,14 @@ skip_url_prefix(
 		return( p );
 	}
 
+	/* check for "pldap://"; prefix */
+	if ( strncasecmp( p, PLDAP_URL_PREFIX, PLDAP_URL_PREFIX_LEN ) == 0 ) {
+		/* skip over "pldap://"; prefix and return success */
+		p += PLDAP_URL_PREFIX_LEN;
+		*scheme = "pldap";
+		return( p );
+	}
+
 	/* check for "ldaps://" prefix */
 	if ( strncasecmp( p, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN ) == 0 ) {
 		/* skip over "ldaps://" prefix and return success */
@@ -236,6 +256,14 @@ skip_url_prefix(
 		return( p );
 	}
 
+	/* check for "pldaps://" prefix */
+	if ( strncasecmp( p, PLDAPS_URL_PREFIX, PLDAPS_URL_PREFIX_LEN ) == 0 ) {
+		/* skip over "pldaps://" prefix and return success */
+		p += PLDAPS_URL_PREFIX_LEN;
+		*scheme = "pldaps";
+		return( p );
+	}
+
 	/* check for "ldapi://" prefix */
 	if ( strncasecmp( p, LDAPI_URL_PREFIX, LDAPI_URL_PREFIX_LEN ) == 0 ) {
 		/* skip over "ldapi://" prefix and return success */
diff --git a/servers/lloadd/daemon.c b/servers/lloadd/daemon.c
index f03e1ab48..8c9e3c79d 100644
--- a/servers/lloadd/daemon.c
+++ b/servers/lloadd/daemon.c
@@ -404,6 +404,8 @@ lload_open_listener(
     l.sl_is_tls = ldap_pvt_url_scheme2tls( lud->lud_scheme );
 #endif /* HAVE_TLS */
 
+l.sl_is_proxied = ldap_pvt_url_scheme2proxied( lud->lud_scheme );
+
 #ifdef LDAP_TCP_BUFFER
     l.sl_tcp_rmem = 0;
     l.sl_tcp_wmem = 0;
diff --git a/servers/lloadd/lload.h b/servers/lloadd/lload.h
index c5a694055..3c2553833 100644
--- a/servers/lloadd/lload.h
+++ b/servers/lloadd/lload.h
@@ -469,6 +469,7 @@ struct LloadListener {
 #ifdef HAVE_TLS
     int sl_is_tls;
 #endif
+    int sl_is_proxied;
     struct event_base *base;
     struct evconnlistener *listener;
     int sl_mute; /* Listener is temporarily disabled due to emfile */
diff --git a/servers/slapd/Makefile.in b/servers/slapd/Makefile.in
index 59248350f..4d40229ce 100644
--- a/servers/slapd/Makefile.in
+++ b/servers/slapd/Makefile.in
@@ -29,7 +29,7 @@ SRCS	= main.c globals.c bconfig.c config.c daemon.c \
 		dn.c compare.c modify.c delete.c modrdn.c ch_malloc.c \
 		value.c ava.c bind.c unbind.c abandon.c filterentry.c \
 		phonetic.c acl.c str2filter.c aclparse.c init.c user.c \
-		lock.c controls.c extended.c passwd.c \
+		lock.c controls.c extended.c passwd.c proxyp.c \
 		schema.c schema_check.c schema_init.c schema_prep.c \
 		schemaparse.c ad.c at.c mr.c syntax.c oc.c saslauthz.c \
 		oidm.c starttls.c index.c sets.c referral.c root_dse.c \
@@ -47,7 +47,7 @@ OBJS	= main.o globals.o bconfig.o config.o daemon.o \
 		dn.o compare.o modify.o delete.o modrdn.o ch_malloc.o \
 		value.o ava.o bind.o unbind.o abandon.o filterentry.o \
 		phonetic.o acl.o str2filter.o aclparse.o init.o user.o \
-		lock.o controls.o extended.o passwd.o \
+		lock.o controls.o extended.o passwd.o proxyp.o \
 		schema.o schema_check.o schema_init.o schema_prep.o \
 		schemaparse.o ad.o at.o mr.o syntax.o oc.o saslauthz.o \
 		oidm.o starttls.o index.o sets.o referral.o root_dse.o \
diff --git a/servers/slapd/daemon.c b/servers/slapd/daemon.c
index 03f3a6f38..d4f878d7b 100644
--- a/servers/slapd/daemon.c
+++ b/servers/slapd/daemon.c
@@ -41,6 +41,8 @@
 
 #include "ldap_rq.h"
 
+#include "proxyp.h"
+
 #ifdef HAVE_POLL
 #include <poll.h>
 #endif
@@ -1549,6 +1551,8 @@ slap_open_listener(
 	}
 #endif /* HAVE_TLS */
 
+	l.sl_is_proxied = ldap_pvt_url_scheme2proxied( lud->lud_scheme );
+
 #ifdef LDAP_TCP_BUFFER
 	l.sl_tcp_rmem = 0;
 	l.sl_tcp_wmem = 0;
@@ -2342,6 +2346,13 @@ slap_listener(
 	case AF_INET6:
 #  endif /* LDAP_PF_INET6 */
 	case AF_INET:
+		if (sl->sl_is_proxied) {
+			if (!proxyp(sfd, &from)) {
+				Debug( LDAP_DEBUG_ANY, "slapd(%ld): proxyp failed\n", (long)sfd);
+				slapd_close(sfd);
+				return 0;
+			}
+		}
 		slap_sockaddrstr( &from, &peerbv );
 		break;
 
diff --git a/servers/slapd/proxyp.c b/servers/slapd/proxyp.c
new file mode 100644
index 000000000..7889df021
--- /dev/null
+++ b/servers/slapd/proxyp.c
@@ -0,0 +1,159 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2020 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+#include "slap.h"
+
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif
+
+#include <lber_types.h>
+#include <ac/string.h>
+#include <ac/errno.h>
+
+typedef struct {
+	uint8_t  sig[12];	/* hex 0d 0a 0d 0a 00 0d 0a 51 55 49 54 0a */
+	uint8_t  ver_cmd;	/* protocol version and command */
+	uint8_t  fam;		/* protocol family and address */
+	uint16_t len;		/* length of address data */
+} proxyp_header;
+
+typedef union {
+	struct {	/* for TCP/UDP over IPv4, len = 12 */
+		uint32_t src_addr;
+		uint32_t dst_addr;
+		uint16_t src_port;
+		uint16_t dst_port;
+	} ip4;
+	struct {	/* for 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 {	/* for AF_UNIX sockets, len = 216 */
+		uint8_t src_addr[108];
+		uint8_t dst_addr[108];
+	} unx;
+} proxyp_addr;
+
+static const uint8_t proxyp_sig[12] = { 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d,
+					0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a };
+
+int proxyp(ber_socket_t sfd, Sockaddr *from) {
+	proxyp_header pph;
+	proxyp_addr ppa;
+	char peername[SLAP_ADDRLEN];
+	struct berval peerbv = BER_BVC(peername);
+	int ret;
+
+	do {
+		ret = tcp_read(SLAP_FD2SOCK(sfd), &pph, sizeof(pph));
+	} while (ret == -1 && errno == EINTR);
+
+	if (ret == -1) {
+		char ebuf[128];
+		int save_errno = errno;
+		Debug(LDAP_DEBUG_ANY, "proxyp(%ld): read failed %d (%s)\n", (long)sfd,
+			save_errno, AC_STRERROR_R(save_errno, ebuf,
+			sizeof(ebuf)));
+		return 0;
+	} else if (ret != sizeof(pph)) {
+		Debug(LDAP_DEBUG_ANY, "proxyp(%ld): read insufficient data %d\n",
+			(long)sfd, ret);
+		return 0;
+	}
+
+	if (memcmp(pph.sig, proxyp_sig, 12) != 0) {
+		Debug(LDAP_DEBUG_ANY, "proxyp(%ld): invalid signature\n", (long)sfd);
+		return 0;
+	}
+
+	if ((pph.ver_cmd & 0xF0) != 0x20) {
+		Debug(LDAP_DEBUG_ANY, "proxyp(%ld): invalid version %x\n", (long)sfd,
+			pph.ver_cmd & 0xF0);
+		return 0;
+	}
+
+	if (ntohs(pph.len) > sizeof(ppa)) {
+		Debug(LDAP_DEBUG_ANY, "proxyp(%ld): invalid address size %d\n",
+			(long)sfd, ntohs(pph.len));
+		return 0;
+	}
+
+	do {
+		ret = tcp_read(SLAP_FD2SOCK(sfd), &ppa, ntohs(pph.len));
+	} while (ret == -1 && errno == EINTR);
+
+	if (ret == -1) {
+		char ebuf[128];
+		int save_errno = errno;
+		Debug(LDAP_DEBUG_ANY, "proxyp(%ld): read address failed %d (%s)\n",
+			(long)sfd, save_errno, AC_STRERROR_R(save_errno, ebuf,
+			sizeof(ebuf)));
+		return 0;
+	} else if (ret != ntohs(pph.len)) {
+		Debug(LDAP_DEBUG_ANY, "proxyp(%ld): insufficient address data,"
+			" expecting %d read %d\n", (long)sfd, ntohs(pph.len), ret);
+		return 0;
+	}
+
+	switch (pph.ver_cmd & 0xF) {
+	case 0x01: /* PROXY command */
+		slap_sockaddrstr(from, &peerbv);
+
+		switch (pph.fam) {
+		case 0x11: /* TCPv4 */
+			Debug(LDAP_DEBUG_STATS, "proxyp(%ld): via %s\n", (long)sfd,
+				peerbv.bv_val);
+			((struct sockaddr_in *)from)->sin_family = AF_INET;
+			((struct sockaddr_in *)from)->sin_addr.s_addr = ppa.ip4.src_addr;
+			((struct sockaddr_in *)from)->sin_port = ppa.ip4.src_port;
+			break;
+
+		case 0x21: /* TCPv6 */
+			slap_sockaddrstr(from, &peerbv);
+			Debug(LDAP_DEBUG_STATS, "proxyp(%ld): via %s\n", (long)sfd,
+				peerbv.bv_val);
+			((struct sockaddr_in6 *)from)->sin6_family = AF_INET6;
+			memcpy(&((struct sockaddr_in6 *)from)->sin6_addr, ppa.ip6.src_addr,
+				16);
+			((struct sockaddr_in6 *)from)->sin6_port = ppa.ip6.src_port;
+			break;
+
+		default:
+			Debug(LDAP_DEBUG_CONNS, "proxyp(%ld): %s unsupported protocol %x"
+				" ignoring proxy data\n", (long)sfd, peerbv.bv_val, pph.fam);
+		}
+
+		break;
+
+	case 0x00: /* LOCAL command */
+		Debug(LDAP_DEBUG_CONNS, "proxyp(%ld): local connection, ignoring proxy"
+			" data\n", (long)sfd, peerbv.bv_val);
+		break;
+
+	default:
+		Debug(LDAP_DEBUG_ANY, "proxyp(%ld): invalid command %x\n", (long)sfd,
+			pph.ver_cmd & 0xF);
+		return 0;
+	}
+
+	return 1;
+}
diff --git a/servers/slapd/proxyp.h b/servers/slapd/proxyp.h
new file mode 100644
index 000000000..847baf7f3
--- /dev/null
+++ b/servers/slapd/proxyp.h
@@ -0,0 +1,21 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2020 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef PROXYP_H_
+#define PROXYP_H_
+
+int proxyp(ber_socket_t sfd, Sockaddr *from);
+
+#endif /* PROXYP_H_ */
diff --git a/servers/slapd/slap.h b/servers/slapd/slap.h
index 6d12f407c..f2451e0ed 100644
--- a/servers/slapd/slap.h
+++ b/servers/slapd/slap.h
@@ -3019,6 +3019,7 @@ struct Listener {
 #ifdef LDAP_CONNECTIONLESS
 	int	sl_is_udp;		/* UDP listener is also data port */
 #endif
+	int	sl_is_proxied;
 	int	sl_mute;	/* Listener is temporarily disabled due to emfile */
 	int	sl_busy;	/* Listener is busy (accept thread activated) */
 	ber_socket_t sl_sd;
-- 
2.26.2

Reply via email to