Commit 647b5f84 authored by Howard Chu's avatar Howard Chu
Browse files

Resurrection/rewrite of CLDAP (RFC1798 Connectionless LDAP).

Compile with -DLDAP_CONNECTIONLESS to use this code.
For slapd, use "-h cldap://" to listen on UDP.
For ldapsearch, use "-H cldap://" to query on UDP.
Client-side support is very minimal:
  no automatic timeout/retries
  no basedn wildcard expansion on results
  no support for specifying multiple servers at once.
parent a466dc75
......@@ -502,6 +502,9 @@ LBER_F( Sockbuf_IO ) ber_sockbuf_io_tcp;
LBER_F( Sockbuf_IO ) ber_sockbuf_io_readahead;
LBER_F( Sockbuf_IO ) ber_sockbuf_io_fd;
LBER_F( Sockbuf_IO ) ber_sockbuf_io_debug;
#ifdef LDAP_CONNECTIONLESS
LBER_F( Sockbuf_IO ) ber_sockbuf_io_udp;
#endif
/*
* LBER memory.c
......
......@@ -871,3 +871,106 @@ Sockbuf_IO ber_sockbuf_io_debug = {
sb_debug_write, /* sbi_write */
NULL /* sbi_close */
};
#ifdef LDAP_CONNECTIONLESS
/*
* Support for UDP (CLDAP)
*
* All I/O at this level must be atomic. For ease of use, the sb_readahead
* must be used above this module. All data reads and writes are prefixed
* with a sockaddr containing the address of the remote entity. Upper levels
* must read and write this sockaddr before doing the usual ber_printf/scanf
* operations on LDAP messages.
*/
static int
sb_dgram_setup( Sockbuf_IO_Desc *sbiod, void *arg )
{
assert( sbiod != NULL);
assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );
if ( arg != NULL )
sbiod->sbiod_sb->sb_fd = *((int *)arg);
return 0;
}
static ber_slen_t
sb_dgram_read( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len )
{
ber_slen_t rc;
socklen_t addrlen;
struct sockaddr *src;
assert( sbiod != NULL );
assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );
assert( buf != NULL );
addrlen = sizeof( struct sockaddr );
src = buf;
buf += addrlen;
rc = recvfrom( sbiod->sbiod_sb->sb_fd, buf, len, 0, src,
&addrlen );
return rc > 0 ? rc+sizeof(struct sockaddr) : rc;
}
static ber_slen_t
sb_dgram_write( Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len )
{
ber_slen_t rc;
struct sockaddr *dst;
assert( sbiod != NULL );
assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );
assert( buf != NULL );
dst = buf;
buf += sizeof( struct sockaddr );
len -= sizeof( struct sockaddr );
rc = sendto( sbiod->sbiod_sb->sb_fd, buf, len, 0, dst,
sizeof( struct sockaddr ) );
if ( rc < 0 )
return -1;
/* fake error if write was not atomic */
if (rc < len) {
# ifdef EMSGSIZE
errno = EMSGSIZE;
# endif
return -1;
}
rc = len + sizeof(struct sockaddr);
return rc;
}
static int
sb_dgram_close( Sockbuf_IO_Desc *sbiod )
{
assert( sbiod != NULL );
assert( SOCKBUF_VALID( sbiod->sbiod_sb ) );
tcp_close( sbiod->sbiod_sb->sb_fd );
return 0;
}
static int
sb_dgram_ctrl( Sockbuf_IO_Desc *sbiod, int opt, void *arg )
{
/* This is an end IO descriptor */
return 0;
}
Sockbuf_IO ber_sockbuf_io_udp =
{
sb_dgram_setup, /* sbi_setup */
NULL, /* sbi_remove */
sb_dgram_ctrl, /* sbi_ctrl */
sb_dgram_read, /* sbi_read */
sb_dgram_write, /* sbi_write */
sb_dgram_close /* sbi_close */
};
#endif /* LDAP_CONNECTIONLESS */
......@@ -147,10 +147,25 @@ do_abandon(
ld->ld_errno = LDAP_NO_MEMORY;
} else {
/* create a message to send */
err = ber_printf( ber, "{iti", /* '}' */
#ifdef LDAP_CONNECTIONLESS
if ( LDAP_IS_UDP(ld) ) {
err = ber_write( ber, ld->ld_options.ldo_peer,
sizeof(struct sockaddr), 0);
if (err == sizeof(struct sockaddr)) {
char *dn = ld->ld_options.ldo_cldapdn;
if (!dn) dn = "";
err = ber_printf( ber, "{isti", /* '}' */
++ld->ld_msgid, dn,
LDAP_REQ_ABANDON, msgid );
}
} else
#endif
{
/* create a message to send */
err = ber_printf( ber, "{iti", /* '}' */
++ld->ld_msgid,
LDAP_REQ_ABANDON, msgid );
LDAP_REQ_ABANDON, msgid );
}
if( err == -1 ) {
/* encoding error */
......
......@@ -396,6 +396,11 @@ void ldap_int_initialize_global_options( struct ldapoptions *gopts, int *dbglvl
LDAP_BOOL_SET(gopts, LDAP_BOOL_REFERRALS);
#ifdef LDAP_CONNECTIONLESS
gopts->ldo_peer = NULL;
gopts->ldo_cldapdn = NULL;
#endif
#ifdef HAVE_CYRUS_SASL
gopts->ldo_def_sasl_mech = NULL;
gopts->ldo_def_sasl_realm = NULL;
......
......@@ -68,6 +68,10 @@ LDAP_BEGIN_DECL
#define LDAPS_URL_PREFIX_LEN (sizeof(LDAPS_URL_PREFIX)-1)
#define LDAPI_URL_PREFIX "ldapi://"
#define LDAPI_URL_PREFIX_LEN (sizeof(LDAPI_URL_PREFIX)-1)
#ifdef LDAP_CONNECTIONLESS
#define LDAPC_URL_PREFIX "cldap://"
#define LDAPC_URL_PREFIX_LEN (sizeof(LDAPC_URL_PREFIX)-1)
#endif
#define LDAP_URL_URLCOLON "URL:"
#define LDAP_URL_URLCOLON_LEN (sizeof(LDAP_URL_URLCOLON)-1)
......@@ -114,6 +118,12 @@ struct ldapoptions {
#define LDAP_UNINITIALIZED 0x0
#define LDAP_INITIALIZED 0x1
#define LDAP_VALID_SESSION 0x2
#ifdef LDAP_CONNECTIONLESS
#define LDAP_UDP_SESSION 0x4
#define LDAP_IS_UDP(ld) (ld->ld_options.ldo_valid & LDAP_UDP_SESSION)
void* ldo_peer; /* struct sockaddr* */
char* ldo_cldapdn;
#endif
int ldo_debug;
/* per API call timeout */
......@@ -291,7 +301,7 @@ struct ldap {
LDAPConn *ld_conns; /* list of server connections */
void *ld_selectinfo; /* platform specifics for select */
};
#define LDAP_VALID(ld) ( (ld)->ld_valid == LDAP_VALID_SESSION )
#define LDAP_VALID(ld) ( (ld)->ld_valid & LDAP_VALID_SESSION )
#ifdef LDAP_R_COMPILE
#include <ldap_pvt_thread.h>
......
......@@ -236,6 +236,8 @@ ldap_initialize( LDAP **ldp, LDAP_CONST char *url )
ldap_ld_free(ld, 1, NULL, NULL);
return rc;
}
if (ldap_is_ldapc_url(url))
ld->ld_options.ldo_valid |= LDAP_UDP_SESSION;
}
*ldp = ld;
......@@ -255,12 +257,12 @@ ldap_int_open_connection(
int sasl_ssf = 0;
#endif
char *host;
int port;
int port, proto;
long addr;
Debug( LDAP_DEBUG_TRACE, "ldap_int_open_connection\n", 0, 0, 0 );
switch ( ldap_pvt_url_scheme2proto( srv->lud_scheme ) ) {
switch ( proto = ldap_pvt_url_scheme2proto( srv->lud_scheme ) ) {
case LDAP_PROTO_TCP:
port = htons( (short) srv->lud_port );
......@@ -272,8 +274,8 @@ ldap_int_open_connection(
host = srv->lud_host;
}
rc = ldap_connect_to_host( ld, conn->lconn_sb, 0,
host, addr, port, async );
rc = ldap_connect_to_host( ld, conn->lconn_sb,
proto, host, addr, port, async );
if ( rc == -1 ) return rc;
......@@ -288,13 +290,36 @@ ldap_int_open_connection(
sasl_host = ldap_host_connected_to( conn->lconn_sb );
#endif
break;
#ifdef LDAP_CONNECTIONLESS
case LDAP_PROTO_UDP:
port = htons( (short) srv->lud_port );
addr = 0;
if ( srv->lud_host == NULL || *srv->lud_host == 0 ) {
host = NULL;
addr = htonl( INADDR_LOOPBACK );
} else {
host = srv->lud_host;
}
ld->ld_options.ldo_valid |= LDAP_UDP_SESSION;
rc = ldap_connect_to_host( ld, conn->lconn_sb,
proto, host, addr, port, async );
if ( rc == -1 ) return rc;
#ifdef LDAP_DEBUG
ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug,
LBER_SBIOD_LEVEL_PROVIDER, (void *)"udp_" );
#endif
ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_udp,
LBER_SBIOD_LEVEL_PROVIDER, NULL );
break;
#endif
case LDAP_PROTO_IPC:
#ifdef LDAP_PF_LOCAL
/* only IPC mechanism supported is PF_LOCAL (PF_UNIX) */
rc = ldap_connect_to_path( ld, conn->lconn_sb,
srv->lud_host, async );
if ( rc == -1 ) return rc;
#ifdef LDAP_DEBUG
ber_sockbuf_add_io( conn->lconn_sb, &ber_sockbuf_io_debug,
LBER_SBIOD_LEVEL_PROVIDER, (void *)"ipc_" );
......@@ -321,6 +346,11 @@ ldap_int_open_connection(
INT_MAX, (void *)"ldap_" );
#endif
#ifdef LDAP_CONNECTIONLESS
if( proto == LDAP_PROTO_UDP )
return 0;
#endif
#ifdef HAVE_CYRUS_SASL
/* establish Cyrus SASL context prior to starting TLS so
that SASL EXTERNAL might be used */
......
......@@ -198,6 +198,18 @@ ldap_pvt_connect(LDAP *ld, ber_socket_t s,
fd_set efds;
#endif
#ifdef LDAP_CONNECTIONLESS
/* We could do a connect() but that would interfere with
* attempts to poll a broadcast address
*/
if (LDAP_IS_UDP(ld)) {
if (ld->ld_options.ldo_peer)
ldap_memfree(ld->ld_options.ldo_peer);
ld->ld_options.ldo_peer=ldap_memalloc(sizeof(struct sockaddr));
AC_MEMCPY(ld->ld_options.ldo_peer,sin,sizeof(struct sockaddr));
return ( 0 );
}
#endif
if ( (opt_tv = ld->ld_options.ldo_tm_net) != NULL ) {
tv.tv_usec = opt_tv->tv_usec;
tv.tv_sec = opt_tv->tv_sec;
......@@ -293,9 +305,18 @@ ldap_connect_to_host(LDAP *ld, Sockbuf *sb,
int rc, i, use_hp = 0;
struct hostent *hp = NULL;
char *ha_buf=NULL, *p, *q;
int socktype;
osip_debug(ld, "ldap_connect_to_host: %s\n",host,0,0);
switch(proto) {
case LDAP_PROTO_TCP: socktype = SOCK_STREAM; break;
case LDAP_PROTO_UDP: socktype = SOCK_DGRAM; break;
default: osip_debug(ld, "ldap_connect_to_host: unknown proto: %d\n",
proto, 0, 0);
return -1;
}
if (host != NULL) {
#if defined( HAVE_GETADDRINFO ) && defined( HAVE_INET_NTOP )
char serv[7];
......@@ -304,7 +325,7 @@ ldap_connect_to_host(LDAP *ld, Sockbuf *sb,
memset( &hints, '\0', sizeof(hints) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_socktype = socktype;
snprintf(serv, sizeof serv, "%d", ntohs(port));
if ( err = getaddrinfo(host, serv, &hints, &res) ) {
......@@ -316,7 +337,7 @@ ldap_connect_to_host(LDAP *ld, Sockbuf *sb,
rc = -1;
do {
/* we assume AF_x and PF_x are equal for all x */
s = ldap_int_socket( ld, sai->ai_family, SOCK_STREAM );
s = ldap_int_socket( ld, sai->ai_family, socktype );
if ( s == AC_SOCKET_INVALID ) {
continue;
}
......@@ -384,7 +405,7 @@ ldap_connect_to_host(LDAP *ld, Sockbuf *sb,
rc = s = -1;
for ( i = 0; !use_hp || (hp->h_addr_list[i] != 0); ++i, rc = -1 ) {
s = ldap_int_socket( ld, PF_INET, SOCK_STREAM );
s = ldap_int_socket( ld, PF_INET, socktype );
if ( s == AC_SOCKET_INVALID ) {
/* use_hp ? continue : break; */
break;
......
......@@ -107,6 +107,18 @@ ldap_send_initial_request(
servers = NULL;
}
#ifdef LDAP_CONNECTIONLESS
if (LDAP_IS_UDP(ld)) {
if (msgtype == LDAP_REQ_BIND) {
if (ld->ld_options.ldo_cldapdn)
ldap_memfree(ld->ld_options.ldo_cldapdn);
ld->ld_options.ldo_cldapdn = ldap_strdup(dn);
return 0;
}
if (msgtype != LDAP_REQ_ABANDON && msgtype != LDAP_REQ_SEARCH)
return LDAP_PARAM_ERROR;
}
#endif
rc = ldap_send_server_request( ld, ber, ld->ld_msgid, NULL,
servers, NULL, NULL );
if (servers)
......
......@@ -372,6 +372,12 @@ try_read1msg(
/* get the next message */
errno = 0;
#ifdef LDAP_CONNECTIONLESS
if ( LDAP_IS_UDP(ld) ) {
struct sockaddr from;
ber_int_sb_read(sb, &from, sizeof(struct sockaddr));
}
#endif
if ( (tag = ber_get_next( sb, &len, ber ))
!= LDAP_TAG_MESSAGE ) {
if ( tag == LBER_DEFAULT) {
......@@ -419,7 +425,14 @@ try_read1msg(
ber_free( ber, 1 );
return( -2 ); /* continue looking */
}
#ifdef LDAP_CONNECTIONLESS
if (LDAP_IS_UDP(ld)) {
char *blank;
ber_scanf(ber, "a{", &blank);
if (blank)
ber_memfree(blank);
}
#endif
/* the message type */
if ( (tag = ber_peek_tag( ber, &len )) == LBER_ERROR ) {
ld->ld_errno = LDAP_DECODING_ERROR;
......
......@@ -423,7 +423,16 @@ ldap_sasl_interactive_bind_s(
#if defined( LDAP_R_COMPILE ) && defined( HAVE_CYRUS_SASL )
ldap_pvt_thread_mutex_lock( &ldap_int_sasl_mutex );
#endif
#ifdef LDAP_CONNECTIONLESS
if( LDAP_IS_UDP(ld) ) {
/* Just force it to simple bind, silly to make the user
* ask all the time. No, we don't ever actually bind, but I'll
* let the final bind handler take care of saving the cdn.
*/
rc = ldap_simple_bind(ld, dn, NULL);
return rc < 0 ? rc : 0;
} else
#endif
if( mechs == NULL || *mechs == '\0' ) {
char *smechs;
......
......@@ -297,11 +297,28 @@ ldap_build_search_req(
}
}
err = ber_printf( ber, "{it{seeiib", ++ld->ld_msgid,
#ifdef LDAP_CONNECTIONLESS
if ( LDAP_IS_UDP(ld) ) {
err = ber_write( ber, ld->ld_options.ldo_peer,
sizeof(struct sockaddr), 0);
if (err == sizeof(struct sockaddr)) {
char *dn = ld->ld_options.ldo_cldapdn;
if (!dn) dn = "";
err = ber_printf( ber, "{ist{seeiib", ++ld->ld_msgid, dn,
LDAP_REQ_SEARCH, base, (ber_int_t) scope, ld->ld_deref,
(sizelimit < 0) ? ld->ld_sizelimit : sizelimit,
(timelimit < 0) ? ld->ld_timelimit : timelimit,
attrsonly );
}
} else
#endif
{
err = ber_printf( ber, "{it{seeiib", ++ld->ld_msgid,
LDAP_REQ_SEARCH, base, (ber_int_t) scope, ld->ld_deref,
(sizelimit < 0) ? ld->ld_sizelimit : sizelimit,
(timelimit < 0) ? ld->ld_timelimit : timelimit,
attrsonly );
}
if ( err == -1 ) {
ld->ld_errno = LDAP_ENCODING_ERROR;
......
......@@ -158,6 +158,10 @@ ldap_send_unbind(
Debug( LDAP_DEBUG_TRACE, "ldap_send_unbind\n", 0, 0, 0 );
#ifdef LDAP_CONNECTIONLESS
if (LDAP_IS_UDP(ld))
return LDAP_SUCCESS;
#endif
/* create a message to send */
if ( (ber = ldap_alloc_ber_with_options( ld )) == NULL ) {
return( ld->ld_errno );
......
......@@ -60,6 +60,11 @@ int ldap_pvt_url_scheme2proto( const char *scheme )
if( strcmp("ldaps", scheme) == 0 ) {
return LDAP_PROTO_TCP;
}
#ifdef LDAP_CONNECTIONLESS
if( strcmp("cldap", scheme) == 0 ) {
return LDAP_PROTO_UDP;
}
#endif
return -1;
}
......@@ -126,6 +131,25 @@ ldap_is_ldapi_url( LDAP_CONST char *url )
return strcmp(scheme, "ldapi") == 0;
}
#ifdef LDAP_CONNECTIONLESS
int
ldap_is_ldapc_url( LDAP_CONST char *url )
{
int enclosed;
const char * scheme;
if( url == NULL ) {
return 0;
}
if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
return 0;
}
return strcmp(scheme, "cldap") == 0;
}
#endif
static const char*
skip_url_prefix(
const char *url,
......@@ -181,6 +205,16 @@ skip_url_prefix(
return( p );
}
#ifdef LDAP_CONNECTIONLESS
/* check for "cldap://" prefix */
if ( strncasecmp( p, LDAPC_URL_PREFIX, LDAPC_URL_PREFIX_LEN ) == 0 ) {
/* skip over "cldap://" prefix and return success */
p += LDAPC_URL_PREFIX_LEN;
*scheme = "cldap";
return( p );
}
#endif
return( NULL );
}
......
......@@ -317,7 +317,7 @@ long connection_init(
const char* dnsname,
const char* peername,
const char* sockname,
int use_tls,
int tls_udp_option,
slap_ssf_t ssf,
const char *authid )
{
......@@ -331,7 +331,7 @@ long connection_init(
assert( sockname != NULL );
#ifndef HAVE_TLS
assert( !use_tls );
assert( tls_udp_option != 1 );
#endif
if( s == AC_SOCKET_INVALID ) {
......@@ -474,12 +474,27 @@ long connection_init(
c->c_activitytime = c->c_starttime = slap_get_time();
#ifdef LDAP_CONNECTIONLESS
c->c_is_udp = 0;
if (tls_udp_option == 2)
{
c->c_is_udp = 1;
#ifdef LDAP_DEBUG
ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_debug,
LBER_SBIOD_LEVEL_PROVIDER, (void*)"udp_" );
#endif
ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_udp,
LBER_SBIOD_LEVEL_PROVIDER, (void *)&s );
} else
#endif
{
#ifdef LDAP_DEBUG
ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_debug,
LBER_SBIOD_LEVEL_PROVIDER, (void*)"tcp_" );
#endif
ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_tcp,
LBER_SBIOD_LEVEL_PROVIDER, (void *)&s );
}
ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_readahead,
LBER_SBIOD_LEVEL_PROVIDER, NULL );
......@@ -511,7 +526,7 @@ long connection_init(
c->c_tls_ssf = 0;
#ifdef HAVE_TLS
if ( use_tls ) {
if ( tls_udp_option == 1 ) {
c->c_is_tls = 1;
c->c_needs_tls_accept = 1;
} else {
......@@ -1160,6 +1175,10 @@ connection_input(
ber_len_t len;
ber_int_t msgid;
BerElement *ber;
#ifdef LDAP_CONNECTIONLESS
Sockaddr peeraddr;
char *cdn = NULL;
#endif
if ( conn->c_currentber == NULL && (conn->c_currentber = ber_alloc())
== NULL ) {
......@@ -1174,6 +1193,21 @@ connection_input(
errno = 0;
#ifdef LDAP_CONNECTIONLESS
if (conn->c_is_udp)
{
char peername[sizeof("IP=255.255.255.255:65336")];
len = ber_int_sb_read(conn->c_sb, &peeraddr,
sizeof(struct sockaddr));
sprintf( peername, "IP=%s:%d",
inet_ntoa( peeraddr.sa_in_addr.sin_addr ),
(unsigned) ntohs( peeraddr.sa_in_addr.sin_port ) );
Statslog( LDAP_DEBUG_STATS,
"conn=%ld UDP request from %s (%s) accepted.\n",
conn->c_connid, peername,
conn->c_sock_name, 0, 0 );
}
#endif
tag = ber_get_next( conn->c_sb, &len, conn->c_currentber );
if ( tag != LDAP_TAG_MESSAGE ) {
int err = errno;
......@@ -1216,6 +1250,11 @@ connection_input(
ber_free( ber, 1 );
return -1;
}
#ifdef LDAP_CONNECTIONLESS
if (conn->c_is_udp) {
tag = ber_get_stringa( ber, &cdn );
}
#endif
if ( (tag = ber_peek_tag( ber, &len )) == LBER_ERROR ) {
/* log, close and send error */
......@@ -1232,6 +1271,22 @@ connection_input(
return -1;
}
#ifdef LDAP_CONNECTIONLESS
if (conn->c_is_udp && (tag != LDAP_REQ_ABANDON &&
tag != LDAP_REQ_SEARCH))
{
#ifdef NEW_LOGGING
LDAP_LOG(( "connection", LDAP_LEVEL_ERR,
"connection_input: conn %d invalid req for UDP 0x%lx.\n",
conn->c_connid, tag ));