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