text: /* $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[LDAP_IPADDRLEN];
  	struct berval peerbv = BER_BVC(peername);
  	/* Maximum size of header minus static component size is max option size */
  	uint8_t proxyp_options[536 - 16];
  	int pph_len;
  	int ret;
  
  	peername[0] = '\0';
  
  	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): "
  				"header 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): "
  				"header 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 header signature\n", (long)sfd );
  		return 0;
  	}
  
  	if ( ( pph.ver_cmd & 0xF0 ) != 0x20 ) {
  		Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
  				"invalid header version %x\n",
  				(long)sfd, pph.ver_cmd & 0xF0 );
  		return 0;
  	}
  
  	pph_len = ntohs( pph.len );
  	if ( ( pph.ver_cmd & 0x0F ) == 0x01 ) { /* PROXY command */
  		int addr_len;
  		switch ( pph.fam ) {
  		case 0x11: /* TCPv4 */
  			addr_len = sizeof( ppa.ip4 );
  			break;
  		case 0x21: /* TCPv6 */
  			addr_len = sizeof( ppa.ip6 );
  			break;
  		default:
  			Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
  					"unsupported protocol %x\n",
  					(long)sfd, pph.fam );
  			return 0;
  		}
  
  		if ( pph_len < addr_len ) {
  			Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
  					"address length %d too small, expecting %d\n",
  					(long)sfd, pph_len, addr_len );
  			return 0;
  		}
  
  		do {
  			ret = tcp_read( SLAP_FD2SOCK (sfd), &ppa, addr_len );
  		} while ( ret == -1 && errno == EINTR );
  
  		if ( ret == -1 ) {
  			char ebuf[128];
  			int save_errno = errno;
  			Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
  					"address read failed %d (%s)\n",
  					(long)sfd, save_errno,
  					AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
  			return 0;
  		} else if ( ret != addr_len ) {
  			Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
  					"address read insufficient data, expecting %d, read %d\n",
  					(long)sfd, addr_len, ret );
  			return 0;
  		}
  
  		pph_len -= addr_len;
  	}
  
  	switch ( pph.ver_cmd & 0x0F ) {
  	case 0x01: /* PROXY command */
  		switch ( pph.fam ) {
  		case 0x11: /* TCPv4 */
  			ldap_pvt_sockaddrstr( from, &peerbv );
  			Debug( LDAP_DEBUG_STATS, "proxyp(%ld): via %s\n",
  					(long)sfd, peername );
  
  			from->sa_in_addr.sin_family = AF_INET;
  			from->sa_in_addr.sin_addr.s_addr = ppa.ip4.src_addr;
  			from->sa_in_addr.sin_port = ppa.ip4.src_port;
  			break;
  
  		case 0x21: /* TCPv6 */
  #ifdef LDAP_PF_INET6
  			ldap_pvt_sockaddrstr( from, &peerbv );
  			Debug( LDAP_DEBUG_STATS, "proxyp(%ld): via %s\n",
  					(long)sfd, peername );
  			from->sa_in6_addr.sin6_family = AF_INET6;
  			memcpy( &from->sa_in6_addr.sin6_addr, ppa.ip6.src_addr,
  					sizeof(ppa.ip6.src_addr) );
  			from->sa_in6_addr.sin6_port = ppa.ip6.src_port;
  #else
  			Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
  					"IPv6 proxied addresses disabled\n",
  					(long)sfd );
  			return 0;
  #endif
  			break;
  		}
  
  		break;
  
  	case 0x00: /* LOCAL command */
  		Debug( LDAP_DEBUG_CONNS, "proxyp(%ld): "
  				"local connection, ignoring proxy data\n",
  				(long)sfd );
  		break;
  
  	default:
  		Debug( LDAP_DEBUG_ANY, "proxyp(%ld): invalid command %x\n",
  				(long)sfd, pph.ver_cmd & 0x0F );
  		return 0;
  	}
  
  	/* Clear out any options left in proxy packet */
  	if ( pph_len > 0 ) {
  		if (pph_len > sizeof( proxyp_options ) ) {
  			Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
  					"options size %d too big\n",
  					(long)sfd, pph_len );
  			return 0;
  		}
  
  		do {
  			ret = tcp_read( SLAP_FD2SOCK (sfd), &proxyp_options, pph_len );
  		} while ( ret == -1 && errno == EINTR );
  
  		if ( ret == -1 ) {
  			char ebuf[128];
  			int save_errno = errno;
  			Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
  					"options read failed %d (%s)\n",
  					(long)sfd, save_errno,
  					AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
  			return 0;
  		} else if ( ret != pph_len ) {
  			Debug( LDAP_DEBUG_ANY, "proxyp(%ld): "
  					"options read insufficient data, expecting %d, read %d\n",
  					(long)sfd, pph_len, ret );
  			return 0;
  		}
  	}
  
  	return 1;
  }
  