From b527294153be3b79563c82c66102adc0004736c0 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 19:54:12 +0200 Subject: Adding upstream version 2.6.7+dfsg. Signed-off-by: Daniel Baumann --- servers/lloadd/Makefile.in | 50 + servers/lloadd/Makefile_module.in | 28 + servers/lloadd/Makefile_server.in | 79 + servers/lloadd/backend.c | 765 +++++++ servers/lloadd/bind.c | 996 +++++++++ servers/lloadd/client.c | 805 ++++++++ servers/lloadd/config.c | 4032 +++++++++++++++++++++++++++++++++++++ servers/lloadd/connection.c | 644 ++++++ servers/lloadd/daemon.c | 1978 ++++++++++++++++++ servers/lloadd/epoch.c | 321 +++ servers/lloadd/epoch.h | 148 ++ servers/lloadd/extended.c | 220 ++ servers/lloadd/init.c | 246 +++ servers/lloadd/libevent_support.c | 169 ++ servers/lloadd/lload-config.h | 39 + servers/lloadd/lload.h | 601 ++++++ servers/lloadd/lloadd.service | 13 + servers/lloadd/main.c | 955 +++++++++ servers/lloadd/module_init.c | 190 ++ servers/lloadd/monitor.c | 1368 +++++++++++++ servers/lloadd/operation.c | 725 +++++++ servers/lloadd/proto-lload.h | 254 +++ servers/lloadd/tier.c | 168 ++ servers/lloadd/tier_bestof.c | 328 +++ servers/lloadd/tier_roundrobin.c | 136 ++ servers/lloadd/tier_weighted.c | 224 +++ servers/lloadd/upstream.c | 1184 +++++++++++ servers/lloadd/value.c | 67 + 28 files changed, 16733 insertions(+) create mode 100644 servers/lloadd/Makefile.in create mode 100644 servers/lloadd/Makefile_module.in create mode 100644 servers/lloadd/Makefile_server.in create mode 100644 servers/lloadd/backend.c create mode 100644 servers/lloadd/bind.c create mode 100644 servers/lloadd/client.c create mode 100644 servers/lloadd/config.c create mode 100644 servers/lloadd/connection.c create mode 100644 servers/lloadd/daemon.c create mode 100644 servers/lloadd/epoch.c create mode 100644 servers/lloadd/epoch.h create mode 100644 servers/lloadd/extended.c create mode 100644 servers/lloadd/init.c create mode 100644 servers/lloadd/libevent_support.c create mode 100644 servers/lloadd/lload-config.h create mode 100644 servers/lloadd/lload.h create mode 100644 servers/lloadd/lloadd.service create mode 100644 servers/lloadd/main.c create mode 100644 servers/lloadd/module_init.c create mode 100644 servers/lloadd/monitor.c create mode 100644 servers/lloadd/operation.c create mode 100644 servers/lloadd/proto-lload.h create mode 100644 servers/lloadd/tier.c create mode 100644 servers/lloadd/tier_bestof.c create mode 100644 servers/lloadd/tier_roundrobin.c create mode 100644 servers/lloadd/tier_weighted.c create mode 100644 servers/lloadd/upstream.c create mode 100644 servers/lloadd/value.c (limited to 'servers/lloadd') diff --git a/servers/lloadd/Makefile.in b/servers/lloadd/Makefile.in new file mode 100644 index 0000000..a4c1dff --- /dev/null +++ b/servers/lloadd/Makefile.in @@ -0,0 +1,50 @@ +# Makefile.in for Load Balancer +# $OpenLDAP$ +## This work is part of OpenLDAP Software . +## +## Copyright 1998-2022 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 +## . + + +XSRCS = version.c + + +SRCS = backend.c bind.c config.c connection.c client.c \ + daemon.c epoch.c extended.c init.c operation.c \ + tier.c tier_roundrobin.c tier_weighted.c tier_bestof.c \ + upstream.c libevent_support.c \ + $(@PLAT@_SRCS) + +O = o + +OBJS = backend.$O bind.$O config.$O connection.$O client.$O \ + daemon.$O epoch.$O extended.$O init.$O operation.$O \ + tier.$O tier_roundrobin.$O tier_weighted.$O tier_bestof.$O \ + upstream.$O libevent_support.$O + +LDAP_INCDIR= ../../include -I$(srcdir) -I$(srcdir)/../slapd +LDAP_LIBDIR= ../../libraries + + +# $(LTHREAD_LIBS) must be last! +XLIBS = $(LLOADD_L) +XXLIBS = $(LLOADD_LIBS) $(SECURITY_LIBS) $(LUTIL_LIBS) +XXXLIBS = $(LTHREAD_LIBS) + +NT_DEPENDS = slapd.exp +NT_OBJECTS = slapd.exp symdummy.o $(LLOADD_OBJS) version.o + +UNIX_DEPENDS = version.o $(LLOADD_L) +UNIX_OBJECTS = $(OBJS) version.o + +LLOADD_DEPENDS = $(@PLAT@_DEPENDS) +LLOADD_OBJECTS = $(@PLAT@_OBJECTS) + diff --git a/servers/lloadd/Makefile_module.in b/servers/lloadd/Makefile_module.in new file mode 100644 index 0000000..8ebb5d6 --- /dev/null +++ b/servers/lloadd/Makefile_module.in @@ -0,0 +1,28 @@ +# Makefile.in for Load Balancer module +# $OpenLDAP$ +## This work is part of OpenLDAP Software . +## +## Copyright 1998-2022 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 +## . + +O = lo + +SRCS += module_init.c monitor.c + +OBJS += module_init.lo monitor.lo + +BUILD_OPT = "--enable-balancer=mod" +BUILD_MOD = @BUILD_BALANCER@ + +LIBBASE=lloadd + +LINK_LIBS=$(LLOADD_LIBS) +MOD_DEFS = -DSLAPD_IMPORT -DBALANCER_MODULE diff --git a/servers/lloadd/Makefile_server.in b/servers/lloadd/Makefile_server.in new file mode 100644 index 0000000..86d99d2 --- /dev/null +++ b/servers/lloadd/Makefile_server.in @@ -0,0 +1,79 @@ +# Makefile.in for standalone Load Balancer +# $OpenLDAP$ +## This work is part of OpenLDAP Software . +## +## Copyright 1998-2022 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 +## . + +PROGRAMS = lloadd +XPROGRAMS = slloadd + +NT_SRCS = ../slapd/nt_svc.c +NT_OBJS = ../slapd/nt_svc.o ../../libraries/liblutil/slapdmsg.res + +SRCS += main.c value.c \ + ../slapd/ch_malloc.c ../slapd/logging.c ../slapd/proxyp.c \ + ../slapd/sl_malloc.c ../slapd/user.c ../slapd/verbs.c + +OBJS += main.o value.o \ + ../slapd/ch_malloc.o ../slapd/logging.o ../slapd/proxyp.o \ + ../slapd/sl_malloc.o ../slapd/user.o ../slapd/verbs.o \ + $(@PLAT@_OBJS) + +BUILD_OPT = "--enable-balancer" +BUILD_SRV = @BUILD_BALANCER@ + +all-local-srv: $(PROGRAMS) all-cffiles + +XXLIBS += $(SYSTEMD_LIBS) + +lloadd: $(LLOADD_DEPENDS) version.o + $(LTLINK) -o $@ $(OBJS) version.o $(LIBS) + +slloadd: version.o + $(LTLINK) -static -o $@ $(OBJS) version.o $(LIBS) + +version.c: Makefile + @-$(RM) $@ + $(MKVERSION) -s -n Versionstr lloadd > $@ + +version.o: version.c $(OBJS) $(LLOADD_L) + +all-cffiles: + @if test -n "$(systemdsystemunitdir)"; then \ + $(SED) -e "s;%LIBEXECDIR%;$(libexecdir);" \ + $(srcdir)/lloadd.service > lloadd.service.tmp ; \ + fi + touch all-cffiles + +clean-local-srv: FORCE + $(RM) *.tmp all-cffiles + +install-local-srv: install-lloadd install-conf + +install-lloadd: FORCE + -$(MKDIR) $(DESTDIR)$(libexecdir) + @-$(INSTALL) -m 700 -d $(DESTDIR)$(localstatedir)/openldap-lloadd + @( \ + for prg in $(PROGRAMS); do \ + $(LTINSTALL) $(INSTALLFLAGS) $(STRIP_OPTS) -m 755 $$prg$(EXEEXT) \ + $(DESTDIR)$(libexecdir); \ + done \ + ) + +install-conf: FORCE + @-$(MKDIR) $(DESTDIR)$(sysconfdir) + if test -n "$(systemdsystemunitdir)" && test ! -f $(DESTDIR)$(systemdsystemunitdir)/lloadd.service; then \ + $(MKDIR) $(DESTDIR)$(systemdsystemunitdir); \ + echo "installing lloadd.service in $(systemdsystemunitdir)"; \ + echo "$(INSTALL) $(INSTALLFLAGS) -m 644 lloadd.service.tmp $(DESTDIR)$(systemdsystemunitdir)/lloadd.service"; \ + $(INSTALL) $(INSTALLFLAGS) -m 644 lloadd.service.tmp $(DESTDIR)$(systemdsystemunitdir)/lloadd.service; \ + fi diff --git a/servers/lloadd/backend.c b/servers/lloadd/backend.c new file mode 100644 index 0000000..d30284e --- /dev/null +++ b/servers/lloadd/backend.c @@ -0,0 +1,765 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2022 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 + * . + */ + +#include "portable.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "lutil.h" +#include "lload.h" + +static void +upstream_connect_cb( evutil_socket_t s, short what, void *arg ) +{ + LloadPendingConnection *conn = arg; + LloadBackend *b = conn->backend; + int error = 0, rc = -1; + epoch_t epoch; + + checked_lock( &b->b_mutex ); + Debug( LDAP_DEBUG_CONNS, "upstream_connect_cb: " + "fd=%d connection callback for backend uri='%s'\n", + s, b->b_uri.bv_val ); + + if ( s != conn->fd ) { + /* backend_reset has been here first */ + goto preempted; + } + + epoch = epoch_join(); + + if ( what == EV_WRITE ) { + socklen_t optlen = sizeof(error); + + if ( getsockopt( conn->fd, SOL_SOCKET, SO_ERROR, (void *)&error, + &optlen ) < 0 ) { + goto done; + } + if ( error == EINTR || error == EINPROGRESS || error == EWOULDBLOCK ) { + checked_unlock( &b->b_mutex ); + epoch_leave( epoch ); + return; + } else if ( error ) { + goto done; + } else if ( upstream_init( s, conn->backend ) == NULL ) { + goto done; + } + rc = LDAP_SUCCESS; + } + +done: + epoch_leave( epoch ); + + LDAP_LIST_REMOVE( conn, next ); + if ( rc ) { + evutil_closesocket( conn->fd ); + b->b_opening--; + b->b_failed++; + if ( what & EV_TIMEOUT ) { + Debug( LDAP_DEBUG_ANY, "upstream_connect_cb: " + "fd=%d connection timed out\n", + s ); + } else { + char ebuf[128]; + Debug( LDAP_DEBUG_ANY, "upstream_connect_cb: " + "fd=%d connection set up failed%s%s\n", + s, error ? ": " : "", + error ? sock_errstr( error, ebuf, sizeof(ebuf) ) : "" ); + } + backend_retry( b ); + } +preempted: + checked_unlock( &b->b_mutex ); + + event_free( conn->event ); + ch_free( conn ); +} + +static void +upstream_name_cb( int result, struct evutil_addrinfo *res, void *arg ) +{ + LloadBackend *b = arg; + ber_socket_t s = AC_SOCKET_INVALID; + epoch_t epoch; + int rc; + + if ( result == EVUTIL_EAI_CANCEL ) { + Debug( LDAP_DEBUG_ANY, "upstream_name_cb: " + "cancelled\n" ); + return; + } + + checked_lock( &b->b_mutex ); + /* We were already running when backend_reset tried to cancel us, but were + * already stuck waiting for the mutex, nothing to do and b_opening has + * been decremented as well */ + if ( b->b_dns_req == NULL ) { + checked_unlock( &b->b_mutex ); + return; + } + b->b_dns_req = NULL; + + epoch = epoch_join(); + if ( result || !res ) { + Debug( LDAP_DEBUG_ANY, "upstream_name_cb: " + "name resolution failed for backend '%s': %s\n", + b->b_uri.bv_val, evutil_gai_strerror( result ) ); + goto fail; + } + + /* TODO: if we get failures, try the other addrinfos */ + if ( (s = socket( res->ai_family, SOCK_STREAM, 0 )) == + AC_SOCKET_INVALID ) { + goto fail; + } + + if ( ber_pvt_socket_set_nonblock( s, 1 ) ) { + goto fail; + } + +#if defined(SO_KEEPALIVE) || defined(TCP_NODELAY) + if ( b->b_proto == LDAP_PROTO_TCP ) { + int dummy = 1; +#ifdef SO_KEEPALIVE + if ( setsockopt( s, SOL_SOCKET, SO_KEEPALIVE, (char *)&dummy, + sizeof(dummy) ) == AC_SOCKET_ERROR ) { + Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: " + "setsockopt(%d, SO_KEEPALIVE) failed (ignored).\n", + s ); + } + if ( bindconf.sb_keepalive.sk_idle > 0 ) { +#ifdef TCP_KEEPIDLE + if ( setsockopt( s, IPPROTO_TCP, TCP_KEEPIDLE, + (void *)&bindconf.sb_keepalive.sk_idle, + sizeof(bindconf.sb_keepalive.sk_idle) ) == + AC_SOCKET_ERROR ) { + Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: " + "setsockopt(%d, TCP_KEEPIDLE) failed (ignored).\n", + s ); + } +#else + Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: " + "sockopt TCP_KEEPIDLE not supported on this system.\n" ); +#endif /* TCP_KEEPIDLE */ + } + if ( bindconf.sb_keepalive.sk_probes > 0 ) { +#ifdef TCP_KEEPCNT + if ( setsockopt( s, IPPROTO_TCP, TCP_KEEPCNT, + (void *)&bindconf.sb_keepalive.sk_probes, + sizeof(bindconf.sb_keepalive.sk_probes) ) == + AC_SOCKET_ERROR ) { + Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: " + "setsockopt(%d, TCP_KEEPCNT) failed (ignored).\n", + s ); + } +#else + Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: " + "sockopt TCP_KEEPCNT not supported on this system.\n" ); +#endif /* TCP_KEEPCNT */ + } + if ( bindconf.sb_keepalive.sk_interval > 0 ) { +#ifdef TCP_KEEPINTVL + if ( setsockopt( s, IPPROTO_TCP, TCP_KEEPINTVL, + (void *)&bindconf.sb_keepalive.sk_interval, + sizeof(bindconf.sb_keepalive.sk_interval) ) == + AC_SOCKET_ERROR ) { + Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: " + "setsockopt(%d, TCP_KEEPINTVL) failed (ignored).\n", + s ); + } +#else + Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: " + "sockopt TCP_KEEPINTVL not supported on this system.\n" ); +#endif /* TCP_KEEPINTVL */ + } +#endif /* SO_KEEPALIVE */ + if ( bindconf.sb_tcp_user_timeout > 0 ) { +#ifdef TCP_USER_TIMEOUT + if ( setsockopt( s, IPPROTO_TCP, TCP_USER_TIMEOUT, + (void *)&bindconf.sb_tcp_user_timeout, + sizeof(bindconf.sb_tcp_user_timeout) ) == + AC_SOCKET_ERROR ) { + Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: " + "setsockopt(%d, TCP_USER_TIMEOUT) failed (ignored).\n", + s ); + } +#else + Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: " + "sockopt TCP_USER_TIMEOUT not supported on this " + "system.\n" ); +#endif /* TCP_USER_TIMEOUT */ + } +#ifdef TCP_NODELAY + if ( setsockopt( s, IPPROTO_TCP, TCP_NODELAY, (char *)&dummy, + sizeof(dummy) ) == AC_SOCKET_ERROR ) { + Debug( LDAP_DEBUG_TRACE, "upstream_name_cb: " + "setsockopt(%d, TCP_NODELAY) failed (ignored).\n", + s ); + } +#endif /* TCP_NODELAY */ + } +#endif /* SO_KEEPALIVE || TCP_NODELAY */ + + if ( res->ai_family == PF_INET ) { + struct sockaddr_in *ai = (struct sockaddr_in *)res->ai_addr; + ai->sin_port = htons( b->b_port ); + rc = connect( s, (struct sockaddr *)ai, res->ai_addrlen ); + } else { + struct sockaddr_in6 *ai = (struct sockaddr_in6 *)res->ai_addr; + ai->sin6_port = htons( b->b_port ); + rc = connect( s, (struct sockaddr *)ai, res->ai_addrlen ); + } + /* Asynchronous connect */ + if ( rc ) { + LloadPendingConnection *conn; + + if ( errno != EINPROGRESS && errno != EWOULDBLOCK ) { + Debug( LDAP_DEBUG_ANY, "upstream_name_cb: " + "failed to connect to server '%s'\n", + b->b_uri.bv_val ); + evutil_closesocket( s ); + goto fail; + } + + conn = ch_calloc( 1, sizeof(LloadPendingConnection) ); + LDAP_LIST_ENTRY_INIT( conn, next ); + conn->backend = b; + conn->fd = s; + + conn->event = event_new( lload_get_base( s ), s, EV_WRITE|EV_PERSIST, + upstream_connect_cb, conn ); + if ( !conn->event ) { + Debug( LDAP_DEBUG_ANY, "upstream_name_cb: " + "failed to acquire an event to finish upstream " + "connection setup.\n" ); + ch_free( conn ); + evutil_closesocket( s ); + goto fail; + } + + event_add( conn->event, lload_timeout_net ); + LDAP_LIST_INSERT_HEAD( &b->b_connecting, conn, next ); + Debug( LDAP_DEBUG_CONNS, "upstream_name_cb: " + "connection to backend uri=%s in progress\n", + b->b_uri.bv_val ); + } else if ( upstream_init( s, b ) == NULL ) { + goto fail; + } + + checked_unlock( &b->b_mutex ); + evutil_freeaddrinfo( res ); + epoch_leave( epoch ); + return; + +fail: + if ( s != AC_SOCKET_INVALID ) { + evutil_closesocket( s ); + } + b->b_opening--; + b->b_failed++; + backend_retry( b ); + checked_unlock( &b->b_mutex ); + if ( res ) { + evutil_freeaddrinfo( res ); + } + epoch_leave( epoch ); +} + +int +try_upstream( + LloadBackend *b, + lload_c_head *head, + LloadOperation *op, + LloadConnection *c, + int *res, + char **message ) +{ + assert_locked( &b->b_mutex ); + + checked_lock( &c->c_io_mutex ); + CONNECTION_LOCK(c); + if ( c->c_state == LLOAD_C_READY && !c->c_pendingber && + ( b->b_max_conn_pending == 0 || + c->c_n_ops_executing < b->b_max_conn_pending ) ) { + Debug( LDAP_DEBUG_CONNS, "try_upstream: " + "selected connection connid=%lu for client " + "connid=%lu msgid=%d\n", + c->c_connid, op->o_client_connid, op->o_client_msgid ); + + /* c_state is DYING if we're about to be unlinked */ + assert( IS_ALIVE( c, c_live ) ); + + if ( head ) { + /* + * Round-robin step: + * Rotate the queue to put this connection at the end. + */ + LDAP_CIRCLEQ_MAKE_TAIL( head, c, c_next ); + } + + b->b_n_ops_executing++; + if ( op->o_tag == LDAP_REQ_BIND ) { + b->b_counters[LLOAD_STATS_OPS_BIND].lc_ops_received++; + } else { + b->b_counters[LLOAD_STATS_OPS_OTHER].lc_ops_received++; + } + c->c_n_ops_executing++; + c->c_counters.lc_ops_received++; + + *res = LDAP_SUCCESS; + CONNECTION_ASSERT_LOCKED(c); + assert_locked( &c->c_io_mutex ); + return 1; + } + CONNECTION_UNLOCK(c); + checked_unlock( &c->c_io_mutex ); + return 0; +} + +int +backend_select( + LloadBackend *b, + LloadOperation *op, + LloadConnection **cp, + int *res, + char **message ) +{ + lload_c_head *head; + LloadConnection *c; + + assert_locked( &b->b_mutex ); + if ( b->b_max_pending && b->b_n_ops_executing >= b->b_max_pending ) { + Debug( LDAP_DEBUG_CONNS, "backend_select: " + "backend %s too busy\n", + b->b_uri.bv_val ); + *res = LDAP_BUSY; + *message = "server busy"; + return 1; + } + + if ( op->o_tag == LDAP_REQ_BIND +#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS + && !(lload_features & LLOAD_FEATURE_VC) +#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */ + ) { + head = &b->b_bindconns; + } else { + head = &b->b_conns; + } + + if ( LDAP_CIRCLEQ_EMPTY( head ) ) { + return 0; + } + + *res = LDAP_BUSY; + *message = "server busy"; + + LDAP_CIRCLEQ_FOREACH( c, head, c_next ) { + if ( try_upstream( b, head, op, c, res, message ) ) { + *cp = c; + CONNECTION_ASSERT_LOCKED(c); + assert_locked( &c->c_io_mutex ); + return 1; + } + } + + return 1; +} + +int +upstream_select( + LloadOperation *op, + LloadConnection **cp, + int *res, + char **message ) +{ + LloadTier *tier; + int finished = 0; + + LDAP_STAILQ_FOREACH( tier, &tiers, t_next ) { + if ( (finished = tier->t_type.tier_select( + tier, op, cp, res, message )) ) { + break; + } + } + + return finished; +} + +/* + * Will schedule a connection attempt if there is a need for it. Need exclusive + * access to backend, its b_mutex is not touched here, though. + */ +void +backend_retry( LloadBackend *b ) +{ + int requested; + + if ( slapd_shutdown ) { + Debug( LDAP_DEBUG_CONNS, "backend_retry: " + "shutting down\n" ); + return; + } + assert_locked( &b->b_mutex ); + + requested = b->b_numconns; +#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS + if ( !(lload_features & LLOAD_FEATURE_VC) ) +#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */ + { + requested += b->b_numbindconns; + } + + if ( b->b_active + b->b_bindavail + b->b_opening >= requested ) { + Debug( LDAP_DEBUG_CONNS, "backend_retry: " + "no more connections needed for this backend\n" ); + assert_locked( &b->b_mutex ); + return; + } + + if ( b->b_opening > 0 ) { + Debug( LDAP_DEBUG_CONNS, "backend_retry: " + "retry in progress already\n" ); + assert( b->b_opening == 1 ); + assert_locked( &b->b_mutex ); + return; + } + + /* We incremented b_opening when we activated the event, so it can't be + * pending */ + assert( !event_pending( b->b_retry_event, EV_TIMEOUT, NULL ) ); + b->b_opening++; + + if ( b->b_failed > 0 ) { + Debug( LDAP_DEBUG_CONNS, "backend_retry: " + "scheduling a retry in %d ms\n", + b->b_retry_timeout ); + event_add( b->b_retry_event, &b->b_retry_tv ); + assert_locked( &b->b_mutex ); + return; + } + + Debug( LDAP_DEBUG_CONNS, "backend_retry: " + "scheduling re-connection straight away\n" ); + + if ( ldap_pvt_thread_pool_submit2( + &connection_pool, backend_connect_task, b, &b->b_cookie ) ) { + Debug( LDAP_DEBUG_ANY, "backend_retry: " + "failed to submit retry task, scheduling a retry instead\n" ); + /* The current implementation of ldap_pvt_thread_pool_submit2 can fail + * and still set (an invalid) cookie */ + b->b_cookie = NULL; + b->b_failed++; + event_add( b->b_retry_event, &b->b_retry_tv ); + } + assert_locked( &b->b_mutex ); +} + +void +backend_connect( evutil_socket_t s, short what, void *arg ) +{ + struct evutil_addrinfo hints = {}; + LloadBackend *b = arg; + struct evdns_getaddrinfo_request *request, *placeholder; + char *hostname; + epoch_t epoch; + + checked_lock( &b->b_mutex ); + assert( b->b_dns_req == NULL ); + + if ( b->b_cookie ) { + b->b_cookie = NULL; + } + + if ( slapd_shutdown ) { + Debug( LDAP_DEBUG_CONNS, "backend_connect: " + "doing nothing, shutdown in progress\n" ); + b->b_opening--; + checked_unlock( &b->b_mutex ); + return; + } + + epoch = epoch_join(); + + Debug( LDAP_DEBUG_CONNS, "backend_connect: " + "%sattempting connection to %s\n", + (what & EV_TIMEOUT) ? "retry timeout finished, " : "", + b->b_host ); + +#ifdef LDAP_PF_LOCAL + if ( b->b_proto == LDAP_PROTO_IPC ) { + struct sockaddr_un addr; + ber_socket_t s = socket( PF_LOCAL, SOCK_STREAM, 0 ); + int rc; + + if ( s == AC_SOCKET_INVALID ) { + goto fail; + } + + rc = ber_pvt_socket_set_nonblock( s, 1 ); + if ( rc ) { + evutil_closesocket( s ); + goto fail; + } + + if ( strlen( b->b_host ) > ( sizeof(addr.sun_path) - 1 ) ) { + evutil_closesocket( s ); + goto fail; + } + memset( &addr, '\0', sizeof(addr) ); + addr.sun_family = AF_LOCAL; + strcpy( addr.sun_path, b->b_host ); + + rc = connect( + s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un) ); + /* Asynchronous connect */ + if ( rc ) { + LloadPendingConnection *conn; + + if ( errno != EINPROGRESS && errno != EWOULDBLOCK ) { + evutil_closesocket( s ); + goto fail; + } + + conn = ch_calloc( 1, sizeof(LloadPendingConnection) ); + LDAP_LIST_ENTRY_INIT( conn, next ); + conn->backend = b; + conn->fd = s; + + conn->event = event_new( lload_get_base( s ), s, + EV_WRITE|EV_PERSIST, upstream_connect_cb, conn ); + if ( !conn->event ) { + Debug( LDAP_DEBUG_ANY, "backend_connect: " + "failed to acquire an event to finish upstream " + "connection setup.\n" ); + ch_free( conn ); + evutil_closesocket( s ); + goto fail; + } + + event_add( conn->event, lload_timeout_net ); + LDAP_LIST_INSERT_HEAD( &b->b_connecting, conn, next ); + Debug( LDAP_DEBUG_CONNS, "backend_connect: " + "connection to backend uri=%s in progress\n", + b->b_uri.bv_val ); + } else if ( upstream_init( s, b ) == NULL ) { + goto fail; + } + + checked_unlock( &b->b_mutex ); + epoch_leave( epoch ); + return; + } +#endif /* LDAP_PF_LOCAL */ + + hints.ai_family = AF_UNSPEC; + hints.ai_flags = EVUTIL_AI_CANONNAME; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + hostname = b->b_host; + + /* + * Picking any value on the stack. This is unique to our thread without + * having to call ldap_pvt_thread_self. + * We might have to revert to using ldap_pvt_thread_self eventually since + * this betrays where exactly our stack lies - potentially weakening some + * protections like ASLR. + */ + placeholder = (struct evdns_getaddrinfo_request *)&request; + b->b_dns_req = placeholder; + checked_unlock( &b->b_mutex ); + + request = evdns_getaddrinfo( + dnsbase, hostname, NULL, &hints, upstream_name_cb, b ); + + checked_lock( &b->b_mutex ); + assert( request || b->b_dns_req != placeholder ); + + /* Record the request, unless upstream_name_cb or another thread + * cleared it. Another thread is usually backend_reset or backend_connect + * if upstream_name_cb finished and scheduled another one */ + if ( b->b_dns_req == placeholder ) { + b->b_dns_req = request; + } + checked_unlock( &b->b_mutex ); + epoch_leave( epoch ); + return; + +fail: + b->b_opening--; + b->b_failed++; + backend_retry( b ); + checked_unlock( &b->b_mutex ); + epoch_leave( epoch ); +} + +void * +backend_connect_task( void *ctx, void *arg ) +{ + backend_connect( -1, 0, arg ); + return NULL; +} + +/* + * Needs exclusive access to the backend and no other thread is allowed to call + * backend_retry while we're handling this. + * + * If gentle == 0, a full pause must be in effect, else we risk deadlocking on + * event_free(). + */ +void +backend_reset( LloadBackend *b, int gentle ) +{ + assert_locked( &b->b_mutex ); + if ( b->b_cookie ) { + if ( ldap_pvt_thread_pool_retract( b->b_cookie ) ) { + b->b_cookie = NULL; + b->b_opening--; + } else { + /* + * The task might not be cancelable because it just started + * executing. + * + * Shutdown should be the only time when the thread pool is + * in that state. Keep the cookie in to keep an eye on whether + * it's finished yet. + */ + assert( slapd_shutdown ); + } + } + /* Not safe to hold our mutex and call event_del/free if the event's + * callback is running, relinquish the mutex while we do so. */ + if ( b->b_retry_event && + event_pending( b->b_retry_event, EV_TIMEOUT, NULL ) ) { + assert( b->b_failed ); + checked_unlock( &b->b_mutex ); + event_del( b->b_retry_event ); + checked_lock( &b->b_mutex ); + b->b_opening--; + } + if ( b->b_dns_req ) { + evdns_getaddrinfo_cancel( b->b_dns_req ); + b->b_dns_req = NULL; + b->b_opening--; + } + while ( !LDAP_LIST_EMPTY( &b->b_connecting ) ) { + LloadPendingConnection *pending = LDAP_LIST_FIRST( &b->b_connecting ); + + Debug( LDAP_DEBUG_CONNS, "backend_reset: " + "destroying socket pending connect() fd=%d\n", + pending->fd ); + + event_active( pending->event, EV_WRITE, 0 ); + evutil_closesocket( pending->fd ); + pending->fd = -1; + LDAP_LIST_REMOVE( pending, next ); + + if ( !gentle ) { + /* None of the event bases are running, we're safe to free the + * event right now and potentially free the backend itself */ + event_free( pending->event ); + ch_free( pending ); + } + /* else, just let the event dispose of the resources on its own later */ + b->b_opening--; + } + connections_walk( + &b->b_mutex, &b->b_preparing, lload_connection_close, &gentle ); + assert( LDAP_CIRCLEQ_EMPTY( &b->b_preparing ) ); + assert( b->b_opening == ( b->b_cookie ? 1 : 0 ) ); + b->b_failed = 0; + + connections_walk_last( &b->b_mutex, &b->b_bindconns, b->b_last_bindconn, + lload_connection_close, &gentle ); + assert( gentle || b->b_bindavail == 0 ); + + connections_walk_last( &b->b_mutex, &b->b_conns, b->b_last_conn, + lload_connection_close, &gentle ); + assert( gentle || b->b_active == 0 ); + assert_locked( &b->b_mutex ); +} + +LloadBackend * +lload_backend_new( void ) +{ + LloadBackend *b; + + b = ch_calloc( 1, sizeof(LloadBackend) ); + + LDAP_CIRCLEQ_INIT( &b->b_conns ); + LDAP_CIRCLEQ_INIT( &b->b_bindconns ); + LDAP_CIRCLEQ_INIT( &b->b_preparing ); + LDAP_CIRCLEQ_ENTRY_INIT( b, b_next ); + + b->b_numconns = 1; + b->b_numbindconns = 1; + b->b_weight = 1; + + b->b_retry_timeout = 5000; + + ldap_pvt_thread_mutex_init( &b->b_mutex ); + + return b; +} + +void +lload_backend_destroy( LloadBackend *b ) +{ + Debug( LDAP_DEBUG_CONNS, "lload_backend_destroy: " + "destroying backend uri='%s', numconns=%d, numbindconns=%d\n", + b->b_uri.bv_val, b->b_numconns, b->b_numbindconns ); + + checked_lock( &b->b_mutex ); + b->b_tier->t_type.tier_remove_backend( b->b_tier, b ); + b->b_numconns = b->b_numbindconns = 0; + backend_reset( b, 0 ); + +#ifdef BALANCER_MODULE + if ( b->b_monitor ) { + BackendDB *be; + struct berval monitordn = BER_BVC("cn=monitor"); + int rc; + + be = select_backend( &monitordn, 0 ); + + /* FIXME: implement proper subsys shutdown in back-monitor or make + * backend just an entry, not a subsys */ + rc = b->b_monitor->mss_destroy( be, b->b_monitor ); + assert( rc == LDAP_SUCCESS ); + } +#endif /* BALANCER_MODULE */ + + checked_unlock( &b->b_mutex ); + ldap_pvt_thread_mutex_destroy( &b->b_mutex ); + + if ( b->b_retry_event ) { + event_del( b->b_retry_event ); + event_free( b->b_retry_event ); + b->b_retry_event = NULL; + } + + ch_free( b->b_host ); + ch_free( b->b_uri.bv_val ); + ch_free( b->b_name.bv_val ); + ch_free( b ); +} diff --git a/servers/lloadd/bind.c b/servers/lloadd/bind.c new file mode 100644 index 0000000..eaecb24 --- /dev/null +++ b/servers/lloadd/bind.c @@ -0,0 +1,996 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2022 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 + * . + */ + +#include "portable.h" + +#include +#include +#include +#include +#include + +#include "lutil.h" +#include "lload.h" + +struct berval mech_external = BER_BVC("EXTERNAL"); + +int +bind_mech_external( + LloadConnection *client, + LloadOperation *op, + struct berval *credentials ) +{ + BerValue binddn; + void *ssl; + char *ptr, *message = ""; + int result = LDAP_SUCCESS; + + CONNECTION_ASSERT_LOCKED(client); + client->c_state = LLOAD_C_READY; + client->c_type = LLOAD_C_OPEN; + + op->o_res = LLOAD_OP_COMPLETED; + + /* + * We only support implicit assertion. + * + * Although RFC 4513 says the credentials field must be missing, RFC 4422 + * doesn't and libsasl2 will pass a zero-length string to send. We have to + * allow that. + */ + if ( !BER_BVISEMPTY( credentials ) ) { + result = LDAP_UNWILLING_TO_PERFORM; + message = "proxy authorization is not supported"; + goto done; + } + +#ifdef HAVE_TLS + ssl = ldap_pvt_tls_sb_ctx( client->c_sb ); + if ( !ssl || ldap_pvt_tls_get_peer_dn( ssl, &binddn, NULL, 0 ) ) { + result = LDAP_INVALID_CREDENTIALS; + message = "no externally negotiated identity"; + goto done; + } + client->c_auth.bv_len = binddn.bv_len + STRLENOF("dn:"); + client->c_auth.bv_val = ch_malloc( client->c_auth.bv_len + 1 ); + + ptr = lutil_strcopy( client->c_auth.bv_val, "dn:" ); + ptr = lutil_strncopy( ptr, binddn.bv_val, binddn.bv_len ); + *ptr = '\0'; + + ber_memfree( binddn.bv_val ); + + if ( !ber_bvstrcasecmp( &client->c_auth, &lloadd_identity ) ) { + client->c_type = LLOAD_C_PRIVILEGED; + } +#else /* ! HAVE_TLS */ + result = LDAP_AUTH_METHOD_NOT_SUPPORTED; + message = "requested SASL mechanism not supported"; +#endif /* ! HAVE_TLS */ + +done: + CONNECTION_UNLOCK(client); + operation_send_reject( op, result, message, 1 ); + return LDAP_SUCCESS; +} + +static int +client_bind( + LloadOperation *op, + LloadConnection *upstream, + struct berval *binddn, + ber_tag_t tag, + struct berval *auth ) +{ + ber_printf( upstream->c_pendingber, "t{titOtO}", LDAP_TAG_MESSAGE, + LDAP_TAG_MSGID, op->o_upstream_msgid, + LDAP_REQ_BIND, &op->o_request, + LDAP_TAG_CONTROLS, BER_BV_OPTIONAL( &op->o_ctrls ) ); + + return 0; +} + +#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS +static int +client_bind_as_vc( + LloadOperation *op, + LloadConnection *upstream, + struct berval *binddn, + ber_tag_t tag, + struct berval *auth ) +{ + CONNECTION_LOCK(upstream); + ber_printf( upstream->c_pendingber, "t{tit{tst{{tOOtOtO}}}}", LDAP_TAG_MESSAGE, + LDAP_TAG_MSGID, op->o_upstream_msgid, + LDAP_REQ_EXTENDED, + LDAP_TAG_EXOP_REQ_OID, LDAP_EXOP_VERIFY_CREDENTIALS, + LDAP_TAG_EXOP_REQ_VALUE, + LDAP_TAG_EXOP_VERIFY_CREDENTIALS_COOKIE, BER_BV_OPTIONAL( &upstream->c_vc_cookie ), + &binddn, tag, &auth, + LDAP_TAG_EXOP_VERIFY_CREDENTIALS_CONTROLS, BER_BV_OPTIONAL( &op->o_ctrls ) ); + CONNECTION_UNLOCK(upstream); + return 0; +} +#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */ + +/* + * The client connection can be in the following states: + * 1) there are between zero and many non-bind operations pending + * client->c_state == LLOAD_C_READY && client->c_pin_id == 0 + * 2) there is one bind operation pending (waiting on an upstream response) + * a) It is a simple bind + * b) It is a SASL bind + * 3) there is one SASL bind in progress (received a LDAP_SASL_BIND_IN_PROGRESS + * response) + * + * In cases 2 and 3, client->c_state == LLOAD_C_BINDING, a SASL bind is in + * progress/pending if c_sasl_bind_mech is set. + * + * In the first case, client_reset abandons all operations on the respective + * upstreams, case 2a has client_reset send an anonymous bind to upstream to + * terminate the bind. In cases 2b and 3, c_pin_id is set and we retrieve the + * op. The rest is the same for both. + * + * If c_pin_id is unset, we request an upstream connection assigned, otherwise, + * we try to reuse the pinned upstream. In the case of no upstream, we reject + * the request. A SASL bind request means we acquire a new pin_id if we don't + * have one already. + * + * We have to reset c_auth (which holds the current or pending identity) and + * make sure we set it up eventually: + * - In the case of a simple bind, we already know the final identity being + * requested so we set it up immediately + * - In SASL binds, for mechanisms we implement ourselves (EXTERNAL), we set it + * up at some point + * - Otherwise, we have to ask the upstream what it thinks as the bind + * succeeds, we send an LDAP "Who Am I?" exop, this is one of the few + * requests we send on our own. If we implement the mechanism, we provide the + * identity (EXTERNAL uses the client certificate DN) + * + * At the end of the request processing, if nothing goes wrong, we're in state + * 2b (with c_pin_id set to the op's o_pin_id), or state 2a (we could reset + * c_pin_id/o_pin_id if we wanted but we don't always do that at the moment). + * If something does go wrong, we're either tearing down the client or we + * reject the request and switch to state 1 (clearing c_pin_id). + * + * As usual, we have to make any changes to the target connection before we've + * sent the PDU over it - while we are in charge of the read side and nothing + * happens there without our ceding control, the other read side could wake up + * at any time and preempt us. + * + * On a response (in handle_bind_response): + * - to a simple bind, clear c_auth on a failure otherwise keep it while we + * just reset the client to state 1 + * - failure response to a SASL bind - reset client to state 1 + * - LDAP_SASL_BIND_IN_PROGRESS - clear o_*_msgid from the op (have to + * remove+reinsert it from the respective c_ops!), we need it since it is the + * vessel maintaining the pin between client and upstream + * - all of the above forward the response immediately + * - LDAP_SUCCESS for a SASL bind - we send a "Who Am I?" request to retrieve + * the client's DN, only on receiving the response do we finalise the + * exchange by forwarding the successful bind response + * + * We can't do the same for VC Exop since the exchange is finished at the end + * and we need a change to the VC Exop spec to have the server (optionally?) + * respond with the final authzid (saving us a roundtrip as well). + */ +int +request_bind( LloadConnection *client, LloadOperation *op ) +{ + LloadConnection *upstream = NULL; + BerElement *ber, *copy; + struct berval binddn, auth, mech = BER_BVNULL; + ber_int_t version; + ber_tag_t tag; + unsigned long pin; + int res = LDAP_UNAVAILABLE, rc = LDAP_SUCCESS; + char *message = "no connections available"; + enum op_restriction client_restricted; + + CONNECTION_LOCK(client); + pin = client->c_pin_id; + + if ( pin ) { + LloadOperation *pinned_op, needle = { + .o_client_connid = client->c_connid, + .o_client_msgid = 0, + .o_pin_id = client->c_pin_id, + }; + + Debug( LDAP_DEBUG_CONNS, "request_bind: " + "client connid=%lu is pinned pin=%lu\n", + client->c_connid, pin ); + + pinned_op = + ldap_tavl_delete( &client->c_ops, &needle, operation_client_cmp ); + if ( pinned_op ) { + assert( op->o_tag == pinned_op->o_tag ); + + pinned_op->o_client_msgid = op->o_client_msgid; + + /* Preserve the new BerElement and its pointers, reclaim the old + * one in operation_destroy_from_client if it's still there */ + needle.o_ber = pinned_op->o_ber; + pinned_op->o_ber = op->o_ber; + op->o_ber = needle.o_ber; + + pinned_op->o_request = op->o_request; + pinned_op->o_ctrls = op->o_ctrls; + + /* No one has seen this operation yet, plant the pin back in its stead */ + client->c_n_ops_executing--; + op->o_res = LLOAD_OP_COMPLETED; + ldap_tavl_delete( &client->c_ops, op, operation_client_cmp ); + op->o_client = NULL; + assert( op->o_upstream == NULL ); + + rc = ldap_tavl_insert( &client->c_ops, pinned_op, operation_client_cmp, + ldap_avl_dup_error ); + assert( rc == LDAP_SUCCESS ); + + /* No one has seen this operation yet */ + op->o_refcnt--; + operation_destroy( op ); + + /* We didn't start a new operation, just continuing an existing one */ + lload_stats.counters[LLOAD_STATS_OPS_BIND].lc_ops_received--; + + op = pinned_op; + } + } + + ldap_tavl_delete( &client->c_ops, op, operation_client_cmp ); + client->c_n_ops_executing--; + + client_reset( client ); + + client->c_state = LLOAD_C_BINDING; + client->c_type = LLOAD_C_OPEN; + + if ( (copy = ber_alloc()) == NULL ) { + goto fail; + } + ber_init2( copy, &op->o_request, 0 ); + + tag = ber_get_int( copy, &version ); + if ( tag == LBER_ERROR ) { + Debug( LDAP_DEBUG_PACKETS, "request_bind: " + "failed to parse version field\n" ); + goto fail; + } else if ( version != LDAP_VERSION3 ) { + CONNECTION_UNLOCK(client); + operation_send_reject( + op, LDAP_PROTOCOL_ERROR, "LDAP version unsupported", 1 ); + CONNECTION_LOCK(client); + goto fail; + } + + tag = ber_get_stringbv( copy, &binddn, LBER_BV_NOTERM ); + if ( tag == LBER_ERROR ) { + Debug( LDAP_DEBUG_PACKETS, "request_bind: " + "failed to parse bind name field\n" ); + goto fail; + } + + if ( !BER_BVISNULL( &client->c_auth ) ) { + ch_free( client->c_auth.bv_val ); + BER_BVZERO( &client->c_auth ); + } + + tag = ber_skip_element( copy, &auth ); + if ( tag == LDAP_AUTH_SIMPLE ) { + if ( !BER_BVISEMPTY( &binddn ) ) { + char *ptr; + client->c_auth.bv_len = STRLENOF("dn:") + binddn.bv_len; + client->c_auth.bv_val = ch_malloc( client->c_auth.bv_len + 1 ); + + ptr = lutil_strcopy( client->c_auth.bv_val, "dn:" ); + ptr = lutil_strncopy( ptr, binddn.bv_val, binddn.bv_len ); + *ptr = '\0'; + } + + if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) { + ber_memfree( client->c_sasl_bind_mech.bv_val ); + BER_BVZERO( &client->c_sasl_bind_mech ); + } + } else if ( tag == LDAP_AUTH_SASL ) { + ber_init2( copy, &auth, 0 ); + + if ( ber_get_stringbv( copy, &mech, LBER_BV_NOTERM ) == LBER_ERROR ) { + goto fail; + } + if ( !ber_bvcmp( &mech, &mech_external ) ) { + struct berval credentials = BER_BVNULL; + + ber_get_stringbv( copy, &credentials, LBER_BV_NOTERM ); + rc = bind_mech_external( client, op, &credentials ); + + /* terminate the upstream side if client switched mechanisms */ + if ( pin ) { + operation_abandon( op ); + } + + ber_free( copy, 0 ); + return rc; + } else if ( BER_BVISNULL( &client->c_sasl_bind_mech ) ) { + ber_dupbv( &client->c_sasl_bind_mech, &mech ); + } else if ( ber_bvcmp( &mech, &client->c_sasl_bind_mech ) ) { + ber_bvreplace( &client->c_sasl_bind_mech, &mech ); + } + } else { + goto fail; + } + + rc = ldap_tavl_insert( &client->c_ops, op, operation_client_cmp, ldap_avl_dup_error ); + assert( rc == LDAP_SUCCESS ); + client->c_n_ops_executing++; + + client_restricted = client->c_restricted; + CONNECTION_UNLOCK(client); + + if ( pin ) { + checked_lock( &op->o_link_mutex ); + upstream = op->o_upstream; + checked_unlock( &op->o_link_mutex ); + } + + if ( upstream ) { + checked_lock( &upstream->c_io_mutex ); + CONNECTION_LOCK(upstream); + if ( !IS_ALIVE( upstream, c_live ) ) { + CONNECTION_UNLOCK(upstream); + checked_unlock( &upstream->c_io_mutex ); + upstream = NULL; + } + } + + /* If we were pinned but lost the link, don't look for a new upstream, we + * have to reject the op and clear pin */ + if ( upstream ) { + /* No need to do anything */ + } else if ( !pin && client_restricted != LLOAD_OP_RESTRICTED_ISOLATE ) { + upstream_select( op, &upstream, &res, &message ); + } else { + Debug( LDAP_DEBUG_STATS, "request_bind: " + "connid=%lu, msgid=%d pinned upstream lost\n", + op->o_client_connid, op->o_client_msgid ); + operation_send_reject( op, LDAP_OTHER, + "connection to the remote server has been severed", 1 ); + pin = 0; + goto done; + } + + if ( !upstream ) { + Debug( LDAP_DEBUG_STATS, "request_bind: " + "connid=%lu, msgid=%d no available connection found\n", + op->o_client_connid, op->o_client_msgid ); + operation_send_reject( op, res, message, 1 ); + assert( client->c_pin_id == 0 ); + goto done; + } + assert_locked( &upstream->c_io_mutex ); + /* + * At this point, either: + * - upstream is READY and pin == 0 + * - upstream is BINDING, pin != 0 and op->o_upstream_msgid == 0 + * + * A pinned upstream we marked for closing at some point ago should have + * closed by now. + */ + + ber = upstream->c_pendingber; + if ( ber == NULL && (ber = ber_alloc()) == NULL ) { + checked_unlock( &upstream->c_io_mutex ); + if ( !pin ) { + LloadBackend *b = upstream->c_backend; + + upstream->c_n_ops_executing--; + CONNECTION_UNLOCK(upstream); + + checked_lock( &b->b_mutex ); + b->b_n_ops_executing--; + operation_update_backend_counters( op, b ); + checked_unlock( &b->b_mutex ); + } else { + CONNECTION_UNLOCK(upstream); + } + + Debug( LDAP_DEBUG_ANY, "request_bind: " + "ber_alloc failed\n" ); + + OPERATION_UNLINK(op); + + CONNECTION_LOCK(client); + goto fail; + } + upstream->c_pendingber = ber; + + if ( !pin ) { + lload_stats.counters[LLOAD_STATS_OPS_BIND].lc_ops_forwarded++; + } + + if ( pin ) { + ldap_tavl_delete( &upstream->c_ops, op, operation_upstream_cmp ); + if ( tag == LDAP_AUTH_SIMPLE ) { + pin = op->o_pin_id = 0; + } + } else if ( tag == LDAP_AUTH_SASL && !op->o_pin_id ) { + checked_lock( &lload_pin_mutex ); + pin = op->o_pin_id = lload_next_pin++; + Debug( LDAP_DEBUG_CONNS, "request_bind: " + "client connid=%lu allocated pin=%lu linking it to upstream " + "connid=%lu\n", + op->o_client_connid, pin, upstream->c_connid ); + checked_unlock( &lload_pin_mutex ); + } + + op->o_upstream = upstream; + op->o_upstream_connid = upstream->c_connid; + op->o_upstream_msgid = upstream->c_next_msgid++; + op->o_res = LLOAD_OP_FAILED; + + /* Was it unlinked in the meantime? No need to send a response since the + * client is dead */ + if ( !IS_ALIVE( op, o_refcnt ) ) { + LloadBackend *b = upstream->c_backend; + + upstream->c_n_ops_executing--; + checked_unlock( &upstream->c_io_mutex ); + CONNECTION_UNLOCK(upstream); + + checked_lock( &b->b_mutex ); + b->b_n_ops_executing--; + checked_unlock( &b->b_mutex ); + + assert( !IS_ALIVE( client, c_live ) ); + checked_lock( &op->o_link_mutex ); + if ( op->o_upstream ) { + op->o_upstream = NULL; + } + checked_unlock( &op->o_link_mutex ); + rc = -1; + goto done; + } + + if ( BER_BVISNULL( &mech ) ) { + if ( !BER_BVISNULL( &upstream->c_sasl_bind_mech ) ) { + ber_memfree( upstream->c_sasl_bind_mech.bv_val ); + BER_BVZERO( &upstream->c_sasl_bind_mech ); + } + } else if ( ber_bvcmp( &upstream->c_sasl_bind_mech, &mech ) ) { + ber_bvreplace( &upstream->c_sasl_bind_mech, &mech ); + } + + Debug( LDAP_DEBUG_TRACE, "request_bind: " + "added bind from client connid=%lu to upstream connid=%lu " + "as msgid=%d\n", + op->o_client_connid, op->o_upstream_connid, op->o_upstream_msgid ); + if ( ldap_tavl_insert( &upstream->c_ops, op, operation_upstream_cmp, + ldap_avl_dup_error ) ) { + assert(0); + } + upstream->c_state = LLOAD_C_BINDING; + CONNECTION_UNLOCK(upstream); + +#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS + if ( lload_features & LLOAD_FEATURE_VC ) { + rc = client_bind_as_vc( op, upstream, &binddn, tag, &auth ); + } else +#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */ + { + rc = client_bind( op, upstream, &binddn, tag, &auth ); + } + checked_unlock( &upstream->c_io_mutex ); + +done: + + CONNECTION_LOCK(client); + if ( rc == LDAP_SUCCESS ) { + client->c_pin_id = pin; + CONNECTION_UNLOCK(client); + + if ( upstream ) { + connection_write_cb( -1, 0, upstream ); + } + } else { +fail: + rc = -1; + + client->c_pin_id = 0; + CONNECTION_DESTROY(client); + } + + ber_free( copy, 0 ); + return rc; +} + +/* + * Remember the response, but first ask the server what + * authorization identity has been negotiated. + * + * Also, this request will fail if the server thinks a SASL + * confidentiality/integrity layer has been negotiated so we catch + * it early and no other clients are affected. + */ +int +finish_sasl_bind( + LloadConnection *upstream, + LloadOperation *op, + BerElement *ber ) +{ + BerElement *output; + LloadOperation *removed; + ber_int_t msgid; + int rc; + + CONNECTION_ASSERT_LOCKED(upstream); + removed = ldap_tavl_delete( &upstream->c_ops, op, operation_upstream_cmp ); + if ( !removed ) { + assert( upstream->c_state != LLOAD_C_BINDING ); + /* FIXME: has client replaced this bind since? */ + assert(0); + } + assert( removed == op && upstream->c_state == LLOAD_C_BINDING ); + + CONNECTION_UNLOCK(upstream); + + checked_lock( &upstream->c_io_mutex ); + output = upstream->c_pendingber; + if ( output == NULL && (output = ber_alloc()) == NULL ) { + checked_unlock( &upstream->c_io_mutex ); + CONNECTION_LOCK_DESTROY(upstream); + return -1; + } + upstream->c_pendingber = output; + + msgid = upstream->c_next_msgid++; + ber_printf( output, "t{tit{ts}}", LDAP_TAG_MESSAGE, + LDAP_TAG_MSGID, msgid, + LDAP_REQ_EXTENDED, + LDAP_TAG_EXOP_REQ_OID, LDAP_EXOP_WHO_AM_I ); + + /* Make sure noone flushes the buffer before we re-insert the operation */ + CONNECTION_LOCK(upstream); + checked_unlock( &upstream->c_io_mutex ); + + op->o_upstream_msgid = msgid; + + /* remember the response for later */ + ber_free( op->o_ber, 1 ); + op->o_ber = ber; + + /* Could we have been unlinked in the meantime? */ + rc = ldap_tavl_insert( + &upstream->c_ops, op, operation_upstream_cmp, ldap_avl_dup_error ); + assert( rc == LDAP_SUCCESS ); + + CONNECTION_UNLOCK(upstream); + + Debug( LDAP_DEBUG_TRACE, "finish_sasl_bind: " + "SASL exchange in lieu of client connid=%lu to upstream " + "connid=%lu finished, resolving final authzid name msgid=%d\n", + op->o_client_connid, op->o_upstream_connid, op->o_upstream_msgid ); + + connection_write_cb( -1, 0, upstream ); + return LDAP_SUCCESS; +} + +int +handle_bind_response( + LloadConnection *client, + LloadOperation *op, + BerElement *ber ) +{ + LloadConnection *upstream; + BerValue response; + BerElement *copy; + LloadOperation *removed; + ber_int_t result; + ber_tag_t tag; + int rc = LDAP_SUCCESS; + + if ( (copy = ber_alloc()) == NULL ) { + rc = -1; + goto done; + } + + tag = ber_peek_element( ber, &response ); + assert( tag == LDAP_RES_BIND ); + + ber_init2( copy, &response, 0 ); + + tag = ber_get_enum( copy, &result ); + ber_free( copy, 0 ); + + if ( tag == LBER_ERROR ) { + rc = -1; + goto done; + } + + Debug( LDAP_DEBUG_STATS, "handle_bind_response: " + "received response for bind request msgid=%d by client " + "connid=%lu, result=%d\n", + op->o_client_msgid, op->o_client_connid, result ); + + checked_lock( &op->o_link_mutex ); + upstream = op->o_upstream; + checked_unlock( &op->o_link_mutex ); + if ( !upstream ) { + return LDAP_SUCCESS; + } + + CONNECTION_LOCK(upstream); + if ( !ldap_tavl_find( upstream->c_ops, op, operation_upstream_cmp ) ) { + /* + * operation might not be found because: + * - it has timed out (only happens when debugging/hung/...) + * a response has been sent for us, we must not send another + * - it has been abandoned (new bind, unbind) + * no response is expected + * - ??? + */ + CONNECTION_UNLOCK(upstream); + return LDAP_SUCCESS; + } + + /* + * We might be marked for closing, forward the response if we can, but do + * no more if it's a SASL bind - just finish the operation and send failure + * in that case (since we can't resolve the bind identity correctly). + */ + if ( upstream->c_state == LLOAD_C_CLOSING ) { + /* FIXME: this is too ad-hoc */ + if ( ( result == LDAP_SUCCESS || + result == LDAP_SASL_BIND_IN_PROGRESS ) && + !BER_BVISNULL( &upstream->c_sasl_bind_mech ) ) { + CONNECTION_UNLOCK(upstream); + operation_send_reject( + op, LDAP_OTHER, "upstream connection is closing", 0 ); + + ber_free( ber, 1 ); + return LDAP_SUCCESS; + } + + assert( op->o_client_msgid && op->o_upstream_msgid ); + op->o_pin_id = 0; + + } else if ( result == LDAP_SASL_BIND_IN_PROGRESS ) { + ldap_tavl_delete( &upstream->c_ops, op, operation_upstream_cmp ); + op->o_upstream_msgid = 0; + rc = ldap_tavl_insert( + &upstream->c_ops, op, operation_upstream_cmp, ldap_avl_dup_error ); + assert( rc == LDAP_SUCCESS ); + } else { + int sasl_finished = 0; + if ( !BER_BVISNULL( &upstream->c_sasl_bind_mech ) ) { + sasl_finished = 1; + ber_memfree( upstream->c_sasl_bind_mech.bv_val ); + BER_BVZERO( &upstream->c_sasl_bind_mech ); + } + + assert( op->o_client_msgid && op->o_upstream_msgid ); + op->o_pin_id = 0; + + if ( (lload_features & LLOAD_FEATURE_PROXYAUTHZ) && sasl_finished && + result == LDAP_SUCCESS ) { + return finish_sasl_bind( upstream, op, ber ); + } + op->o_res = LLOAD_OP_COMPLETED; + } + CONNECTION_UNLOCK(upstream); + + if ( !op->o_pin_id ) { + operation_unlink_upstream( op, upstream ); + } + + CONNECTION_LOCK(client); + removed = ldap_tavl_delete( &client->c_ops, op, operation_client_cmp ); + assert( !removed || op == removed ); + + if ( client->c_state == LLOAD_C_BINDING ) { + assert( removed ); + switch ( result ) { + case LDAP_SASL_BIND_IN_PROGRESS: + op->o_saved_msgid = op->o_client_msgid; + op->o_client_msgid = 0; + rc = ldap_tavl_insert( &client->c_ops, op, operation_client_cmp, + ldap_avl_dup_error ); + assert( rc == LDAP_SUCCESS ); + break; + case LDAP_SUCCESS: + default: { + client->c_state = LLOAD_C_READY; + client->c_type = LLOAD_C_OPEN; + client->c_pin_id = 0; + client->c_n_ops_executing--; + if ( !BER_BVISNULL( &client->c_auth ) ) { + if ( result != LDAP_SUCCESS ) { + ber_memfree( client->c_auth.bv_val ); + BER_BVZERO( &client->c_auth ); + } else if ( !ber_bvstrcasecmp( + &client->c_auth, &lloadd_identity ) ) { + client->c_type = LLOAD_C_PRIVILEGED; + } + } + if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) { + ber_memfree( client->c_sasl_bind_mech.bv_val ); + BER_BVZERO( &client->c_sasl_bind_mech ); + } + break; + } + } + } else { + if ( removed ) { + client->c_n_ops_executing--; + } + assert( client->c_state == LLOAD_C_DYING || + client->c_state == LLOAD_C_CLOSING ); + } + CONNECTION_UNLOCK(client); + +done: + if ( rc ) { + operation_send_reject( op, LDAP_OTHER, "internal error", 1 ); + + ber_free( ber, 1 ); + return LDAP_SUCCESS; + } + return forward_final_response( client, op, ber ); +} + +int +handle_whoami_response( + LloadConnection *client, + LloadOperation *op, + BerElement *ber ) +{ + LloadConnection *upstream; + BerValue matched, diagmsg; + BerElement *saved_response = op->o_ber; + LloadOperation *removed; + ber_int_t result; + ber_tag_t tag; + ber_len_t len; + + Debug( LDAP_DEBUG_TRACE, "handle_whoami_response: " + "connid=%ld received whoami response in lieu of connid=%ld\n", + op->o_upstream_connid, client->c_connid ); + + tag = ber_scanf( ber, "{emm" /* "}" */, + &result, &matched, &diagmsg ); + if ( tag == LBER_ERROR ) { + operation_send_reject( op, LDAP_OTHER, "upstream protocol error", 0 ); + return -1; + } + + checked_lock( &op->o_link_mutex ); + upstream = op->o_upstream; + checked_unlock( &op->o_link_mutex ); + if ( !upstream ) { + return LDAP_SUCCESS; + } + + op->o_res = LLOAD_OP_COMPLETED; + /* Clear upstream status */ + operation_unlink_upstream( op, upstream ); + + if ( result == LDAP_PROTOCOL_ERROR ) { + LloadBackend *b; + + CONNECTION_LOCK(upstream); + b = upstream->c_backend; + Debug( LDAP_DEBUG_ANY, "handle_whoami_response: " + "Who Am I? extended operation not supported on backend %s, " + "proxyauthz with clients that do SASL binds will not work " + "msg=%s!\n", + b->b_uri.bv_val, diagmsg.bv_val ); + CONNECTION_UNLOCK(upstream); + operation_send_reject( op, LDAP_OTHER, "upstream protocol error", 0 ); + return -1; + } + + tag = ber_peek_tag( ber, &len ); + + CONNECTION_LOCK(client); + + assert( client->c_state == LLOAD_C_BINDING || + client->c_state == LLOAD_C_CLOSING ); + + assert( BER_BVISNULL( &client->c_auth ) ); + if ( !BER_BVISNULL( &client->c_auth ) ) { + ber_memfree( client->c_auth.bv_val ); + BER_BVZERO( &client->c_auth ); + } + + if ( tag == LDAP_TAG_EXOP_RES_VALUE ) { + tag = ber_scanf( ber, "o", &client->c_auth ); + if ( tag == LBER_ERROR ) { + CONNECTION_DESTROY(client); + return -1; + } + } + + removed = ldap_tavl_delete( &client->c_ops, op, operation_client_cmp ); + assert( !removed || op == removed ); + op->o_pin_id = 0; + if ( removed ) { + client->c_n_ops_executing--; + } + + Debug( LDAP_DEBUG_TRACE, "handle_whoami_response: " + "connid=%ld new authid=%s\n", + client->c_connid, client->c_auth.bv_val ); + + if ( client->c_state == LLOAD_C_BINDING ) { + client->c_state = LLOAD_C_READY; + client->c_type = LLOAD_C_OPEN; + client->c_pin_id = 0; + if ( !BER_BVISNULL( &client->c_auth ) && + !ber_bvstrcasecmp( &client->c_auth, &lloadd_identity ) ) { + client->c_type = LLOAD_C_PRIVILEGED; + } + if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) { + ber_memfree( client->c_sasl_bind_mech.bv_val ); + BER_BVZERO( &client->c_sasl_bind_mech ); + } + } + + CONNECTION_UNLOCK(client); + + /* defer the disposal of ber to operation_destroy */ + op->o_ber = ber; + + return forward_final_response( client, op, saved_response ); +} + +#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS +int +handle_vc_bind_response( + LloadConnection *client, + LloadOperation *op, + BerElement *ber ) +{ + BerElement *output; + BerValue matched, diagmsg, creds = BER_BVNULL, controls = BER_BVNULL; + ber_int_t result; + ber_tag_t tag; + ber_len_t len; + int rc = 0; + + tag = ber_scanf( ber, "{emm" /* "}" */, + &result, &matched, &diagmsg ); + if ( tag == LBER_ERROR ) { + rc = -1; + goto done; + } + + tag = ber_peek_tag( ber, &len ); + if ( result == LDAP_PROTOCOL_ERROR ) { + LloadConnection *upstream; + + checked_lock( &op->o_link_mutex ); + upstream = op->o_upstream; + checked_unlock( &op->o_link_mutex ); + if ( upstream ) { + LloadBackend *b; + + CONNECTION_LOCK(upstream); + b = upstream->c_backend; + Debug( LDAP_DEBUG_ANY, "handle_vc_bind_response: " + "VC extended operation not supported on backend %s\n", + b->b_uri.bv_val ); + CONNECTION_UNLOCK(upstream); + } + } + + Debug( LDAP_DEBUG_STATS, "handle_vc_bind_response: " + "received response for bind request msgid=%d by client " + "connid=%lu, result=%d\n", + op->o_client_msgid, op->o_client_connid, result ); + + CONNECTION_LOCK(client); + + if ( tag == LDAP_TAG_EXOP_VERIFY_CREDENTIALS_COOKIE ) { + if ( !BER_BVISNULL( &client->c_vc_cookie ) ) { + ber_memfree( client->c_vc_cookie.bv_val ); + } + tag = ber_scanf( ber, "o", &client->c_vc_cookie ); + if ( tag == LBER_ERROR ) { + rc = -1; + CONNECTION_UNLOCK(client); + goto done; + } + tag = ber_peek_tag( ber, &len ); + } + + if ( tag == LDAP_TAG_EXOP_VERIFY_CREDENTIALS_SCREDS ) { + tag = ber_scanf( ber, "m", &creds ); + if ( tag == LBER_ERROR ) { + rc = -1; + CONNECTION_UNLOCK(client); + goto done; + } + tag = ber_peek_tag( ber, &len ); + } + + if ( tag == LDAP_TAG_EXOP_VERIFY_CREDENTIALS_CONTROLS ) { + tag = ber_scanf( ber, "m", &controls ); + if ( tag == LBER_ERROR ) { + rc = -1; + CONNECTION_UNLOCK(client); + goto done; + } + } + + if ( client->c_state == LLOAD_C_BINDING ) { + switch ( result ) { + case LDAP_SASL_BIND_IN_PROGRESS: + break; + case LDAP_SUCCESS: + default: { + client->c_state = LLOAD_C_READY; + client->c_type = LLOAD_C_OPEN; + client->c_pin_id = 0; + if ( result != LDAP_SUCCESS ) { + ber_memfree( client->c_auth.bv_val ); + BER_BVZERO( &client->c_auth ); + } else if ( !ber_bvstrcasecmp( + &client->c_auth, &lloadd_identity ) ) { + client->c_type = LLOAD_C_PRIVILEGED; + } + if ( !BER_BVISNULL( &client->c_vc_cookie ) ) { + ber_memfree( client->c_vc_cookie.bv_val ); + BER_BVZERO( &client->c_vc_cookie ); + } + if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) { + ber_memfree( client->c_sasl_bind_mech.bv_val ); + BER_BVZERO( &client->c_sasl_bind_mech ); + } + break; + } + } + } else { + assert( client->c_state == LLOAD_C_INVALID || + client->c_state == LLOAD_C_CLOSING ); + } + CONNECTION_UNLOCK(client); + + checked_lock( &client->c_io_mutex ); + output = client->c_pendingber; + if ( output == NULL && (output = ber_alloc()) == NULL ) { + rc = -1; + checked_unlock( &client->c_io_mutex ); + goto done; + } + client->c_pendingber = output; + + rc = ber_printf( output, "t{tit{eOOtO}tO}", LDAP_TAG_MESSAGE, + LDAP_TAG_MSGID, op->o_client_msgid, LDAP_RES_BIND, + result, &matched, &diagmsg, + LDAP_TAG_SASL_RES_CREDS, BER_BV_OPTIONAL( &creds ), + LDAP_TAG_CONTROLS, BER_BV_OPTIONAL( &controls ) ); + + checked_unlock( &client->c_io_mutex ); + if ( rc >= 0 ) { + connection_write_cb( -1, 0, client ); + rc = 0; + } + +done: + OPERATION_UNLINK(op); + ber_free( ber, 1 ); + return rc; +} +#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */ diff --git a/servers/lloadd/client.c b/servers/lloadd/client.c new file mode 100644 index 0000000..1241286 --- /dev/null +++ b/servers/lloadd/client.c @@ -0,0 +1,805 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2022 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 + * . + */ + +#include "portable.h" + +#include +#include +#include +#include +#include + +#include "lutil.h" +#include "lload.h" + +long lload_client_max_pending = 0; + +lload_c_head clients = LDAP_CIRCLEQ_HEAD_INITIALIZER( clients ); + +ldap_pvt_thread_mutex_t clients_mutex; + +static void client_unlink( LloadConnection *upstream ); + +int +request_abandon( LloadConnection *c, LloadOperation *op ) +{ + LloadOperation *request, needle = { .o_client_connid = c->c_connid }; + int rc = LDAP_SUCCESS; + + op->o_res = LLOAD_OP_COMPLETED; + + if ( ber_decode_int( &op->o_request, &needle.o_client_msgid ) ) { + Debug( LDAP_DEBUG_STATS, "request_abandon: " + "connid=%lu msgid=%d invalid integer sent in abandon request\n", + c->c_connid, op->o_client_msgid ); + + OPERATION_UNLINK(op); + CONNECTION_LOCK_DESTROY(c); + return -1; + } + + CONNECTION_LOCK(c); + request = ldap_tavl_find( c->c_ops, &needle, operation_client_cmp ); + if ( !request ) { + Debug( LDAP_DEBUG_STATS, "request_abandon: " + "connid=%lu msgid=%d requests abandon of an operation " + "msgid=%d not being processed anymore\n", + c->c_connid, op->o_client_msgid, needle.o_client_msgid ); + CONNECTION_UNLOCK(c); + goto done; + } else if ( request->o_tag == LDAP_REQ_BIND ) { + /* RFC 4511 states we must not allow Abandon on Binds */ + Debug( LDAP_DEBUG_STATS, "request_abandon: " + "connid=%lu msgid=%d requests abandon of a bind operation " + "msgid=%d\n", + c->c_connid, op->o_client_msgid, needle.o_client_msgid ); + CONNECTION_UNLOCK(c); + goto done; + } + Debug( LDAP_DEBUG_STATS, "request_abandon: " + "connid=%lu msgid=%d abandoning %s msgid=%d\n", + c->c_connid, op->o_client_msgid, + lload_msgtype2str( request->o_tag ), needle.o_client_msgid ); + + if ( c->c_state == LLOAD_C_BINDING ) { + assert(0); + } + + CONNECTION_UNLOCK(c); + operation_abandon( request ); + +done: + OPERATION_UNLINK(op); + return rc; +} + +int +request_process( LloadConnection *client, LloadOperation *op ) +{ + BerElement *output; + LloadConnection *upstream = NULL; + LloadBackend *b = NULL; + ber_int_t msgid; + int res = LDAP_UNAVAILABLE, rc = LDAP_SUCCESS; + char *message = "no connections available"; + enum op_restriction client_restricted; + + if ( lload_control_actions && !BER_BVISNULL( &op->o_ctrls ) ) { + BerElementBuffer copy_berbuf; + BerElement *copy = (BerElement *)©_berbuf; + struct berval control; + + ber_init2( copy, &op->o_ctrls, 0 ); + + while ( ber_skip_element( copy, &control ) == LBER_SEQUENCE ) { + struct restriction_entry *entry, needle = {}; + BerElementBuffer control_berbuf; + BerElement *control_ber = (BerElement *)&control_berbuf; + + ber_init2( control_ber, &control, 0 ); + + if ( ber_skip_element( control_ber, &needle.oid ) == LBER_ERROR ) { + res = LDAP_PROTOCOL_ERROR; + message = "invalid control"; + + operation_send_reject( op, res, message, 1 ); + goto fail; + } + + entry = ldap_tavl_find( + lload_control_actions, &needle, lload_restriction_cmp ); + if ( entry && op->o_restricted < entry->action ) { + op->o_restricted = entry->action; + } + } + } + if ( op->o_restricted < LLOAD_OP_RESTRICTED_WRITE && + lload_write_coherence && + op->o_tag != LDAP_REQ_SEARCH && + op->o_tag != LDAP_REQ_COMPARE ) { + op->o_restricted = LLOAD_OP_RESTRICTED_WRITE; + } + + if ( op->o_restricted == LLOAD_OP_RESTRICTED_REJECT ) { + res = LDAP_UNWILLING_TO_PERFORM; + message = "extended operation or control disallowed"; + + operation_send_reject( op, res, message, 1 ); + goto fail; + } + + CONNECTION_LOCK(client); + client_restricted = client->c_restricted; + if ( client_restricted ) { + if ( client_restricted == LLOAD_OP_RESTRICTED_WRITE && + client->c_restricted_inflight == 0 && + client->c_restricted_at >= 0 && + client->c_restricted_at + lload_write_coherence < + op->o_start.tv_sec ) { + Debug( LDAP_DEBUG_TRACE, "request_process: " + "connid=%lu write coherence to backend '%s' expired\n", + client->c_connid, client->c_backend->b_name.bv_val ); + client->c_backend = NULL; + client_restricted = client->c_restricted = LLOAD_OP_NOT_RESTRICTED; + } + switch ( client_restricted ) { + case LLOAD_OP_NOT_RESTRICTED: + break; + case LLOAD_OP_RESTRICTED_WRITE: + case LLOAD_OP_RESTRICTED_BACKEND: + b = client->c_backend; + assert( b ); + break; + case LLOAD_OP_RESTRICTED_UPSTREAM: + case LLOAD_OP_RESTRICTED_ISOLATE: + upstream = client->c_linked_upstream; + assert( upstream ); + break; + default: + assert(0); + break; + } + } + if ( op->o_restricted < client_restricted ) { + op->o_restricted = client_restricted; + } + CONNECTION_UNLOCK(client); + + if ( upstream ) { + b = upstream->c_backend; + checked_lock( &b->b_mutex ); + if ( !try_upstream( b, NULL, op, upstream, &res, &message ) ) { + upstream = NULL; + } + checked_unlock( &b->b_mutex ); + } else if ( b ) { + backend_select( b, op, &upstream, &res, &message ); + } else { + upstream_select( op, &upstream, &res, &message ); + } + + if ( !upstream ) { + Debug( LDAP_DEBUG_STATS, "request_process: " + "connid=%lu, msgid=%d no available connection found\n", + op->o_client_connid, op->o_client_msgid ); + + operation_send_reject( op, res, message, 1 ); + goto fail; + } + CONNECTION_ASSERT_LOCKED(upstream); + assert_locked( &upstream->c_io_mutex ); + op->o_upstream = upstream; + op->o_upstream_connid = upstream->c_connid; + op->o_res = LLOAD_OP_FAILED; + + /* Was it unlinked in the meantime? No need to send a response since the + * client is dead */ + if ( !IS_ALIVE( op, o_refcnt ) ) { + LloadBackend *b = upstream->c_backend; + + upstream->c_n_ops_executing--; + checked_unlock( &upstream->c_io_mutex ); + CONNECTION_UNLOCK(upstream); + + checked_lock( &b->b_mutex ); + b->b_n_ops_executing--; + checked_unlock( &b->b_mutex ); + + assert( !IS_ALIVE( client, c_live ) ); + checked_lock( &op->o_link_mutex ); + if ( op->o_upstream ) { + op->o_upstream = NULL; + } + checked_unlock( &op->o_link_mutex ); + return -1; + } + + output = upstream->c_pendingber; + if ( output == NULL && (output = ber_alloc()) == NULL ) { + LloadBackend *b = upstream->c_backend; + + upstream->c_n_ops_executing--; + CONNECTION_UNLOCK(upstream); + checked_unlock( &upstream->c_io_mutex ); + + checked_lock( &b->b_mutex ); + b->b_n_ops_executing--; + operation_update_backend_counters( op, b ); + checked_unlock( &b->b_mutex ); + + Debug( LDAP_DEBUG_ANY, "request_process: " + "ber_alloc failed\n" ); + + rc = -1; + goto fail; + } + upstream->c_pendingber = output; + + if ( client_restricted < LLOAD_OP_RESTRICTED_UPSTREAM && + op->o_restricted >= LLOAD_OP_RESTRICTED_UPSTREAM ) { + rc = ldap_tavl_insert( + &upstream->c_linked, client, lload_upstream_entry_cmp, + ldap_avl_dup_error ); + assert( rc == LDAP_SUCCESS ); + } + + op->o_upstream_msgid = msgid = upstream->c_next_msgid++; + rc = ldap_tavl_insert( + &upstream->c_ops, op, operation_upstream_cmp, ldap_avl_dup_error ); + + CONNECTION_UNLOCK(upstream); + + Debug( LDAP_DEBUG_TRACE, "request_process: " + "client connid=%lu added %s msgid=%d to upstream connid=%lu as " + "msgid=%d\n", + op->o_client_connid, lload_msgtype2str( op->o_tag ), + op->o_client_msgid, op->o_upstream_connid, op->o_upstream_msgid ); + assert( rc == LDAP_SUCCESS ); + + lload_stats.counters[LLOAD_STATS_OPS_OTHER].lc_ops_forwarded++; + + if ( op->o_restricted > client_restricted || + client_restricted == LLOAD_OP_RESTRICTED_WRITE ) { + CONNECTION_LOCK(client); + if ( op->o_restricted > client_restricted ) { + client->c_restricted = op->o_restricted; + } + if ( op->o_restricted == LLOAD_OP_RESTRICTED_WRITE ) { + client->c_restricted_inflight++; + } + if ( op->o_restricted >= LLOAD_OP_RESTRICTED_UPSTREAM ) { + if ( client_restricted < LLOAD_OP_RESTRICTED_UPSTREAM ) { + client->c_linked_upstream = upstream; + } + assert( client->c_linked_upstream == upstream ); + client->c_backend = NULL; + } else if ( op->o_restricted >= LLOAD_OP_RESTRICTED_WRITE ) { + if ( client_restricted < LLOAD_OP_RESTRICTED_WRITE ) { + client->c_backend = upstream->c_backend; + } + assert( client->c_backend == upstream->c_backend ); + } + CONNECTION_UNLOCK(client); + } + + if ( (lload_features & LLOAD_FEATURE_PROXYAUTHZ) && + client->c_type != LLOAD_C_PRIVILEGED ) { + CONNECTION_LOCK(client); + Debug( LDAP_DEBUG_TRACE, "request_process: " + "proxying identity %s to upstream\n", + client->c_auth.bv_val ); + ber_printf( output, "t{titOt{{sbO}" /* "}}" */, LDAP_TAG_MESSAGE, + LDAP_TAG_MSGID, msgid, + op->o_tag, &op->o_request, + LDAP_TAG_CONTROLS, + LDAP_CONTROL_PROXY_AUTHZ, 1, &client->c_auth ); + CONNECTION_UNLOCK(client); + + if ( !BER_BVISNULL( &op->o_ctrls ) ) { + ber_write( output, op->o_ctrls.bv_val, op->o_ctrls.bv_len, 0 ); + } + + ber_printf( output, /* "{{" */ "}}" ); + } else { + ber_printf( output, "t{titOtO}", LDAP_TAG_MESSAGE, + LDAP_TAG_MSGID, msgid, + op->o_tag, &op->o_request, + LDAP_TAG_CONTROLS, BER_BV_OPTIONAL( &op->o_ctrls ) ); + } + checked_unlock( &upstream->c_io_mutex ); + + connection_write_cb( -1, 0, upstream ); + return rc; + +fail: + if ( upstream ) { + CONNECTION_LOCK_DESTROY(upstream); + + /* We have not committed any restrictions in the end */ + op->o_restricted = LLOAD_OP_NOT_RESTRICTED; + operation_send_reject( op, LDAP_OTHER, "internal error", 0 ); + } + + OPERATION_UNLINK(op); + if ( rc ) { + CONNECTION_LOCK_DESTROY(client); + } + return rc; +} + +int +handle_one_request( LloadConnection *c ) +{ + BerElement *ber; + LloadOperation *op = NULL; + RequestHandler handler = NULL; + int over_limit = 0; + enum sc_state state; + enum sc_io_state io_state; + + ber = c->c_currentber; + c->c_currentber = NULL; + + CONNECTION_LOCK(c); + op = operation_init( c, ber ); + if ( !op ) { + Debug( LDAP_DEBUG_ANY, "handle_one_request: " + "connid=%lu, operation_init failed\n", + c->c_connid ); + CONNECTION_DESTROY(c); + ber_free( ber, 1 ); + return -1; + } + if ( lload_client_max_pending && + c->c_n_ops_executing >= lload_client_max_pending ) { + over_limit = 1; + } + + /* + * Remember the current state so we don't have to lock again, + * we're only screening whether we can keep going, e.g. noone can change + * state to LLOAD_C_BINDING from under us (would imply a new operation was + * received but that's us), but the opposite is possible - a Bind response + * could be received and processed in the meantime. + */ + state = c->c_state; + CONNECTION_UNLOCK(c); + + switch ( op->o_tag ) { + case LDAP_REQ_UNBIND: + /* There is never a response for this operation */ + op->o_res = LLOAD_OP_COMPLETED; + OPERATION_UNLINK(op); + + Debug( LDAP_DEBUG_STATS, "handle_one_request: " + "received unbind, closing client connid=%lu\n", + c->c_connid ); + CONNECTION_LOCK_DESTROY(c); + return -1; + case LDAP_REQ_BIND: + handler = request_bind; + break; + case LDAP_REQ_ABANDON: + /* We can't send a response to abandon requests even if a bind is + * currently in progress */ + return request_abandon( c, op ); + case LDAP_REQ_EXTENDED: + default: + if ( state == LLOAD_C_BINDING ) { + operation_send_reject( + op, LDAP_PROTOCOL_ERROR, "bind in progress", 0 ); + return LDAP_SUCCESS; + } + if ( over_limit ) { + operation_send_reject( op, LDAP_BUSY, + "pending operation limit reached on this connection", + 0 ); + return LDAP_SUCCESS; + } + + checked_lock( &c->c_io_mutex ); + io_state = c->c_io_state; + checked_unlock( &c->c_io_mutex ); + if ( io_state & LLOAD_C_READ_PAUSE ) { + operation_send_reject( op, LDAP_BUSY, + "writing side backlogged, please keep reading", 0 ); + return LDAP_SUCCESS; + } + + if ( op->o_tag == LDAP_REQ_EXTENDED ) { + handler = request_extended; + } else { + handler = request_process; + } + break; + } + + if ( state == LLOAD_C_CLOSING ) { + operation_send_reject( + op, LDAP_UNAVAILABLE, "connection is shutting down", 0 ); + return LDAP_SUCCESS; + } + + return handler( c, op ); +} + +#ifdef HAVE_TLS +/* + * The connection has a token assigned to it when the callback is set up. + */ +void +client_tls_handshake_cb( evutil_socket_t s, short what, void *arg ) +{ + LloadConnection *c = arg; + epoch_t epoch; + int rc = 0; + + if ( what & EV_TIMEOUT ) { + Debug( LDAP_DEBUG_CONNS, "client_tls_handshake_cb: " + "connid=%lu, timeout reached, destroying\n", + c->c_connid ); + goto fail; + } + + /* + * In case of StartTLS, make sure we flush the response first. + * Also before we try to read anything from the connection, it isn't + * permitted to Abandon a StartTLS exop per RFC4511 anyway. + */ + checked_lock( &c->c_io_mutex ); + if ( c->c_pendingber ) { + checked_unlock( &c->c_io_mutex ); + connection_write_cb( s, what, arg ); + + if ( !IS_ALIVE( c, c_live ) ) { + goto fail; + } + + /* Do we still have data pending? If so, connection_write_cb would + * already have arranged the write callback to trigger again */ + checked_lock( &c->c_io_mutex ); + if ( c->c_pendingber ) { + checked_unlock( &c->c_io_mutex ); + return; + } + } + + rc = ldap_pvt_tls_accept( c->c_sb, LLOAD_TLS_CTX ); + checked_unlock( &c->c_io_mutex ); + if ( rc < 0 ) { + goto fail; + } + + if ( rc == 0 ) { + struct event_base *base = event_get_base( c->c_read_event ); + + /* + * We're finished, replace the callbacks + * + * This is deadlock-safe, since both share the same base - the one + * that's just running us. + */ + CONNECTION_LOCK(c); + event_del( c->c_read_event ); + event_del( c->c_write_event ); + + c->c_read_timeout = NULL; + event_assign( c->c_read_event, base, c->c_fd, EV_READ|EV_PERSIST, + connection_read_cb, c ); + if ( IS_ALIVE( c, c_live ) ) { + event_add( c->c_read_event, c->c_read_timeout ); + } + + event_assign( c->c_write_event, base, c->c_fd, EV_WRITE, + connection_write_cb, c ); + Debug( LDAP_DEBUG_CONNS, "client_tls_handshake_cb: " + "connid=%lu finished\n", + c->c_connid ); + + c->c_is_tls = LLOAD_TLS_ESTABLISHED; + CONNECTION_UNLOCK(c); + return; + } else if ( ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_NEEDS_WRITE, NULL ) ) { + if ( IS_ALIVE( c, c_live ) ) { + CONNECTION_LOCK(c); + event_add( c->c_write_event, lload_write_timeout ); + CONNECTION_UNLOCK(c); + } + Debug( LDAP_DEBUG_CONNS, "client_tls_handshake_cb: " + "connid=%lu need write rc=%d\n", + c->c_connid, rc ); + } + return; + +fail: + Debug( LDAP_DEBUG_CONNS, "client_tls_handshake_cb: " + "connid=%lu failed rc=%d\n", + c->c_connid, rc ); + + assert( c->c_ops == NULL ); + epoch = epoch_join(); + CONNECTION_LOCK_DESTROY(c); + epoch_leave( epoch ); +} +#endif /* HAVE_TLS */ + +LloadConnection * +client_init( + ber_socket_t s, + const char *peername, + struct event_base *base, + int flags ) +{ + LloadConnection *c; + struct event *event; + event_callback_fn read_cb = connection_read_cb, + write_cb = connection_write_cb; + + if ( (c = lload_connection_init( s, peername, flags) ) == NULL ) { + return NULL; + } + + { + ber_len_t max = sockbuf_max_incoming_client; + ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &max ); + } + + c->c_state = LLOAD_C_READY; + + if ( flags & CONN_IS_TLS ) { +#ifdef HAVE_TLS + int rc; + + c->c_is_tls = LLOAD_LDAPS; + + rc = ldap_pvt_tls_accept( c->c_sb, LLOAD_TLS_CTX ); + if ( rc < 0 ) { + Debug( LDAP_DEBUG_CONNS, "client_init: " + "connid=%lu failed initial TLS accept rc=%d\n", + c->c_connid, rc ); + CONNECTION_LOCK(c); + goto fail; + } + + if ( rc ) { + c->c_read_timeout = lload_timeout_net; + read_cb = write_cb = client_tls_handshake_cb; + } +#else /* ! HAVE_TLS */ + assert(0); +#endif /* ! HAVE_TLS */ + } + + event = event_new( base, s, EV_READ|EV_PERSIST, read_cb, c ); + if ( !event ) { + Debug( LDAP_DEBUG_ANY, "client_init: " + "Read event could not be allocated\n" ); + CONNECTION_LOCK(c); + goto fail; + } + c->c_read_event = event; + + event = event_new( base, s, EV_WRITE, write_cb, c ); + if ( !event ) { + Debug( LDAP_DEBUG_ANY, "client_init: " + "Write event could not be allocated\n" ); + CONNECTION_LOCK(c); + goto fail; + } + c->c_write_event = event; + + CONNECTION_LOCK(c); +#ifdef BALANCER_MODULE + if ( lload_monitor_client_subsys ) { + acquire_ref( &c->c_refcnt ); + CONNECTION_UNLOCK(c); + if ( lload_monitor_conn_entry_create( + c, lload_monitor_client_subsys ) ) { + CONNECTION_LOCK(c); + RELEASE_REF( c, c_refcnt, c->c_destroy ); + goto fail; + } + CONNECTION_LOCK(c); + RELEASE_REF( c, c_refcnt, c->c_destroy ); + } +#endif /* BALANCER_MODULE */ + + c->c_destroy = client_destroy; + c->c_unlink = client_unlink; + c->c_pdu_cb = handle_one_request; + + /* We only register the write event when we have data pending */ + event_add( c->c_read_event, c->c_read_timeout ); + + checked_lock( &clients_mutex ); + LDAP_CIRCLEQ_INSERT_TAIL( &clients, c, c_next ); + checked_unlock( &clients_mutex ); + CONNECTION_UNLOCK(c); + + return c; +fail: + if ( !IS_ALIVE( c, c_live ) ) { + /* + * Released while we were unlocked, it's scheduled for destruction + * already + */ + return NULL; + } + + if ( c->c_write_event ) { + event_free( c->c_write_event ); + c->c_write_event = NULL; + } + if ( c->c_read_event ) { + event_free( c->c_read_event ); + c->c_read_event = NULL; + } + + c->c_state = LLOAD_C_INVALID; + c->c_live--; + c->c_refcnt--; + connection_destroy( c ); + return NULL; +} + +void +client_reset( LloadConnection *c ) +{ + TAvlnode *root; + long freed = 0, executing; + LloadConnection *linked_upstream = NULL; + enum op_restriction restricted = c->c_restricted; + + CONNECTION_ASSERT_LOCKED(c); + root = c->c_ops; + c->c_ops = NULL; + executing = c->c_n_ops_executing; + c->c_n_ops_executing = 0; + + if ( !BER_BVISNULL( &c->c_auth ) ) { + ch_free( c->c_auth.bv_val ); + BER_BVZERO( &c->c_auth ); + } + if ( !BER_BVISNULL( &c->c_sasl_bind_mech ) ) { + ch_free( c->c_sasl_bind_mech.bv_val ); + BER_BVZERO( &c->c_sasl_bind_mech ); + } + + if ( restricted && restricted < LLOAD_OP_RESTRICTED_ISOLATE ) { + if ( c->c_backend ) { + assert( c->c_restricted <= LLOAD_OP_RESTRICTED_BACKEND ); + assert( c->c_restricted_inflight == 0 ); + c->c_backend = NULL; + c->c_restricted_at = 0; + } else { + assert( c->c_restricted == LLOAD_OP_RESTRICTED_UPSTREAM ); + assert( c->c_linked_upstream != NULL ); + linked_upstream = c->c_linked_upstream; + c->c_linked_upstream = NULL; + } + } + CONNECTION_UNLOCK(c); + + if ( root ) { + freed = ldap_tavl_free( root, (AVL_FREE)operation_abandon ); + Debug( LDAP_DEBUG_TRACE, "client_reset: " + "dropped %ld operations\n", + freed ); + } + assert( freed == executing ); + + if ( linked_upstream && restricted == LLOAD_OP_RESTRICTED_UPSTREAM ) { + LloadConnection *removed = ldap_tavl_delete( + &linked_upstream->c_linked, c, lload_upstream_entry_cmp ); + assert( removed == c ); + } + + CONNECTION_LOCK(c); + CONNECTION_ASSERT_LOCKED(c); +} + +void +client_unlink( LloadConnection *c ) +{ + enum sc_state state; + struct event *read_event, *write_event; + + Debug( LDAP_DEBUG_CONNS, "client_unlink: " + "removing client connid=%lu\n", + c->c_connid ); + + CONNECTION_ASSERT_LOCKED(c); + assert( c->c_state != LLOAD_C_INVALID ); + assert( c->c_state != LLOAD_C_DYING ); + + state = c->c_state; + c->c_state = LLOAD_C_DYING; + + if ( c->c_restricted == LLOAD_OP_RESTRICTED_ISOLATE ) { + /* Allow upstream connection to be severed in client_reset() */ + c->c_restricted = LLOAD_OP_RESTRICTED_UPSTREAM; + } + + read_event = c->c_read_event; + write_event = c->c_write_event; + CONNECTION_UNLOCK(c); + + if ( read_event ) { + event_del( read_event ); + } + + if ( write_event ) { + event_del( write_event ); + } + + if ( state != LLOAD_C_DYING ) { + checked_lock( &clients_mutex ); + LDAP_CIRCLEQ_REMOVE( &clients, c, c_next ); + checked_unlock( &clients_mutex ); + } + + CONNECTION_LOCK(c); + client_reset( c ); + CONNECTION_ASSERT_LOCKED(c); +} + +void +client_destroy( LloadConnection *c ) +{ + Debug( LDAP_DEBUG_CONNS, "client_destroy: " + "destroying client connid=%lu\n", + c->c_connid ); + + CONNECTION_LOCK(c); + assert( c->c_state == LLOAD_C_DYING ); + +#ifdef BALANCER_MODULE + /* + * Can't do this in client_unlink as that could be run from cn=monitor + * modify callback. + */ + if ( !BER_BVISNULL( &c->c_monitor_dn ) ) { + lload_monitor_conn_unlink( c ); + } +#endif /* BALANCER_MODULE */ + + c->c_state = LLOAD_C_INVALID; + + assert( c->c_ops == NULL ); + + if ( c->c_read_event ) { + event_free( c->c_read_event ); + c->c_read_event = NULL; + } + + if ( c->c_write_event ) { + event_free( c->c_write_event ); + c->c_write_event = NULL; + } + + assert( c->c_refcnt == 0 ); + connection_destroy( c ); +} + +void +clients_destroy( int gentle ) +{ + epoch_t epoch = epoch_join(); + checked_lock( &clients_mutex ); + connections_walk( + &clients_mutex, &clients, lload_connection_close, &gentle ); + checked_unlock( &clients_mutex ); + epoch_leave( epoch ); +} diff --git a/servers/lloadd/config.c b/servers/lloadd/config.c new file mode 100644 index 0000000..ab7a26b --- /dev/null +++ b/servers/lloadd/config.c @@ -0,0 +1,4032 @@ +/* config.c - configuration file handling routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2022 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 + * . + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifndef S_ISREG +#define S_ISREG(m) ( ((m) & _S_IFMT ) == _S_IFREG ) +#endif + +#include "lload.h" +#include "lutil.h" +#include "lutil_ldap.h" +#include "lload-config.h" +#include "../slapd/slap-cfglog.h" + +#ifdef _WIN32 +#define LUTIL_ATOULX lutil_atoullx +#define Z "I" +#else +#define LUTIL_ATOULX lutil_atoulx +#define Z "z" +#endif + +#define ARGS_STEP 512 + +/* + * defaults for various global variables + */ +#ifdef BALANCER_MODULE +char *listeners_list = NULL; +#else /* !BALANCER_MODULE */ +slap_mask_t global_allows = 0; +slap_mask_t global_disallows = 0; +int global_gentlehup = 0; +int global_idletimeout = 0; +char *global_host = NULL; + +char *slapd_pid_file = NULL; +char *slapd_args_file = NULL; +#endif /* !BALANCER_MODULE */ + +static struct timeval timeout_api_tv, timeout_net_tv, + timeout_write_tv = { 10, 0 }; + +lload_features_t lload_features; +int lload_write_coherence = 0; + +ber_len_t sockbuf_max_incoming_client = LLOAD_SB_MAX_INCOMING_CLIENT; +ber_len_t sockbuf_max_incoming_upstream = LLOAD_SB_MAX_INCOMING_UPSTREAM; + +int lload_conn_max_pdus_per_cycle = LLOAD_CONN_MAX_PDUS_PER_CYCLE_DEFAULT; + +struct timeval *lload_timeout_api = NULL; +struct timeval *lload_timeout_net = NULL; +struct timeval *lload_write_timeout = &timeout_write_tv; + +static int fp_getline( FILE *fp, ConfigArgs *c ); +static void fp_getline_init( ConfigArgs *c ); + +static char *strtok_quote( + char *line, + char *sep, + char **quote_ptr, + int *inquote ); + +typedef struct ConfigFile { + struct ConfigFile *c_sibs; + struct ConfigFile *c_kids; + struct berval c_file; + BerVarray c_dseFiles; +} ConfigFile; + +static ConfigFile *cfn; + +static ConfigDriver config_fname; +static ConfigDriver config_generic; +static ConfigDriver config_tier; +static ConfigDriver config_backend; +static ConfigDriver config_bindconf; +static ConfigDriver config_restrict_oid; +#ifdef LDAP_TCP_BUFFER +static ConfigDriver config_tcp_buffer; +#endif /* LDAP_TCP_BUFFER */ +static ConfigDriver config_restrict; +static ConfigDriver config_include; +static ConfigDriver config_feature; +#ifdef HAVE_TLS +static ConfigDriver config_tls_option; +static ConfigDriver config_tls_config; +#endif +#ifdef BALANCER_MODULE +static ConfigDriver config_share_tls_ctx; +static ConfigDriver backend_cf_gen; +#endif /* BALANCER_MODULE */ + +struct slap_bindconf bindconf = {}; +struct berval lloadd_identity = BER_BVNULL; + +enum { + CFG_ACL = 1, + CFG_BACKEND, + CFG_BINDCONF, + CFG_LISTEN, + CFG_LISTEN_URI, + CFG_TLS_RAND, + CFG_TLS_CIPHER, + CFG_TLS_PROTOCOL_MIN, + CFG_TLS_CERT_FILE, + CFG_TLS_CERT_KEY, + CFG_TLS_CA_PATH, + CFG_TLS_CA_FILE, + CFG_TLS_DH_FILE, + CFG_TLS_VERIFY, + CFG_TLS_CRLCHECK, + CFG_TLS_CRL_FILE, + CFG_TLS_SHARE_CTX, + CFG_CONCUR, + CFG_THREADS, + CFG_MIRRORMODE, + CFG_IOTHREADS, + CFG_MAXBUF_CLIENT, + CFG_MAXBUF_UPSTREAM, + CFG_FEATURE, + CFG_THREADQS, + CFG_TLS_ECNAME, + CFG_TLS_CACERT, + CFG_TLS_CERT, + CFG_TLS_KEY, + CFG_RESCOUNT, + CFG_IOTIMEOUT, + CFG_URI, + CFG_NUMCONNS, + CFG_BINDCONNS, + CFG_RETRY, + CFG_MAX_PENDING_OPS, + CFG_MAX_PENDING_CONNS, + CFG_STARTTLS, + CFG_CLIENT_PENDING, + CFG_RESTRICT_EXOP, + CFG_RESTRICT_CONTROL, + CFG_TIER, + CFG_WEIGHT, + + CFG_LAST +}; + +/* alphabetical ordering */ + +static ConfigTable config_back_cf_table[] = { + /* This attr is read-only */ + { "", "", 0, 0, 0, + ARG_MAGIC, + &config_fname, + NULL, NULL, NULL + }, + { "argsfile", "file", 2, 2, 0, + ARG_STRING, + &slapd_args_file, + NULL, NULL, NULL + }, + { "concurrency", "level", 2, 2, 0, + ARG_UINT|ARG_MAGIC|CFG_CONCUR, + &config_generic, + NULL, NULL, NULL + }, + { "tier", "name", 2, 2, 0, + ARG_MAGIC|ARG_STRING|CFG_TIER, + &config_tier, + "( OLcfgBkAt:13.39 " + "NAME 'olcBkLloadTierType' " + "DESC 'Tier type' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + /* conf-file only option */ + { "backend-server", "backend options", 2, 0, 0, + ARG_MAGIC|CFG_BACKEND, + &config_backend, + NULL, NULL, NULL + }, + { "bindconf", "backend credentials", 2, 0, 0, + ARG_MAGIC|CFG_BINDCONF, + &config_bindconf, + "( OLcfgBkAt:13.2 " + "NAME 'olcBkLloadBindconf' " + "DESC 'Backend credentials' " + /* No EQUALITY since this is a compound attribute (and needs + * splitting up anyway - which is a TODO) */ + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + { "gentlehup", "on|off", 2, 2, 0, +#ifdef SIGHUP + ARG_ON_OFF, + &global_gentlehup, +#else + ARG_IGNORED, + NULL, +#endif + NULL, NULL, NULL + }, + { "idletimeout", "timeout", 2, 2, 0, + ARG_UINT, + &global_idletimeout, + "( OLcfgBkAt:13.3 " + "NAME 'olcBkLloadIdleTimeout' " + "DESC 'Connection idle timeout' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", + NULL, NULL + }, + { "include", "file", 2, 2, 0, + ARG_MAGIC, + &config_include, + NULL, NULL, NULL + }, + { "io-threads", "count", 2, 0, 0, + ARG_UINT|ARG_MAGIC|CFG_IOTHREADS, + &config_generic, + "( OLcfgBkAt:13.4 " + "NAME 'olcBkLloadIOThreads' " + "DESC 'I/O thread count' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", + NULL, NULL + }, +#ifdef BALANCER_MODULE + { "listen", "uri list", 2, 2, 0, + ARG_STRING|ARG_MAGIC|CFG_LISTEN, + &config_generic, + NULL, NULL, NULL + }, + { "", "uri", 2, 2, 0, + ARG_MAGIC|CFG_LISTEN_URI, + &config_generic, + "( OLcfgBkAt:13.5 " + "NAME 'olcBkLloadListen' " + "DESC 'A listener adress' " + /* We don't handle adding/removing a value, so no EQUALITY yet */ + "SYNTAX OMsDirectoryString )", + NULL, NULL + }, +#endif /* BALANCER_MODULE */ + { "logfile", "file", 2, 2, 0, + ARG_STRING|ARG_MAGIC|CFG_LOGFILE, + &config_logging, + NULL, NULL, NULL + }, + { "logfile-format", "debug|syslog-utc|syslog-localtime", 2, 2, 0, + ARG_MAGIC|CFG_LOGFILE_FORMAT, + &config_logging, + NULL, NULL, NULL + }, + { "logfile-only", "on|off", 2, 2, 0, + ARG_ON_OFF|ARG_MAGIC|CFG_LOGFILE_ONLY, + &config_logging, + NULL, NULL, NULL + }, + { "logfile-rotate", "max> ] [{read|write}=]size", 0, 0, 0, +#ifdef LDAP_TCP_BUFFER + ARG_MAGIC, + &config_tcp_buffer, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.8 " + "NAME 'olcBkLloadTcpBuffer' " + "DESC 'TCP Buffer size' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + { "threads", "count", 2, 2, 0, + ARG_UINT|ARG_MAGIC|CFG_THREADS, + &config_generic, + NULL, NULL, NULL + }, + { "threadqueues", "count", 2, 2, 0, + ARG_UINT|ARG_MAGIC|CFG_THREADQS, + &config_generic, + NULL, NULL, NULL + }, + { "max_pdus_per_cycle", "count", 2, 2, 0, + ARG_UINT|ARG_MAGIC|CFG_RESCOUNT, + &config_generic, + "( OLcfgBkAt:13.9 " + "NAME 'olcBkLloadMaxPDUPerCycle' " + "DESC 'Maximum number of PDUs to handle in a single cycle' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", + NULL, NULL + }, + { "feature", "name", 2, 0, 0, + ARG_MAGIC|CFG_FEATURE, + &config_feature, + "( OLcfgBkAt:13.10 " + "NAME 'olcBkLloadFeature' " + "DESC 'Lload features enabled' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", + NULL, NULL + }, + { "TLSCACertificate", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CACERT|ARG_BINARY|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.11 " + "NAME 'olcBkLloadTLSCACertificate' " + "DESC 'X.509 certificate, must use ;binary' " + "EQUALITY certificateExactMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 " + "SINGLE-VALUE )", + NULL, NULL + }, + { "TLSCACertificateFile", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CA_FILE|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.12 " + "NAME 'olcBkLloadTLSCACertificateFile' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + { "TLSCACertificatePath", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CA_PATH|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.13 " + "NAME 'olcBkLloadTLSCACertificatePath' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + { "TLSCertificate", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CERT|ARG_BINARY|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.14 " + "NAME 'olcBkLloadTLSCertificate' " + "DESC 'X.509 certificate, must use ;binary' " + "EQUALITY certificateExactMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 " + "SINGLE-VALUE )", + NULL, NULL + }, + { "TLSCertificateFile", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CERT_FILE|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.15 " + "NAME 'olcBkLloadTLSCertificateFile' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + { "TLSCertificateKey", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_KEY|ARG_BINARY|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.16 " + "NAME 'olcBkLloadTLSCertificateKey' " + "DESC 'X.509 privateKey, must use ;binary' " + "EQUALITY privateKeyMatch " + "SYNTAX 1.2.840.113549.1.8.1.1 " + "SINGLE-VALUE )", + NULL, NULL + }, + { "TLSCertificateKeyFile", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CERT_KEY|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.17 " + "NAME 'olcBkLloadTLSCertificateKeyFile' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + { "TLSCipherSuite", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CIPHER|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.18 " + "NAME 'olcBkLloadTLSCipherSuite' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + { "TLSCRLCheck", NULL, 2, 2, 0, +#if defined(HAVE_TLS) && defined(HAVE_OPENSSL) + CFG_TLS_CRLCHECK|ARG_STRING|ARG_MAGIC, + &config_tls_config, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.19 " + "NAME 'olcBkLloadTLSCRLCheck' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + { "TLSCRLFile", NULL, 2, 2, 0, +#if defined(HAVE_GNUTLS) + CFG_TLS_CRL_FILE|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.20 " + "NAME 'olcBkLloadTLSCRLFile' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + { "TLSRandFile", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_RAND|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.21 " + "NAME 'olcBkLloadTLSRandFile' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + { "TLSVerifyClient", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_VERIFY|ARG_STRING|ARG_MAGIC, + &config_tls_config, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.22 " + "NAME 'olcBkLloadVerifyClient' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + { "TLSDHParamFile", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_DH_FILE|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.23 " + "NAME 'olcBkLloadTLSDHParamFile' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + { "TLSECName", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_ECNAME|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.24 " + "NAME 'olcBkLloadTLSECName' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + { "TLSProtocolMin", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_PROTOCOL_MIN|ARG_STRING|ARG_MAGIC, + &config_tls_config, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.25 " + "NAME 'olcBkLloadTLSProtocolMin' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + { "TLSShareSlapdCTX", NULL, 2, 2, 0, +#if defined(HAVE_TLS) && defined(BALANCER_MODULE) + CFG_TLS_SHARE_CTX|ARG_ON_OFF|ARG_MAGIC, + &config_share_tls_ctx, +#else + ARG_IGNORED, + NULL, +#endif + "( OLcfgBkAt:13.33 " + "NAME 'olcBkLloadTLSShareSlapdCTX' " + "DESC 'Share slapd TLS context (all other lloadd TLS options cease to take effect)' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL + }, + { "iotimeout", "ms timeout", 2, 2, 0, + ARG_UINT|ARG_MAGIC|CFG_IOTIMEOUT, + &config_generic, + "( OLcfgBkAt:13.26 " + "NAME 'olcBkLloadIOTimeout' " + "DESC 'I/O timeout threshold in milliseconds' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", + NULL, NULL + }, + { "client_max_pending", NULL, 2, 2, 0, + ARG_MAGIC|ARG_UINT|CFG_CLIENT_PENDING, + &config_generic, + "( OLcfgBkAt:13.35 " + "NAME 'olcBkLloadClientMaxPending' " + "DESC 'Maximum pending operations per client connection' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", + NULL, + { .v_uint = 0 } + }, + { "write_coherence", "seconds", 2, 2, 0, + ARG_INT, + &lload_write_coherence, + "( OLcfgBkAt:13.36 " + "NAME 'olcBkLloadWriteCoherence' " + "DESC 'Keep operations to the same backend after a write' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", + NULL, + { .v_int = 0 } + }, + { "restrict_exop", "OID> op == SLAP_CONFIG_EMIT ) { + switch ( c->type ) { + case CFG_IOTHREADS: + c->value_uint = lload_daemon_threads; + break; + case CFG_LISTEN_URI: { + LloadListener **ll = lloadd_get_listeners(); + struct berval bv = BER_BVNULL; + + for ( ; ll && *ll; ll++ ) { + /* The same url could have spawned several consecutive + * listeners */ + if ( !BER_BVISNULL( &bv ) && + !ber_bvcmp( &bv, &(*ll)->sl_url ) ) { + continue; + } + ber_dupbv( &bv, &(*ll)->sl_url ); + ber_bvarray_add( &c->rvalue_vals, &bv ); + } + } break; + case CFG_MAXBUF_CLIENT: + c->value_uint = sockbuf_max_incoming_client; + break; + case CFG_MAXBUF_UPSTREAM: + c->value_uint = sockbuf_max_incoming_upstream; + break; + case CFG_RESCOUNT: + c->value_uint = lload_conn_max_pdus_per_cycle; + break; + case CFG_IOTIMEOUT: + c->value_uint = 1000 * lload_write_timeout->tv_sec + + lload_write_timeout->tv_usec / 1000; + break; + case CFG_CLIENT_PENDING: + c->value_uint = lload_client_max_pending; + break; + default: + rc = 1; + break; + } + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + /* We only need to worry about deletions to multi-value or MAY + * attributes that belong to the lloadd module - we don't have any at + * the moment */ + return rc; + } + + lload_change.type = LLOAD_CHANGE_MODIFY; + lload_change.object = LLOAD_DAEMON; + + switch ( c->type ) { + case CFG_CONCUR: + ldap_pvt_thread_set_concurrency( c->value_uint ); + break; + case CFG_LISTEN: + if ( lloadd_inited ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "listen directive can only be specified once" ); + ch_free( c->value_string ); + return 1; + } + if ( lloadd_listeners_init( c->value_string ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "could not open one of the listener sockets: %s", + c->value_string ); + ch_free( c->value_string ); + return 1; + } + ch_free( c->value_string ); + break; + case CFG_LISTEN_URI: { + LDAPURLDesc *lud; + LloadListener *l; + + if ( ldap_url_parse_ext( + c->line, &lud, LDAP_PVT_URL_PARSE_DEF_PORT ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "string %s could not be parsed as an LDAP URL", + c->line ); + goto fail; + } + + /* A sanity check, although it will not catch everything */ + if ( ( l = lload_config_check_my_url( c->line, lud ) ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "Load Balancer already configured to listen on %s " + "(while adding %s)", + l->sl_url.bv_val, c->line ); + goto fail; + } + + if ( !lloadd_inited ) { + if ( lload_open_new_listener( c->line, lud ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "could not open a listener for %s", c->line ); + goto fail; + } + } else { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "listener changes will not take effect until restart: " + "%s", + c->line ); + Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg ); + } + } break; + case CFG_THREADS: + if ( c->value_uint < 2 ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "threads=%d smaller than minimum value 2", + c->value_uint ); + goto fail; + + } else if ( c->value_uint > 2 * SLAP_MAX_WORKER_THREADS ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "warning, threads=%d larger than twice the default " + "(2*%d=%d); YMMV", + c->value_uint, SLAP_MAX_WORKER_THREADS, + 2 * SLAP_MAX_WORKER_THREADS ); + Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg ); + } + if ( slapMode & SLAP_SERVER_MODE ) + ldap_pvt_thread_pool_maxthreads( + &connection_pool, c->value_uint ); + connection_pool_max = c->value_uint; /* save for reference */ + break; + + case CFG_THREADQS: + if ( c->value_uint < 1 ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "threadqueues=%d smaller than minimum value 1", + c->value_uint ); + goto fail; + } + if ( slapMode & SLAP_SERVER_MODE ) + ldap_pvt_thread_pool_queues( &connection_pool, c->value_uint ); + connection_pool_queues = c->value_uint; /* save for reference */ + break; + + case CFG_IOTHREADS: { + int mask = 0; + /* use a power of two */ + while ( c->value_uint > 1 ) { + c->value_uint >>= 1; + mask <<= 1; + mask |= 1; + } + if ( !lloadd_inited ) { + lload_daemon_mask = mask; + lload_daemon_threads = mask + 1; + flag = LLOAD_DAEMON_MOD_THREADS; + } else { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "io thread changes will not take effect until " + "restart" ); + Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg ); + } + } break; + + case CFG_RESCOUNT: + lload_conn_max_pdus_per_cycle = c->value_uint; + break; + + case CFG_IOTIMEOUT: + if ( c->value_uint > 0 ) { + timeout_write_tv.tv_sec = c->value_uint / 1000; + timeout_write_tv.tv_usec = 1000 * ( c->value_uint % 1000 ); + lload_write_timeout = &timeout_write_tv; + } else { + lload_write_timeout = NULL; + } + break; + case CFG_MAXBUF_CLIENT: + sockbuf_max_incoming_client = c->value_uint; + break; + case CFG_MAXBUF_UPSTREAM: + sockbuf_max_incoming_upstream = c->value_uint; + break; + case CFG_CLIENT_PENDING: + lload_client_max_pending = c->value_uint; + break; + default: + Debug( LDAP_DEBUG_ANY, "%s: unknown CFG_TYPE %d\n", + c->log, c->type ); + return 1; + } + + lload_change.flags.daemon |= flag; + + return 0; + +fail: + if ( lload_change.type == LLOAD_CHANGE_ADD ) { + /* Abort the ADD */ + lload_change.type = LLOAD_CHANGE_DEL; + } + + Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg ); + return 1; +} + +static int +lload_backend_finish( ConfigArgs *ca ) +{ + LloadBackend *b = ca->ca_private; + + if ( ca->reply.err != LDAP_SUCCESS ) { + /* Not reached since cleanup is only called on success */ + goto fail; + } + + if ( b->b_numconns <= 0 || b->b_numbindconns <= 0 ) { + Debug( LDAP_DEBUG_ANY, "lload_backend_finish: " + "invalid connection pool configuration\n" ); + goto fail; + } + + if ( b->b_retry_timeout < 0 ) { + Debug( LDAP_DEBUG_ANY, "lload_backend_finish: " + "invalid retry timeout configuration\n" ); + goto fail; + } + + b->b_retry_tv.tv_sec = b->b_retry_timeout / 1000; + b->b_retry_tv.tv_usec = ( b->b_retry_timeout % 1000 ) * 1000; + + /* daemon_base is only allocated after initial configuration happens, those + * events are allocated on startup, we only deal with online Adds */ + if ( !b->b_retry_event && daemon_base ) { + struct event *event; + assert( CONFIG_ONLINE_ADD( ca ) ); + event = evtimer_new( daemon_base, backend_connect, b ); + if ( !event ) { + Debug( LDAP_DEBUG_ANY, "lload_backend_finish: " + "failed to allocate retry event\n" ); + goto fail; + } + b->b_retry_event = event; + } + + if ( BER_BVISEMPTY( &b->b_name ) ) { + struct berval bv; + LloadBackend *b2; + int i = 1; + + LDAP_CIRCLEQ_FOREACH ( b2, &b->b_tier->t_backends, b_next ) { + i++; + } + + bv.bv_val = ca->cr_msg; + bv.bv_len = + snprintf( ca->cr_msg, sizeof(ca->cr_msg), "server %d", i ); + + ber_dupbv( &b->b_name, &bv ); + } + + if ( b->b_tier->t_type.tier_add_backend( b->b_tier, b ) ) { + goto fail; + } + + return LDAP_SUCCESS; + +fail: + if ( lload_change.type == LLOAD_CHANGE_ADD ) { + /* Abort the ADD */ + lload_change.type = LLOAD_CHANGE_DEL; + } + + lload_backend_destroy( b ); + return -1; +} + +static int +backend_config_url( LloadBackend *b, struct berval *uri ) +{ + LDAPURLDesc *lud = NULL; + char *host = NULL; + int rc, proto, tls = b->b_tls_conf; + + /* Effect no changes until we've checked everything */ + + rc = ldap_url_parse_ext( uri->bv_val, &lud, LDAP_PVT_URL_PARSE_DEF_PORT ); + if ( rc != LDAP_URL_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "backend_config_url: " + "listen URL \"%s\" parse error=%d\n", + uri->bv_val, rc ); + return -1; + } + + if ( ldap_pvt_url_scheme2tls( lud->lud_scheme ) ) { +#ifdef HAVE_TLS + /* Specifying ldaps:// overrides starttls= settings */ + tls = LLOAD_LDAPS; +#else /* ! HAVE_TLS */ + + Debug( LDAP_DEBUG_ANY, "backend_config_url: " + "TLS not supported (%s)\n", + uri->bv_val ); + rc = -1; + goto done; +#endif /* ! HAVE_TLS */ + } + + proto = ldap_pvt_url_scheme2proto( lud->lud_scheme ); + if ( proto == LDAP_PROTO_IPC ) { +#ifdef LDAP_PF_LOCAL + if ( lud->lud_host == NULL || lud->lud_host[0] == '\0' ) { + host = LDAPI_SOCK; + } +#else /* ! LDAP_PF_LOCAL */ + + Debug( LDAP_DEBUG_ANY, "backend_config_url: " + "URL scheme not supported: %s", + url ); + rc = -1; + goto done; +#endif /* ! LDAP_PF_LOCAL */ + } else { + if ( lud->lud_host == NULL || lud->lud_host[0] == '\0' ) { + Debug( LDAP_DEBUG_ANY, "backend_config_url: " + "backend url missing hostname: '%s'\n", + uri->bv_val ); + rc = -1; + goto done; + } + } + if ( !host ) { + host = lud->lud_host; + } + + if ( b->b_host ) { + ch_free( b->b_host ); + } + + b->b_proto = proto; + b->b_tls = tls; + b->b_port = lud->lud_port; + b->b_host = ch_strdup( host ); + +done: + ldap_free_urldesc( lud ); + return rc; +} + +static int +config_backend( ConfigArgs *c ) +{ + LloadBackend *b; + LloadTier *tier; + int i, rc = 0; + + tier = LDAP_STAILQ_LAST( &tiers, LloadTier, t_next ); + if ( !tier ) { + Debug( LDAP_DEBUG_ANY, "config_backend: " + "no tier configured yet\n" ); + return -1; + } + + /* FIXME: maybe tier_add_backend could allocate it? */ + b = lload_backend_new(); + b->b_tier = tier; + + for ( i = 1; i < c->argc; i++ ) { + if ( lload_backend_parse( c->argv[i], b ) ) { + if ( !tier->t_type.tier_backend_config || + tier->t_type.tier_backend_config( tier, b, c->argv[i] ) ) { + Debug( LDAP_DEBUG_ANY, "config_backend: " + "error parsing backend configuration item '%s'\n", + c->argv[i] ); + return -1; + } + } + } + + if ( BER_BVISNULL( &b->b_uri ) ) { + Debug( LDAP_DEBUG_ANY, "config_backend: " + "backend address not specified\n" ); + rc = -1; + goto done; + } + + if ( backend_config_url( b, &b->b_uri ) ) { + rc = -1; + goto done; + } + + c->ca_private = b; + rc = lload_backend_finish( c ); +done: + if ( rc ) { + ch_free( b ); + } + return rc; +} + +static int +config_bindconf( ConfigArgs *c ) +{ + int i; + + if ( c->op == SLAP_CONFIG_EMIT ) { + struct berval bv; + + lload_bindconf_unparse( &bindconf, &bv ); + + for ( i = 0; isspace( (unsigned char)bv.bv_val[i] ); i++ ) + /* count spaces */; + + if ( i ) { + bv.bv_len -= i; + AC_MEMCPY( bv.bv_val, &bv.bv_val[i], bv.bv_len + 1 ); + } + + value_add_one( &c->rvalue_vals, &bv ); + ber_memfree( bv.bv_val ); + return LDAP_SUCCESS; + } else if ( c->op == LDAP_MOD_DELETE ) { + /* It's a MUST single-valued attribute, noop for now */ + lload_bindconf_free( &bindconf ); + return LDAP_SUCCESS; + } + + lload_change.type = LLOAD_CHANGE_MODIFY; + lload_change.object = LLOAD_DAEMON; + lload_change.flags.daemon |= LLOAD_DAEMON_MOD_BINDCONF; + + for ( i = 1; i < c->argc; i++ ) { + if ( lload_bindconf_parse( c->argv[i], &bindconf ) ) { + Debug( LDAP_DEBUG_ANY, "config_bindconf: " + "error parsing backend configuration item '%s'\n", + c->argv[i] ); + return -1; + } + } + + if ( bindconf.sb_method == LDAP_AUTH_SASL ) { +#ifndef HAVE_CYRUS_SASL + Debug( LDAP_DEBUG_ANY, "config_bindconf: " + "no sasl support available\n" ); + return -1; +#endif + } + + if ( !BER_BVISNULL( &bindconf.sb_authzId ) ) { + ber_bvreplace( &lloadd_identity, &bindconf.sb_authzId ); + } else if ( !BER_BVISNULL( &bindconf.sb_authcId ) ) { + ber_bvreplace( &lloadd_identity, &bindconf.sb_authcId ); + } else if ( !BER_BVISNULL( &bindconf.sb_binddn ) ) { + char *ptr; + + lloadd_identity.bv_len = STRLENOF("dn:") + bindconf.sb_binddn.bv_len; + lloadd_identity.bv_val = ch_realloc( + lloadd_identity.bv_val, lloadd_identity.bv_len + 1 ); + + ptr = lutil_strcopy( lloadd_identity.bv_val, "dn:" ); + ptr = lutil_strncopy( + ptr, bindconf.sb_binddn.bv_val, bindconf.sb_binddn.bv_len ); + *ptr = '\0'; + } + + if ( bindconf.sb_timeout_api ) { + timeout_api_tv.tv_sec = bindconf.sb_timeout_api; + lload_timeout_api = &timeout_api_tv; + if ( lload_timeout_event ) { + event_add( lload_timeout_event, lload_timeout_api ); + } + } else { + lload_timeout_api = NULL; + if ( lload_timeout_event ) { + event_del( lload_timeout_event ); + } + } + + if ( bindconf.sb_timeout_net ) { + timeout_net_tv.tv_sec = bindconf.sb_timeout_net; + lload_timeout_net = &timeout_net_tv; + } else { + lload_timeout_net = NULL; + } + +#ifdef HAVE_TLS + if ( bindconf.sb_tls_do_init ) { + lload_bindconf_tls_set( &bindconf, lload_tls_backend_ld ); + } +#endif /* HAVE_TLS */ + return 0; +} + +#ifndef BALANCER_MODULE +char * +oidm_find( char *oid ) +{ + if ( OID_LEADCHAR( *oid ) ) { + return oid; + } + Debug( LDAP_DEBUG_ANY, "oidm_find: " + "full OID parsing only available when compiled as a module\n" ); + return NULL; +} +#endif /* !BALANCER_MODULE */ + +static struct { + const char *name; + enum op_restriction action; +} restrictopts[] = { + { "ignore", LLOAD_OP_NOT_RESTRICTED }, + { "write", LLOAD_OP_RESTRICTED_WRITE }, + { "backend", LLOAD_OP_RESTRICTED_BACKEND }, + { "connection", LLOAD_OP_RESTRICTED_UPSTREAM }, + { "isolate", LLOAD_OP_RESTRICTED_ISOLATE }, + { "reject", LLOAD_OP_RESTRICTED_REJECT }, + { NULL } +}; + +void +lload_restriction_free( struct restriction_entry *restriction ) +{ + ch_free( restriction->oid.bv_val ); + ch_free( restriction ); +} + +static int +config_restrict_oid( ConfigArgs *c ) +{ + TAvlnode *node = NULL, **root = ( c->type == CFG_RESTRICT_EXOP ) ? + &lload_exop_actions : + &lload_control_actions; + struct restriction_entry *entry = NULL; + char *parsed_oid; + int i, rc = -1; + + if ( c->op == SLAP_CONFIG_EMIT ) { + struct berval bv = { .bv_val = c->cr_msg }; + + if ( c->type == CFG_RESTRICT_EXOP && lload_default_exop_action ) { + bv.bv_len = snprintf( bv.bv_val, sizeof(c->cr_msg), "1.1 %s", + restrictopts[lload_default_exop_action].name ); + value_add_one( &c->rvalue_vals, &bv ); + } + for ( node = ldap_tavl_end( *root, TAVL_DIR_LEFT ); + node; + node = ldap_tavl_next( node, TAVL_DIR_RIGHT ) ) { + entry = node->avl_data; + + bv.bv_len = snprintf( bv.bv_val, sizeof(c->cr_msg), "%s %s", + entry->oid.bv_val, restrictopts[entry->action].name ); + value_add_one( &c->rvalue_vals, &bv ); + } + + return LDAP_SUCCESS; + + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( !c->line ) { + ldap_tavl_free( *root, (AVL_FREE)lload_restriction_free ); + *root = NULL; + if ( c->type == CFG_RESTRICT_EXOP ) { + lload_default_exop_action = LLOAD_OP_NOT_RESTRICTED; + } + rc = LDAP_SUCCESS; + } else { + struct restriction_entry needle; + + parsed_oid = strchr( c->line, ' ' ); + if ( !parsed_oid ) { + return rc; + } + + memcpy( c->cr_msg, c->line, parsed_oid - c->line ); + c->cr_msg[parsed_oid - c->line] = '\0'; + + needle.oid.bv_val = oidm_find( c->cr_msg ); + needle.oid.bv_len = strlen( needle.oid.bv_val ); + + if ( !needle.oid.bv_val ) { + return rc; + } else if ( c->type == CFG_RESTRICT_EXOP && + !strcmp( needle.oid.bv_val, "1.1" ) ) { + lload_default_exop_action = LLOAD_OP_NOT_RESTRICTED; + } else { + /* back-config should have checked we have this value */ + entry = ldap_tavl_delete( root, &needle, + lload_restriction_cmp ); + assert( entry != NULL ); + } + rc = LDAP_SUCCESS; + } + return rc; + } + + parsed_oid = oidm_find( c->argv[1] ); + if ( !parsed_oid ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), "Could not parse oid %s", + c->argv[1] ); + goto done; + } + + for ( i = 0; restrictopts[i].name; i++ ) { + if ( !strcasecmp( c->argv[2], restrictopts[i].name ) ) { + break; + } + } + + if ( !restrictopts[i].name ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), "Could not parse action %s", + c->argv[2] ); + goto done; + } + + if ( !strcmp( parsed_oid, "1.1" ) ) { + if ( lload_default_exop_action ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), "Default already set" ); + goto done; + } else { + lload_default_exop_action = i; + } + } + + entry = ch_malloc( sizeof(struct restriction_entry) ); + /* Copy only if a reference to argv[1] was returned */ + ber_str2bv( parsed_oid, 0, parsed_oid == c->argv[1], &entry->oid ); + entry->action = i; + + if ( ldap_tavl_insert( root, entry, lload_restriction_cmp, + ldap_avl_dup_error ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "%s with OID %s already restricted", + c->type == CFG_RESTRICT_EXOP ? "Extended operation" : "Control", + c->argv[1] ); + goto done; + } + + rc = LDAP_SUCCESS; +done: + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg ); + if ( parsed_oid ) ch_free( parsed_oid ); + if ( entry ) ch_free( entry ); + } + + return rc; +} + +static int +config_tier( ConfigArgs *c ) +{ + int rc = LDAP_SUCCESS; + struct lload_tier_type *tier_impl; + LloadTier *tier = c->ca_private; + struct berval bv; + int i = 1; + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch ( c->type ) { + case CFG_TIER: + c->value_string = ch_strdup( tier->t_type.tier_name ); + break; + default: + goto fail; + break; + } + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( lload_change.type != LLOAD_CHANGE_DEL ) { + /* + * TODO: Shouldn't really happen while this attribute is in the + * RDN, but we don't enforce it yet. + * + * How would we go about changing the backend type if we ever supported that? + */ + goto fail; + } + return rc; + } + + if ( CONFIG_ONLINE_ADD( c ) ) { + assert( tier ); + lload_change.target = tier; + ch_free( c->value_string ); + return rc; + } + + tier_impl = lload_tier_find( c->value_string ); + ch_free( c->value_string ); + if ( !tier_impl ) { + goto fail; + } + tier = tier_impl->tier_init(); + if ( !tier ) { + goto fail; + } + + lload_change.target = tier; + + if ( LDAP_STAILQ_EMPTY( &tiers ) ) { + LDAP_STAILQ_INSERT_HEAD( &tiers, tier, t_next ); + } else { + LloadTier *tier2; + LDAP_STAILQ_FOREACH ( tier2, &tiers, t_next ) { + i++; + } + LDAP_STAILQ_INSERT_TAIL( &tiers, tier, t_next ); + } + + bv.bv_val = c->cr_msg; + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "tier %d", i ); + ber_dupbv( &tier->t_name, &bv ); + + return rc; + +fail: + if ( lload_change.type == LLOAD_CHANGE_ADD ) { + /* Abort the ADD */ + lload_change.type = LLOAD_CHANGE_DEL; + } + return 1; +} + +static int +config_fname( ConfigArgs *c ) +{ + return 0; +} + +/* + * [listener=] [{read|write}=] + */ + +#ifdef LDAP_TCP_BUFFER +static BerVarray tcp_buffer; +static int tcp_buffer_num; + +#define SLAP_TCP_RMEM ( 0x1U ) +#define SLAP_TCP_WMEM ( 0x2U ) + +static int +tcp_buffer_parse( + struct berval *val, + int argc, + char **argv, + int *size, + int *rw, + LloadListener **l ) +{ + int i, rc = LDAP_SUCCESS; + LDAPURLDesc *lud = NULL; + char *ptr; + + if ( val != NULL && argv == NULL ) { + char *s = val->bv_val; + + argv = ldap_str2charray( s, " \t" ); + if ( argv == NULL ) { + return LDAP_OTHER; + } + } + + i = 0; + if ( strncasecmp( argv[i], "listener=", STRLENOF("listener=") ) == 0 ) { + char *url = argv[i] + STRLENOF("listener="); + + if ( ldap_url_parse_ext( url, &lud, LDAP_PVT_URL_PARSE_DEF_PORT ) ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + *l = lload_config_check_my_url( url, lud ); + if ( *l == NULL ) { + rc = LDAP_NO_SUCH_ATTRIBUTE; + goto done; + } + + i++; + } + + ptr = argv[i]; + if ( strncasecmp( ptr, "read=", STRLENOF("read=") ) == 0 ) { + *rw |= SLAP_TCP_RMEM; + ptr += STRLENOF("read="); + + } else if ( strncasecmp( ptr, "write=", STRLENOF("write=") ) == 0 ) { + *rw |= SLAP_TCP_WMEM; + ptr += STRLENOF("write="); + + } else { + *rw |= ( SLAP_TCP_RMEM | SLAP_TCP_WMEM ); + } + + /* accept any base */ + if ( lutil_atoix( size, ptr, 0 ) ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + +done:; + if ( val != NULL && argv != NULL ) { + ldap_charray_free( argv ); + } + + if ( lud != NULL ) { + ldap_free_urldesc( lud ); + } + + return rc; +} + +#ifdef BALANCER_MODULE +static int +tcp_buffer_delete_one( struct berval *val ) +{ + int rc = 0; + int size = -1, rw = 0; + LloadListener *l = NULL; + + rc = tcp_buffer_parse( val, 0, NULL, &size, &rw, &l ); + if ( rc != 0 ) { + return rc; + } + + if ( l != NULL ) { + int i; + LloadListener **ll = lloadd_get_listeners(); + + for ( i = 0; ll[i] != NULL; i++ ) { + if ( ll[i] == l ) break; + } + + if ( ll[i] == NULL ) { + return LDAP_NO_SUCH_ATTRIBUTE; + } + + if ( rw & SLAP_TCP_RMEM ) l->sl_tcp_rmem = -1; + if ( rw & SLAP_TCP_WMEM ) l->sl_tcp_wmem = -1; + + for ( i++; ll[i] != NULL && bvmatch( &l->sl_url, &ll[i]->sl_url ); + i++ ) { + if ( rw & SLAP_TCP_RMEM ) ll[i]->sl_tcp_rmem = -1; + if ( rw & SLAP_TCP_WMEM ) ll[i]->sl_tcp_wmem = -1; + } + + } else { + /* NOTE: this affects listeners without a specific setting, + * does not reset all listeners. If a listener without + * specific settings was assigned a buffer because of + * a global setting, it will not be reset. In any case, + * buffer changes will only take place at restart. */ + if ( rw & SLAP_TCP_RMEM ) slapd_tcp_rmem = -1; + if ( rw & SLAP_TCP_WMEM ) slapd_tcp_wmem = -1; + } + + return rc; +} + +static int +tcp_buffer_delete( BerVarray vals ) +{ + int i; + + for ( i = 0; !BER_BVISNULL( &vals[i] ); i++ ) { + tcp_buffer_delete_one( &vals[i] ); + } + + return 0; +} +#endif /* BALANCER_MODULE */ + +static int +tcp_buffer_unparse( int size, int rw, LloadListener *l, struct berval *val ) +{ + char buf[sizeof("2147483648")], *ptr; + + /* unparse for later use */ + val->bv_len = snprintf( buf, sizeof(buf), "%d", size ); + if ( l != NULL ) { + val->bv_len += STRLENOF( "listener=" + " " ) + + l->sl_url.bv_len; + } + + if ( rw != ( SLAP_TCP_RMEM | SLAP_TCP_WMEM ) ) { + if ( rw & SLAP_TCP_RMEM ) { + val->bv_len += STRLENOF("read="); + } else if ( rw & SLAP_TCP_WMEM ) { + val->bv_len += STRLENOF("write="); + } + } + + val->bv_val = SLAP_MALLOC( val->bv_len + 1 ); + + ptr = val->bv_val; + + if ( l != NULL ) { + ptr = lutil_strcopy( ptr, "listener=" ); + ptr = lutil_strncopy( ptr, l->sl_url.bv_val, l->sl_url.bv_len ); + *ptr++ = ' '; + } + + if ( rw != ( SLAP_TCP_RMEM | SLAP_TCP_WMEM ) ) { + if ( rw & SLAP_TCP_RMEM ) { + ptr = lutil_strcopy( ptr, "read=" ); + } else if ( rw & SLAP_TCP_WMEM ) { + ptr = lutil_strcopy( ptr, "write=" ); + } + } + + ptr = lutil_strcopy( ptr, buf ); + *ptr = '\0'; + + assert( val->bv_val + val->bv_len == ptr ); + + return LDAP_SUCCESS; +} + +static int +tcp_buffer_add_one( int argc, char **argv ) +{ + int rc = 0; + int size = -1, rw = 0; + LloadListener *l = NULL; + + struct berval val; + + /* parse */ + rc = tcp_buffer_parse( NULL, argc, argv, &size, &rw, &l ); + if ( rc != 0 ) { + return rc; + } + + /* unparse for later use */ + rc = tcp_buffer_unparse( size, rw, l, &val ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + /* use parsed values */ + if ( l != NULL ) { + int i; + LloadListener **ll = lloadd_get_listeners(); + + for ( i = 0; ll[i] != NULL; i++ ) { + if ( ll[i] == l ) break; + } + + if ( ll[i] == NULL ) { + return LDAP_NO_SUCH_ATTRIBUTE; + } + + /* buffer only applies to TCP listeners; + * we do not do any check here, and delegate them + * to setsockopt(2) */ + if ( rw & SLAP_TCP_RMEM ) l->sl_tcp_rmem = size; + if ( rw & SLAP_TCP_WMEM ) l->sl_tcp_wmem = size; + + for ( i++; ll[i] != NULL && bvmatch( &l->sl_url, &ll[i]->sl_url ); + i++ ) { + if ( rw & SLAP_TCP_RMEM ) ll[i]->sl_tcp_rmem = size; + if ( rw & SLAP_TCP_WMEM ) ll[i]->sl_tcp_wmem = size; + } + + } else { + /* NOTE: this affects listeners without a specific setting, + * does not set all listeners */ + if ( rw & SLAP_TCP_RMEM ) slapd_tcp_rmem = size; + if ( rw & SLAP_TCP_WMEM ) slapd_tcp_wmem = size; + } + + tcp_buffer = SLAP_REALLOC( + tcp_buffer, sizeof(struct berval) * ( tcp_buffer_num + 2 ) ); + /* append */ + tcp_buffer[tcp_buffer_num] = val; + + tcp_buffer_num++; + BER_BVZERO( &tcp_buffer[tcp_buffer_num] ); + + return rc; +} + +static int +config_tcp_buffer( ConfigArgs *c ) +{ + int rc = LDAP_SUCCESS; + +#ifdef BALANCER_MODULE + if ( c->op == SLAP_CONFIG_EMIT ) { + if ( tcp_buffer == NULL || BER_BVISNULL( &tcp_buffer[0] ) ) { + return 1; + } + value_add( &c->rvalue_vals, tcp_buffer ); + value_add( &c->rvalue_nvals, tcp_buffer ); + + return 0; + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( !c->line ) { + tcp_buffer_delete( tcp_buffer ); + ber_bvarray_free( tcp_buffer ); + tcp_buffer = NULL; + tcp_buffer_num = 0; + + } else { + int size = -1, rw = 0; + LloadListener *l = NULL; + + struct berval val = BER_BVNULL; + + int i; + + if ( tcp_buffer_num == 0 ) { + return 1; + } + + /* parse */ + rc = tcp_buffer_parse( + NULL, c->argc - 1, &c->argv[1], &size, &rw, &l ); + if ( rc != 0 ) { + return 1; + } + + /* unparse for later use */ + rc = tcp_buffer_unparse( size, rw, l, &val ); + if ( rc != LDAP_SUCCESS ) { + return 1; + } + + for ( i = 0; !BER_BVISNULL( &tcp_buffer[i] ); i++ ) { + if ( bvmatch( &tcp_buffer[i], &val ) ) { + break; + } + } + + if ( BER_BVISNULL( &tcp_buffer[i] ) ) { + /* not found */ + rc = 1; + goto done; + } + + tcp_buffer_delete_one( &tcp_buffer[i] ); + ber_memfree( tcp_buffer[i].bv_val ); + for ( ; i < tcp_buffer_num; i++ ) { + tcp_buffer[i] = tcp_buffer[i + 1]; + } + tcp_buffer_num--; + +done:; + if ( !BER_BVISNULL( &val ) ) { + SLAP_FREE(val.bv_val); + } + } + + return rc; + } +#endif /* BALANCER_MODULE */ + + rc = tcp_buffer_add_one( c->argc - 1, &c->argv[1] ); + if ( rc ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), "<%s> unable to add value #%d", + c->argv[0], tcp_buffer_num ); + Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg ); + return 1; + } + + return 0; +} +#endif /* LDAP_TCP_BUFFER */ + +static int +config_restrict( ConfigArgs *c ) +{ + slap_mask_t restrictops = 0; + int i; + slap_verbmasks restrictable_ops[] = { + { BER_BVC("bind"), SLAP_RESTRICT_OP_BIND }, + { BER_BVC("add"), SLAP_RESTRICT_OP_ADD }, + { BER_BVC("modify"), SLAP_RESTRICT_OP_MODIFY }, + { BER_BVC("rename"), SLAP_RESTRICT_OP_RENAME }, + { BER_BVC("modrdn"), 0 }, + { BER_BVC("delete"), SLAP_RESTRICT_OP_DELETE }, + { BER_BVC("search"), SLAP_RESTRICT_OP_SEARCH }, + { BER_BVC("compare"), SLAP_RESTRICT_OP_COMPARE }, + { BER_BVC("read"), SLAP_RESTRICT_OP_READS }, + { BER_BVC("write"), SLAP_RESTRICT_OP_WRITES }, + { BER_BVC("extended"), SLAP_RESTRICT_OP_EXTENDED }, + { BER_BVC("extended=" LDAP_EXOP_START_TLS), SLAP_RESTRICT_EXOP_START_TLS }, + { BER_BVC("extended=" LDAP_EXOP_MODIFY_PASSWD), SLAP_RESTRICT_EXOP_MODIFY_PASSWD }, + { BER_BVC("extended=" LDAP_EXOP_X_WHO_AM_I), SLAP_RESTRICT_EXOP_WHOAMI }, + { BER_BVC("extended=" LDAP_EXOP_X_CANCEL), SLAP_RESTRICT_EXOP_CANCEL }, + { BER_BVC("all"), SLAP_RESTRICT_OP_ALL }, + { BER_BVNULL, 0 } + }; + + i = verbs_to_mask( c->argc, c->argv, restrictable_ops, &restrictops ); + if ( i ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), "<%s> unknown operation", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s %s\n", + c->log, c->cr_msg, c->argv[i] ); + return 1; + } + if ( restrictops & SLAP_RESTRICT_OP_EXTENDED ) + restrictops &= ~SLAP_RESTRICT_EXOP_MASK; + return 0; +} + +static int +config_include( ConfigArgs *c ) +{ + int savelineno = c->lineno; + int rc; + ConfigFile *cf; + ConfigFile *cfsave = cfn; + ConfigFile *cf2 = NULL; + + /* Leftover from RE23. No dynamic config for include files */ + if ( c->op == SLAP_CONFIG_EMIT || c->op == LDAP_MOD_DELETE ) return 1; + + cf = ch_calloc( 1, sizeof(ConfigFile) ); + if ( cfn->c_kids ) { + for ( cf2 = cfn->c_kids; cf2 && cf2->c_sibs; cf2 = cf2->c_sibs ) + /* empty */; + cf2->c_sibs = cf; + } else { + cfn->c_kids = cf; + } + cfn = cf; + ber_str2bv( c->argv[1], 0, 1, &cf->c_file ); + rc = lload_read_config_file( + c->argv[1], c->depth + 1, c, config_back_cf_table ); + c->lineno = savelineno - 1; + cfn = cfsave; + if ( rc ) { + if ( cf2 ) + cf2->c_sibs = NULL; + else + cfn->c_kids = NULL; + ch_free( cf->c_file.bv_val ); + ch_free( cf ); + } else { + c->ca_private = cf; + } + return rc; +} + +static int +config_feature( ConfigArgs *c ) +{ + slap_verbmasks features[] = { +#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS + { BER_BVC("vc"), LLOAD_FEATURE_VC }, +#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */ + { BER_BVC("proxyauthz"), LLOAD_FEATURE_PROXYAUTHZ }, + { BER_BVC("read_pause"), LLOAD_FEATURE_PAUSE }, + { BER_BVNULL, 0 } + }; + slap_mask_t mask = 0; + int i; + + if ( c->op == SLAP_CONFIG_EMIT ) { + return mask_to_verbs( features, lload_features, &c->rvalue_vals ); + } + + lload_change.type = LLOAD_CHANGE_MODIFY; + lload_change.object = LLOAD_DAEMON; + lload_change.flags.daemon |= LLOAD_DAEMON_MOD_FEATURES; + if ( !lload_change.target ) { + lload_change.target = (void *)(uintptr_t)~lload_features; + } + + if ( c->op == LDAP_MOD_DELETE ) { + if ( !c->line ) { + /* Last value has been deleted */ + lload_features = 0; + } else { + i = verb_to_mask( c->line, features ); + lload_features &= ~features[i].mask; + } + return 0; + } + + i = verbs_to_mask( c->argc, c->argv, features, &mask ); + if ( i ) { + Debug( LDAP_DEBUG_ANY, "%s: <%s> unknown feature %s\n", c->log, + c->argv[0], c->argv[i] ); + return 1; + } + + if ( mask & ~LLOAD_FEATURE_SUPPORTED_MASK ) { + for ( i = 1; i < c->argc; i++ ) { + int j = verb_to_mask( c->argv[i], features ); + if ( features[j].mask & ~LLOAD_FEATURE_SUPPORTED_MASK ) { + Debug( LDAP_DEBUG_ANY, "%s: <%s> " + "experimental feature %s is undocumented, unsupported " + "and can change or disappear at any time!\n", + c->log, c->argv[0], c->argv[i] ); + } + } + } + + lload_features |= mask; + return 0; +} + +#ifdef HAVE_TLS +static int +config_tls_cleanup( ConfigArgs *c ) +{ + int rc = 0; + + if ( lload_tls_ld ) { + int opt = 1; + + ldap_pvt_tls_ctx_free( lload_tls_ctx ); + lload_tls_ctx = NULL; + + /* Force new ctx to be created */ + rc = ldap_pvt_tls_set_option( + lload_tls_ld, LDAP_OPT_X_TLS_NEWCTX, &opt ); + if ( rc == 0 ) { + /* The ctx's refcount is bumped up here */ + ldap_pvt_tls_get_option( + lload_tls_ld, LDAP_OPT_X_TLS_CTX, &lload_tls_ctx ); + } else { + if ( rc == LDAP_NOT_SUPPORTED ) + rc = LDAP_UNWILLING_TO_PERFORM; + else + rc = LDAP_OTHER; + } + } + return rc; +} + +static int +config_tls_option( ConfigArgs *c ) +{ + int flag; + int berval = 0; + LDAP *ld = lload_tls_ld; + + switch ( c->type ) { + case CFG_TLS_RAND: + flag = LDAP_OPT_X_TLS_RANDOM_FILE; + ld = NULL; + break; + case CFG_TLS_CIPHER: + flag = LDAP_OPT_X_TLS_CIPHER_SUITE; + break; + case CFG_TLS_CERT_FILE: + flag = LDAP_OPT_X_TLS_CERTFILE; + break; + case CFG_TLS_CERT_KEY: + flag = LDAP_OPT_X_TLS_KEYFILE; + break; + case CFG_TLS_CA_PATH: + flag = LDAP_OPT_X_TLS_CACERTDIR; + break; + case CFG_TLS_CA_FILE: + flag = LDAP_OPT_X_TLS_CACERTFILE; + break; + case CFG_TLS_DH_FILE: + flag = LDAP_OPT_X_TLS_DHFILE; + break; + case CFG_TLS_ECNAME: + flag = LDAP_OPT_X_TLS_ECNAME; + break; +#ifdef HAVE_GNUTLS + case CFG_TLS_CRL_FILE: + flag = LDAP_OPT_X_TLS_CRLFILE; + break; +#endif + case CFG_TLS_CACERT: + flag = LDAP_OPT_X_TLS_CACERT; + berval = 1; + break; + case CFG_TLS_CERT: + flag = LDAP_OPT_X_TLS_CERT; + berval = 1; + break; + case CFG_TLS_KEY: + flag = LDAP_OPT_X_TLS_KEY; + berval = 1; + break; + default: + Debug( LDAP_DEBUG_ANY, "%s: " + "unknown tls_option <0x%x>\n", + c->log, c->type ); + return 1; + } + if ( c->op == SLAP_CONFIG_EMIT ) { + return ldap_pvt_tls_get_option( ld, flag, + berval ? (void *)&c->value_bv : (void *)&c->value_string ); + } + + lload_change.type = LLOAD_CHANGE_MODIFY; + lload_change.object = LLOAD_DAEMON; + lload_change.flags.daemon |= LLOAD_DAEMON_MOD_TLS; + + config_push_cleanup( c, config_tls_cleanup ); + if ( c->op == LDAP_MOD_DELETE ) { + return ldap_pvt_tls_set_option( ld, flag, NULL ); + } + if ( !berval ) ch_free( c->value_string ); + return ldap_pvt_tls_set_option( + ld, flag, berval ? (void *)&c->value_bv : (void *)c->argv[1] ); +} + +/* FIXME: this ought to be provided by libldap */ +static int +config_tls_config( ConfigArgs *c ) +{ + int i, flag; + + switch ( c->type ) { + case CFG_TLS_CRLCHECK: + flag = LDAP_OPT_X_TLS_CRLCHECK; + break; + case CFG_TLS_VERIFY: + flag = LDAP_OPT_X_TLS_REQUIRE_CERT; + break; + case CFG_TLS_PROTOCOL_MIN: + flag = LDAP_OPT_X_TLS_PROTOCOL_MIN; + break; + default: + Debug( LDAP_DEBUG_ANY, "%s: " + "unknown tls_option <0x%x>\n", + c->log, c->type ); + return 1; + } + if ( c->op == SLAP_CONFIG_EMIT ) { + return lload_tls_get_config( lload_tls_ld, flag, &c->value_string ); + } + + lload_change.type = LLOAD_CHANGE_MODIFY; + lload_change.object = LLOAD_DAEMON; + lload_change.flags.daemon |= LLOAD_DAEMON_MOD_TLS; + + config_push_cleanup( c, config_tls_cleanup ); + if ( c->op == LDAP_MOD_DELETE ) { + int i = 0; + return ldap_pvt_tls_set_option( lload_tls_ld, flag, &i ); + } + ch_free( c->value_string ); + if ( isdigit( (unsigned char)c->argv[1][0] ) && + c->type != CFG_TLS_PROTOCOL_MIN ) { + if ( lutil_atoi( &i, c->argv[1] ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "%s: " + "unable to parse %s \"%s\"\n", + c->log, c->argv[0], c->argv[1] ); + return 1; + } + return ldap_pvt_tls_set_option( lload_tls_ld, flag, &i ); + } else { + return ldap_pvt_tls_config( lload_tls_ld, flag, c->argv[1] ); + } +} +#endif + +#ifdef BALANCER_MODULE +static int +config_share_tls_ctx( ConfigArgs *c ) +{ + int rc = LDAP_SUCCESS; + + if ( c->op == SLAP_CONFIG_EMIT ) { + c->value_int = lload_use_slap_tls_ctx; + return rc; + } + + lload_change.type = LLOAD_CHANGE_MODIFY; + lload_change.object = LLOAD_DAEMON; + lload_change.flags.daemon |= LLOAD_DAEMON_MOD_TLS; + + if ( c->op == LDAP_MOD_DELETE ) { + lload_use_slap_tls_ctx = 0; + return rc; + } + + lload_use_slap_tls_ctx = c->value_int; + return rc; +} +#endif /* BALANCER_MODULE */ + +void +lload_init_config_argv( ConfigArgs *c ) +{ + c->argv = ch_calloc( ARGS_STEP + 1, sizeof(*c->argv) ); + c->argv_size = ARGS_STEP + 1; +} + +ConfigTable * +lload_config_find_keyword( ConfigTable *Conf, ConfigArgs *c ) +{ + int i; + + for ( i = 0; Conf[i].name; i++ ) + if ( ( Conf[i].length && + ( !strncasecmp( + c->argv[0], Conf[i].name, Conf[i].length ) ) ) || + ( !strcasecmp( c->argv[0], Conf[i].name ) ) ) + break; + if ( !Conf[i].name ) return NULL; + if ( (Conf[i].arg_type & ARGS_TYPES) == ARG_BINARY ) { + size_t decode_len = LUTIL_BASE64_DECODE_LEN( c->linelen ); + ch_free( c->tline ); + c->tline = ch_malloc( decode_len + 1 ); + c->linelen = lutil_b64_pton( c->line, c->tline, decode_len ); + if ( c->linelen < 0 ) { + ch_free( c->tline ); + c->tline = NULL; + return NULL; + } + c->line = c->tline; + } + c->ca_desc = Conf + i; + return c->ca_desc; +} + +int +lload_config_check_vals( ConfigTable *Conf, ConfigArgs *c, int check_only ) +{ + int arg_user, arg_type, arg_syn, iarg; + unsigned uiarg; + long larg; + unsigned long ularg; + ber_len_t barg; + + if ( Conf->arg_type == ARG_IGNORED ) { + Debug( LDAP_DEBUG_CONFIG, "%s: keyword <%s> ignored\n", + c->log, Conf->name ); + return 0; + } + arg_type = Conf->arg_type & ARGS_TYPES; + arg_user = Conf->arg_type & ARGS_USERLAND; + arg_syn = Conf->arg_type & ARGS_SYNTAX; + + if ( Conf->min_args && ( c->argc < Conf->min_args ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), "<%s> missing <%s> argument", + c->argv[0], Conf->what ? Conf->what : "" ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: keyword %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + if ( Conf->max_args && ( c->argc > Conf->max_args ) ) { + char *ignored = " ignored"; + + snprintf( c->cr_msg, sizeof(c->cr_msg), "<%s> extra cruft after <%s>", + c->argv[0], Conf->what ); + + ignored = ""; + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s%s\n", + c->log, c->cr_msg, ignored ); + return ARG_BAD_CONF; + } + if ( (arg_syn & ARG_PAREN) && *c->argv[1] != '(' /*')'*/ ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), "<%s> old format not supported", + c->argv[0] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + if ( arg_type && !Conf->arg_item && !(arg_syn & ARG_OFFSET) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "<%s> invalid config_table, arg_item is NULL", + c->argv[0] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + c->type = arg_user; + memset( &c->values, 0, sizeof(c->values) ); + if ( arg_type == ARG_STRING ) { + assert( c->argc == 2 ); + if ( !check_only ) c->value_string = ch_strdup( c->argv[1] ); + } else if ( arg_type == ARG_BERVAL ) { + assert( c->argc == 2 ); + if ( !check_only ) ber_str2bv( c->argv[1], 0, 1, &c->value_bv ); + } else if ( arg_type == ARG_BINARY ) { + assert( c->argc == 2 ); + if ( !check_only ) { + c->value_bv.bv_len = c->linelen; + c->value_bv.bv_val = ch_malloc( c->linelen ); + AC_MEMCPY( c->value_bv.bv_val, c->line, c->linelen ); + } + } else { /* all numeric */ + int j; + iarg = 0; + larg = 0; + barg = 0; + switch ( arg_type ) { + case ARG_INT: + assert( c->argc == 2 ); + if ( lutil_atoix( &iarg, c->argv[1], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "<%s> unable to parse \"%s\" as int", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + break; + case ARG_UINT: + assert( c->argc == 2 ); + if ( lutil_atoux( &uiarg, c->argv[1], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "<%s> unable to parse \"%s\" as unsigned int", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + break; + case ARG_LONG: + assert( c->argc == 2 ); + if ( lutil_atolx( &larg, c->argv[1], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "<%s> unable to parse \"%s\" as long", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + break; + case ARG_ULONG: + assert( c->argc == 2 ); + if ( LUTIL_ATOULX( &ularg, c->argv[1], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "<%s> unable to parse \"%s\" as unsigned long", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + break; + case ARG_BER_LEN_T: { + unsigned long l; + assert( c->argc == 2 ); + if ( lutil_atoulx( &l, c->argv[1], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "<%s> unable to parse \"%s\" as ber_len_t", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + barg = (ber_len_t)l; + } break; + case ARG_ON_OFF: + /* note: this is an explicit exception + * to the "need exactly 2 args" rule */ + if ( c->argc == 1 ) { + iarg = 1; + } else if ( !strcasecmp( c->argv[1], "on" ) || + !strcasecmp( c->argv[1], "true" ) || + !strcasecmp( c->argv[1], "yes" ) ) { + iarg = 1; + } else if ( !strcasecmp( c->argv[1], "off" ) || + !strcasecmp( c->argv[1], "false" ) || + !strcasecmp( c->argv[1], "no" ) ) { + iarg = 0; + } else { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "<%s> invalid value", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + break; + } + j = (arg_type & ARG_NONZERO) ? 1 : 0; + if ( iarg < j && larg < j && barg < (unsigned)j ) { + larg = larg ? larg : ( barg ? (long)barg : iarg ); + snprintf( c->cr_msg, sizeof(c->cr_msg), "<%s> invalid value", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY|LDAP_DEBUG_NONE, "%s: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + switch ( arg_type ) { + case ARG_ON_OFF: + case ARG_INT: + c->value_int = iarg; + break; + case ARG_UINT: + c->value_uint = uiarg; + break; + case ARG_LONG: + c->value_long = larg; + break; + case ARG_ULONG: + c->value_ulong = ularg; + break; + case ARG_BER_LEN_T: + c->value_ber_t = barg; + break; + } + } + return 0; +} + +int +lload_config_set_vals( ConfigTable *Conf, ConfigArgs *c ) +{ + int rc, arg_type; + void *ptr = NULL; + + arg_type = Conf->arg_type; + if ( arg_type & ARG_MAGIC ) { + c->cr_msg[0] = '\0'; + rc = ( *( (ConfigDriver *)Conf->arg_item ) )( c ); + if ( rc ) { + if ( !c->cr_msg[0] ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "<%s> handler exited with %d", + c->argv[0], rc ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s!\n", c->log, c->cr_msg ); + } + return ARG_BAD_CONF; + } + return 0; + } + if ( arg_type & ARG_OFFSET ) { + { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "<%s> offset is missing base pointer", + c->argv[0] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s!\n", c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + ptr = (void *)( (char *)ptr + (long)Conf->arg_item ); + } else if ( arg_type & ARGS_TYPES ) { + ptr = Conf->arg_item; + } + if ( arg_type & ARGS_TYPES ) switch ( arg_type & ARGS_TYPES ) { + case ARG_ON_OFF: + case ARG_INT: + *(int *)ptr = c->value_int; + break; + case ARG_UINT: + *(unsigned *)ptr = c->value_uint; + break; + case ARG_LONG: + *(long *)ptr = c->value_long; + break; + case ARG_ULONG: + *(size_t *)ptr = c->value_ulong; + break; + case ARG_BER_LEN_T: + *(ber_len_t *)ptr = c->value_ber_t; + break; + case ARG_STRING: { + char *cc = *(char **)ptr; + if ( cc ) { + if ( (arg_type & ARG_UNIQUE) && + c->op == SLAP_CONFIG_ADD ) { + Debug( LDAP_DEBUG_CONFIG, "%s: already set %s!\n", + c->log, Conf->name ); + return ARG_BAD_CONF; + } + ch_free( cc ); + } + *(char **)ptr = c->value_string; + break; + } + case ARG_BERVAL: + case ARG_BINARY: + *(struct berval *)ptr = c->value_bv; + break; + } + return 0; +} + +int +lload_config_add_vals( ConfigTable *Conf, ConfigArgs *c ) +{ + int rc, arg_type; + + arg_type = Conf->arg_type; + if ( arg_type == ARG_IGNORED ) { + Debug( LDAP_DEBUG_CONFIG, "%s: keyword <%s> ignored\n", + c->log, Conf->name ); + return 0; + } + rc = lload_config_check_vals( Conf, c, 0 ); + if ( rc ) return rc; + return lload_config_set_vals( Conf, c ); +} + +int +lload_read_config_file( + const char *fname, + int depth, + ConfigArgs *cf, + ConfigTable *cft ) +{ + FILE *fp; + ConfigTable *ct; + ConfigArgs *c; + int rc; + struct stat s; + + c = ch_calloc( 1, sizeof(ConfigArgs) ); + if ( c == NULL ) { + return 1; + } + + if ( depth ) { + memcpy( c, cf, sizeof(ConfigArgs) ); + } else { + c->depth = depth; /* XXX */ + } + + c->valx = -1; + c->fname = fname; + lload_init_config_argv( c ); + + if ( stat( fname, &s ) != 0 ) { + char ebuf[128]; + int saved_errno = errno; + ldap_syslog = 1; + Debug( LDAP_DEBUG_ANY, "could not stat config file \"%s\": %s (%d)\n", + fname, AC_STRERROR_R( saved_errno, ebuf, sizeof(ebuf) ), + saved_errno ); + ch_free( c->argv ); + ch_free( c ); + return 1; + } + + if ( !S_ISREG(s.st_mode) ) { + ldap_syslog = 1; + Debug( LDAP_DEBUG_ANY, "regular file expected, got \"%s\"\n", fname ); + ch_free( c->argv ); + ch_free( c ); + return 1; + } + + fp = fopen( fname, "r" ); + if ( fp == NULL ) { + char ebuf[128]; + int saved_errno = errno; + ldap_syslog = 1; + Debug( LDAP_DEBUG_ANY, "could not open config file \"%s\": %s (%d)\n", + fname, AC_STRERROR_R( saved_errno, ebuf, sizeof(ebuf) ), + saved_errno ); + ch_free( c->argv ); + ch_free( c ); + return 1; + } + + Debug( LDAP_DEBUG_CONFIG, "reading config file %s\n", fname ); + + fp_getline_init( c ); + + c->tline = NULL; + + while ( fp_getline( fp, c ) ) { + /* skip comments and blank lines */ + if ( c->line[0] == '#' || c->line[0] == '\0' ) { + continue; + } + + snprintf( c->log, sizeof(c->log), "%s: line %d", + c->fname, c->lineno ); + + c->argc = 0; + ch_free( c->tline ); + if ( lload_config_fp_parse_line( c ) ) { + rc = 1; + goto done; + } + + if ( c->argc < 1 ) { + Debug( LDAP_DEBUG_ANY, "%s: bad config line\n", c->log ); + rc = 1; + goto done; + } + + c->op = SLAP_CONFIG_ADD; + + ct = lload_config_find_keyword( cft, c ); + if ( ct ) { + c->table = Cft_Global; + rc = lload_config_add_vals( ct, c ); + if ( !rc ) continue; + + if ( rc & ARGS_USERLAND ) { + /* XXX a usertype would be opaque here */ + Debug( LDAP_DEBUG_CONFIG, "%s: unknown user type <%s>\n", + c->log, c->argv[0] ); + rc = 1; + goto done; + + } else if ( rc == ARG_BAD_CONF ) { + rc = 1; + goto done; + } + + } else { + Debug( LDAP_DEBUG_ANY, "%s: unknown directive " + "<%s> outside backend info and database definitions\n", + c->log, *c->argv ); + rc = 1; + goto done; + } + } + + rc = 0; + +done: + ch_free( c->tline ); + fclose( fp ); + ch_free( c->argv ); + ch_free( c ); + return rc; +} + +int +lload_read_config( const char *fname, const char *dir ) +{ + if ( !fname ) fname = LLOADD_DEFAULT_CONFIGFILE; + + cfn = ch_calloc( 1, sizeof(ConfigFile) ); + + return lload_read_config_file( fname, 0, NULL, config_back_cf_table ); +} + +#ifndef BALANCER_MODULE +int +config_push_cleanup( ConfigArgs *ca, ConfigDriver *cleanup ) +{ + /* Stub, cleanups only run in online config */ + return 0; +} +#endif /* !BALANCER_MODULE */ + +static slap_verbmasks tlskey[] = { + { BER_BVC("no"), LLOAD_CLEARTEXT }, + { BER_BVC("yes"), LLOAD_STARTTLS_OPTIONAL }, + { BER_BVC("critical"), LLOAD_STARTTLS }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks crlkeys[] = { + { BER_BVC("none"), LDAP_OPT_X_TLS_CRL_NONE }, + { BER_BVC("peer"), LDAP_OPT_X_TLS_CRL_PEER }, + { BER_BVC("all"), LDAP_OPT_X_TLS_CRL_ALL }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks vfykeys[] = { + { BER_BVC("never"), LDAP_OPT_X_TLS_NEVER }, + { BER_BVC("allow"), LDAP_OPT_X_TLS_ALLOW }, + { BER_BVC("try"), LDAP_OPT_X_TLS_TRY }, + { BER_BVC("demand"), LDAP_OPT_X_TLS_DEMAND }, + { BER_BVC("hard"), LDAP_OPT_X_TLS_HARD }, + { BER_BVC("true"), LDAP_OPT_X_TLS_HARD }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks methkey[] = { + { BER_BVC("none"), LDAP_AUTH_NONE }, + { BER_BVC("simple"), LDAP_AUTH_SIMPLE }, +#ifdef HAVE_CYRUS_SASL + { BER_BVC("sasl"), LDAP_AUTH_SASL }, +#endif + { BER_BVNULL, 0 } +}; + +int +lload_keepalive_parse( + struct berval *val, + void *bc, + slap_cf_aux_table *tab0, + const char *tabmsg, + int unparse ) +{ + if ( unparse ) { + slap_keepalive *sk = (slap_keepalive *)bc; + int rc = snprintf( val->bv_val, val->bv_len, "%d:%d:%d", + sk->sk_idle, sk->sk_probes, sk->sk_interval ); + if ( rc < 0 ) { + return -1; + } + + if ( (unsigned)rc >= val->bv_len ) { + return -1; + } + + val->bv_len = rc; + + } else { + char *s = val->bv_val; + char *next; + slap_keepalive *sk = (slap_keepalive *)bc; + slap_keepalive sk2; + + if ( s[0] == ':' ) { + sk2.sk_idle = 0; + s++; + + } else { + sk2.sk_idle = strtol( s, &next, 10 ); + if ( next == s || next[0] != ':' ) { + return -1; + } + + if ( sk2.sk_idle < 0 ) { + return -1; + } + + s = ++next; + } + + if ( s[0] == ':' ) { + sk2.sk_probes = 0; + s++; + + } else { + sk2.sk_probes = strtol( s, &next, 10 ); + if ( next == s || next[0] != ':' ) { + return -1; + } + + if ( sk2.sk_probes < 0 ) { + return -1; + } + + s = ++next; + } + + if ( *s == '\0' ) { + sk2.sk_interval = 0; + + } else { + sk2.sk_interval = strtol( s, &next, 10 ); + if ( next == s || next[0] != '\0' ) { + return -1; + } + + if ( sk2.sk_interval < 0 ) { + return -1; + } + } + + *sk = sk2; + + ber_memfree( val->bv_val ); + BER_BVZERO( val ); + } + + return 0; +} + +static slap_cf_aux_table backendkey[] = { + { BER_BVC("uri="), offsetof(LloadBackend, b_uri), 'b', 1, NULL }, + + { BER_BVC("numconns="), offsetof(LloadBackend, b_numconns), 'i', 0, NULL }, + { BER_BVC("bindconns="), offsetof(LloadBackend, b_numbindconns), 'i', 0, NULL }, + { BER_BVC("retry="), offsetof(LloadBackend, b_retry_timeout), 'i', 0, NULL }, + + { BER_BVC("max-pending-ops="), offsetof(LloadBackend, b_max_pending), 'i', 0, NULL }, + { BER_BVC("conn-max-pending="), offsetof(LloadBackend, b_max_conn_pending), 'i', 0, NULL }, + { BER_BVC("starttls="), offsetof(LloadBackend, b_tls_conf), 'i', 0, tlskey }, + + { BER_BVC("weight="), offsetof(LloadBackend, b_weight), 'i', 0, NULL }, + + { BER_BVNULL, 0, 0, 0, NULL } +}; + +static slap_cf_aux_table bindkey[] = { + { BER_BVC("bindmethod="), offsetof(slap_bindconf, sb_method), 'i', 0, methkey }, + { BER_BVC("timeout="), offsetof(slap_bindconf, sb_timeout_api), 'i', 0, NULL }, + { BER_BVC("network-timeout="), offsetof(slap_bindconf, sb_timeout_net), 'i', 0, NULL }, + { BER_BVC("binddn="), offsetof(slap_bindconf, sb_binddn), 'b', 1, NULL }, + { BER_BVC("credentials="), offsetof(slap_bindconf, sb_cred), 'b', 1, NULL }, + { BER_BVC("saslmech="), offsetof(slap_bindconf, sb_saslmech), 'b', 0, NULL }, + { BER_BVC("secprops="), offsetof(slap_bindconf, sb_secprops), 's', 0, NULL }, + { BER_BVC("realm="), offsetof(slap_bindconf, sb_realm), 'b', 0, NULL }, + { BER_BVC("authcID="), offsetof(slap_bindconf, sb_authcId), 'b', 1, NULL }, + { BER_BVC("authzID="), offsetof(slap_bindconf, sb_authzId), 'b', 1, NULL }, + { BER_BVC("keepalive="), offsetof(slap_bindconf, sb_keepalive), 'x', 0, (slap_verbmasks *)lload_keepalive_parse }, + { BER_BVC("tcp-user-timeout="), offsetof(slap_bindconf, sb_tcp_user_timeout), 'u', 0, NULL }, +#ifdef HAVE_TLS + /* NOTE: replace "12" with the actual index + * of the first TLS-related line */ +#define aux_TLS (bindkey+12) /* beginning of TLS keywords */ + + { BER_BVC("tls_cert="), offsetof(slap_bindconf, sb_tls_cert), 's', 1, NULL }, + { BER_BVC("tls_key="), offsetof(slap_bindconf, sb_tls_key), 's', 1, NULL }, + { BER_BVC("tls_cacert="), offsetof(slap_bindconf, sb_tls_cacert), 's', 1, NULL }, + { BER_BVC("tls_cacertdir="), offsetof(slap_bindconf, sb_tls_cacertdir), 's', 1, NULL }, + { BER_BVC("tls_reqcert="), offsetof(slap_bindconf, sb_tls_reqcert), 's', 0, NULL }, + { BER_BVC("tls_reqsan="), offsetof(slap_bindconf, sb_tls_reqsan), 's', 0, NULL }, + { BER_BVC("tls_cipher_suite="), offsetof(slap_bindconf, sb_tls_cipher_suite), 's', 0, NULL }, + { BER_BVC("tls_protocol_min="), offsetof(slap_bindconf, sb_tls_protocol_min), 's', 0, NULL }, + { BER_BVC("tls_ecname="), offsetof(slap_bindconf, sb_tls_ecname), 's', 0, NULL }, +#ifdef HAVE_OPENSSL + { BER_BVC("tls_crlcheck="), offsetof(slap_bindconf, sb_tls_crlcheck), 's', 0, NULL }, +#endif +#endif + { BER_BVNULL, 0, 0, 0, NULL } +}; + +/* + * 's': char * + * 'b': struct berval + * 'i': int; if !NULL, compute using ((slap_verbmasks *)aux) + * 'u': unsigned + * 'I': long + * 'U': unsigned long + */ + +int +lload_cf_aux_table_parse( + const char *word, + void *dst, + slap_cf_aux_table *tab0, + LDAP_CONST char *tabmsg ) +{ + int rc = SLAP_CONF_UNKNOWN; + slap_cf_aux_table *tab; + + for ( tab = tab0; !BER_BVISNULL( &tab->key ); tab++ ) { + if ( !strncasecmp( word, tab->key.bv_val, tab->key.bv_len ) ) { + char **cptr; + int *iptr, j; + unsigned *uptr; + long *lptr; + unsigned long *ulptr; + struct berval *bptr; + const char *val = word + tab->key.bv_len; + + switch ( tab->type ) { + case 's': + cptr = (char **)( (char *)dst + tab->off ); + *cptr = ch_strdup( val ); + rc = 0; + break; + + case 'b': + bptr = (struct berval *)( (char *)dst + tab->off ); + assert( tab->aux == NULL ); + ber_str2bv( val, 0, 1, bptr ); + rc = 0; + break; + + case 'i': + iptr = (int *)( (char *)dst + tab->off ); + + if ( tab->aux != NULL ) { + slap_verbmasks *aux = (slap_verbmasks *)tab->aux; + + assert( aux != NULL ); + + rc = 1; + for ( j = 0; !BER_BVISNULL( &aux[j].word ); j++ ) { + if ( !strcasecmp( val, aux[j].word.bv_val ) ) { + *iptr = aux[j].mask; + rc = 0; + break; + } + } + + } else { + rc = lutil_atoix( iptr, val, 0 ); + } + break; + + case 'u': + uptr = (unsigned *)( (char *)dst + tab->off ); + + rc = lutil_atoux( uptr, val, 0 ); + break; + + case 'I': + lptr = (long *)( (char *)dst + tab->off ); + + rc = lutil_atolx( lptr, val, 0 ); + break; + + case 'U': + ulptr = (unsigned long *)( (char *)dst + tab->off ); + + rc = lutil_atoulx( ulptr, val, 0 ); + break; + + case 'x': + if ( tab->aux != NULL ) { + struct berval value; + lload_cf_aux_table_parse_x *func = + (lload_cf_aux_table_parse_x *)tab->aux; + + ber_str2bv( val, 0, 1, &value ); + + rc = func( &value, (void *)( (char *)dst + tab->off ), + tab, tabmsg, 0 ); + + } else { + rc = 1; + } + break; + } + + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "invalid %s value %s\n", tabmsg, word ); + } + + return rc; + } + } + + return rc; +} + +int +lload_cf_aux_table_unparse( + void *src, + struct berval *bv, + slap_cf_aux_table *tab0 ) +{ + char buf[AC_LINE_MAX], *ptr; + slap_cf_aux_table *tab; + struct berval tmp; + + ptr = buf; + for ( tab = tab0; !BER_BVISNULL( &tab->key ); tab++ ) { + char **cptr; + int *iptr, i; + unsigned *uptr; + long *lptr; + unsigned long *ulptr; + struct berval *bptr; + + cptr = (char **)( (char *)src + tab->off ); + + switch ( tab->type ) { + case 'b': + bptr = (struct berval *)( (char *)src + tab->off ); + cptr = &bptr->bv_val; + + case 's': + if ( *cptr ) { + *ptr++ = ' '; + ptr = lutil_strcopy( ptr, tab->key.bv_val ); + if ( tab->quote ) *ptr++ = '"'; + ptr = lutil_strcopy( ptr, *cptr ); + if ( tab->quote ) *ptr++ = '"'; + } + break; + + case 'i': + iptr = (int *)( (char *)src + tab->off ); + + if ( tab->aux != NULL ) { + slap_verbmasks *aux = (slap_verbmasks *)tab->aux; + + for ( i = 0; !BER_BVISNULL( &aux[i].word ); i++ ) { + if ( *iptr == aux[i].mask ) { + *ptr++ = ' '; + ptr = lutil_strcopy( ptr, tab->key.bv_val ); + ptr = lutil_strcopy( ptr, aux[i].word.bv_val ); + break; + } + } + + } else { + *ptr++ = ' '; + ptr = lutil_strcopy( ptr, tab->key.bv_val ); + ptr += snprintf( ptr, sizeof(buf) - ( ptr - buf ), "%d", + *iptr ); + } + break; + + case 'u': + uptr = (unsigned *)( (char *)src + tab->off ); + *ptr++ = ' '; + ptr = lutil_strcopy( ptr, tab->key.bv_val ); + ptr += snprintf( ptr, sizeof(buf) - ( ptr - buf ), "%u", + *uptr ); + break; + + case 'I': + lptr = (long *)( (char *)src + tab->off ); + *ptr++ = ' '; + ptr = lutil_strcopy( ptr, tab->key.bv_val ); + ptr += snprintf( ptr, sizeof(buf) - ( ptr - buf ), "%ld", + *lptr ); + break; + + case 'U': + ulptr = (unsigned long *)( (char *)src + tab->off ); + *ptr++ = ' '; + ptr = lutil_strcopy( ptr, tab->key.bv_val ); + ptr += snprintf( ptr, sizeof(buf) - ( ptr - buf ), "%lu", + *ulptr ); + break; + + case 'x': { + char *saveptr = ptr; + *ptr++ = ' '; + ptr = lutil_strcopy( ptr, tab->key.bv_val ); + if ( tab->quote ) *ptr++ = '"'; + if ( tab->aux != NULL ) { + struct berval value; + lload_cf_aux_table_parse_x *func = + (lload_cf_aux_table_parse_x *)tab->aux; + int rc; + + value.bv_val = ptr; + value.bv_len = buf + sizeof(buf) - ptr; + + rc = func( &value, (void *)( (char *)src + tab->off ), tab, + "(unparse)", 1 ); + if ( rc == 0 ) { + if ( value.bv_len ) { + ptr += value.bv_len; + } else { + ptr = saveptr; + break; + } + } + } + if ( tab->quote ) *ptr++ = '"'; + } break; + + default: + assert(0); + } + } + tmp.bv_val = buf; + tmp.bv_len = ptr - buf; + ber_dupbv( bv, &tmp ); + return 0; +} + +int +lload_tls_get_config( LDAP *ld, int opt, char **val ) +{ +#ifdef HAVE_TLS + slap_verbmasks *keys; + int i, ival; + + *val = NULL; + switch ( opt ) { + case LDAP_OPT_X_TLS_CRLCHECK: + keys = crlkeys; + break; + case LDAP_OPT_X_TLS_REQUIRE_CERT: + keys = vfykeys; + break; + case LDAP_OPT_X_TLS_PROTOCOL_MIN: { + char buf[8]; + ldap_pvt_tls_get_option( ld, opt, &ival ); + snprintf( buf, sizeof(buf), "%d.%d", + ( ival >> 8 ) & 0xff, ival & 0xff ); + *val = ch_strdup( buf ); + return 0; + } + default: + return -1; + } + ldap_pvt_tls_get_option( ld, opt, &ival ); + for ( i = 0; !BER_BVISNULL( &keys[i].word ); i++ ) { + if ( keys[i].mask == ival ) { + *val = ch_strdup( keys[i].word.bv_val ); + return 0; + } + } +#endif + return -1; +} + +#ifdef HAVE_TLS +static struct { + const char *key; + size_t offset; + int opt; +} bindtlsopts[] = { + { "tls_cert", offsetof(slap_bindconf, sb_tls_cert), LDAP_OPT_X_TLS_CERTFILE }, + { "tls_key", offsetof(slap_bindconf, sb_tls_key), LDAP_OPT_X_TLS_KEYFILE }, + { "tls_cacert", offsetof(slap_bindconf, sb_tls_cacert), LDAP_OPT_X_TLS_CACERTFILE }, + { "tls_cacertdir", offsetof(slap_bindconf, sb_tls_cacertdir), LDAP_OPT_X_TLS_CACERTDIR }, + { "tls_cipher_suite", offsetof(slap_bindconf, sb_tls_cipher_suite), LDAP_OPT_X_TLS_CIPHER_SUITE }, + { "tls_ecname", offsetof(slap_bindconf, sb_tls_ecname), LDAP_OPT_X_TLS_ECNAME }, + { NULL, 0 } +}; + +int +lload_bindconf_tls_set( slap_bindconf *bc, LDAP *ld ) +{ + int i, rc, newctx = 0, res = 0; + char *ptr = (char *)bc, **word; + + if ( bc->sb_tls_do_init ) { + for ( i = 0; bindtlsopts[i].opt; i++ ) { + word = (char **)( ptr + bindtlsopts[i].offset ); + if ( *word ) { + rc = ldap_set_option( ld, bindtlsopts[i].opt, *word ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "lload_bindconf_tls_set: " + "failed to set %s to %s\n", + bindtlsopts[i].key, *word ); + res = -1; + } else + newctx = 1; + } + } + if ( bc->sb_tls_reqcert ) { + rc = ldap_pvt_tls_config( + ld, LDAP_OPT_X_TLS_REQUIRE_CERT, bc->sb_tls_reqcert ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "lload_bindconf_tls_set: " + "failed to set tls_reqcert to %s\n", + bc->sb_tls_reqcert ); + res = -1; + } else { + newctx = 1; + /* retrieve the parsed setting for later use */ + ldap_get_option( ld, LDAP_OPT_X_TLS_REQUIRE_CERT, + &bc->sb_tls_int_reqcert ); + } + } + if ( bc->sb_tls_reqsan ) { + rc = ldap_pvt_tls_config( + ld, LDAP_OPT_X_TLS_REQUIRE_SAN, bc->sb_tls_reqsan ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "lload_bindconf_tls_set: " + "failed to set tls_reqsan to %s\n", + bc->sb_tls_reqsan ); + res = -1; + } else { + newctx = 1; + /* retrieve the parsed setting for later use */ + ldap_get_option( ld, LDAP_OPT_X_TLS_REQUIRE_SAN, + &bc->sb_tls_int_reqsan ); + } + } + if ( bc->sb_tls_protocol_min ) { + rc = ldap_pvt_tls_config( + ld, LDAP_OPT_X_TLS_PROTOCOL_MIN, bc->sb_tls_protocol_min ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "lload_bindconf_tls_set: " + "failed to set tls_protocol_min to %s\n", + bc->sb_tls_protocol_min ); + res = -1; + } else + newctx = 1; + } +#ifdef HAVE_OPENSSL + if ( bc->sb_tls_crlcheck ) { + rc = ldap_pvt_tls_config( + ld, LDAP_OPT_X_TLS_CRLCHECK, bc->sb_tls_crlcheck ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "lload_bindconf_tls_set: " + "failed to set tls_crlcheck to %s\n", + bc->sb_tls_crlcheck ); + res = -1; + } else + newctx = 1; + } +#endif + if ( !res ) bc->sb_tls_do_init = 0; + } + + if ( newctx ) { + int opt = 0; + + if ( bc->sb_tls_ctx ) { + ldap_pvt_tls_ctx_free( bc->sb_tls_ctx ); + bc->sb_tls_ctx = NULL; + } + rc = ldap_set_option( ld, LDAP_OPT_X_TLS_NEWCTX, &opt ); + if ( rc ) + res = rc; + else + ldap_get_option( ld, LDAP_OPT_X_TLS_CTX, &bc->sb_tls_ctx ); + } else if ( bc->sb_tls_ctx ) { + rc = ldap_set_option( ld, LDAP_OPT_X_TLS_CTX, bc->sb_tls_ctx ); + if ( rc == LDAP_SUCCESS ) { + /* these options aren't actually inside the ctx, so have to be set again */ + ldap_set_option( + ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &bc->sb_tls_int_reqcert ); + ldap_set_option( + ld, LDAP_OPT_X_TLS_REQUIRE_SAN, &bc->sb_tls_int_reqsan ); + } else + res = rc; + } + + return res; +} +#endif + +int +lload_bindconf_tls_parse( const char *word, slap_bindconf *bc ) +{ +#ifdef HAVE_TLS + if ( lload_cf_aux_table_parse( word, bc, aux_TLS, "tls config" ) == 0 ) { + bc->sb_tls_do_init = 1; + return 0; + } +#endif + return -1; +} + +int +lload_backend_parse( const char *word, LloadBackend *b ) +{ + return lload_cf_aux_table_parse( word, b, backendkey, "backend config" ); +} + +int +lload_bindconf_parse( const char *word, slap_bindconf *bc ) +{ +#ifdef HAVE_TLS + /* Detect TLS config changes explicitly */ + if ( lload_bindconf_tls_parse( word, bc ) == 0 ) { + return 0; + } +#endif + return lload_cf_aux_table_parse( word, bc, bindkey, "bind config" ); +} + +int +lload_bindconf_unparse( slap_bindconf *bc, struct berval *bv ) +{ + return lload_cf_aux_table_unparse( bc, bv, bindkey ); +} + +void +lload_bindconf_free( slap_bindconf *bc ) +{ + if ( !BER_BVISNULL( &bc->sb_uri ) ) { + ch_free( bc->sb_uri.bv_val ); + BER_BVZERO( &bc->sb_uri ); + } + if ( !BER_BVISNULL( &bc->sb_binddn ) ) { + ch_free( bc->sb_binddn.bv_val ); + BER_BVZERO( &bc->sb_binddn ); + } + if ( !BER_BVISNULL( &bc->sb_cred ) ) { + ch_free( bc->sb_cred.bv_val ); + BER_BVZERO( &bc->sb_cred ); + } + if ( !BER_BVISNULL( &bc->sb_saslmech ) ) { + ch_free( bc->sb_saslmech.bv_val ); + BER_BVZERO( &bc->sb_saslmech ); + } + if ( bc->sb_secprops ) { + ch_free( bc->sb_secprops ); + bc->sb_secprops = NULL; + } + if ( !BER_BVISNULL( &bc->sb_realm ) ) { + ch_free( bc->sb_realm.bv_val ); + BER_BVZERO( &bc->sb_realm ); + } + if ( !BER_BVISNULL( &bc->sb_authcId ) ) { + ch_free( bc->sb_authcId.bv_val ); + BER_BVZERO( &bc->sb_authcId ); + } + if ( !BER_BVISNULL( &bc->sb_authzId ) ) { + ch_free( bc->sb_authzId.bv_val ); + BER_BVZERO( &bc->sb_authzId ); + } +#ifdef HAVE_TLS + if ( bc->sb_tls_cert ) { + ch_free( bc->sb_tls_cert ); + bc->sb_tls_cert = NULL; + } + if ( bc->sb_tls_key ) { + ch_free( bc->sb_tls_key ); + bc->sb_tls_key = NULL; + } + if ( bc->sb_tls_cacert ) { + ch_free( bc->sb_tls_cacert ); + bc->sb_tls_cacert = NULL; + } + if ( bc->sb_tls_cacertdir ) { + ch_free( bc->sb_tls_cacertdir ); + bc->sb_tls_cacertdir = NULL; + } + if ( bc->sb_tls_reqcert ) { + ch_free( bc->sb_tls_reqcert ); + bc->sb_tls_reqcert = NULL; + } + if ( bc->sb_tls_cipher_suite ) { + ch_free( bc->sb_tls_cipher_suite ); + bc->sb_tls_cipher_suite = NULL; + } + if ( bc->sb_tls_protocol_min ) { + ch_free( bc->sb_tls_protocol_min ); + bc->sb_tls_protocol_min = NULL; + } +#ifdef HAVE_OPENSSL_CRL + if ( bc->sb_tls_crlcheck ) { + ch_free( bc->sb_tls_crlcheck ); + bc->sb_tls_crlcheck = NULL; + } +#endif + if ( bc->sb_tls_ctx ) { + ldap_pvt_tls_ctx_free( bc->sb_tls_ctx ); + bc->sb_tls_ctx = NULL; + } +#endif +} + +void +lload_bindconf_tls_defaults( slap_bindconf *bc ) +{ +#ifdef HAVE_TLS + if ( bc->sb_tls_do_init ) { + if ( !bc->sb_tls_cacert ) + ldap_pvt_tls_get_option( lload_tls_ld, LDAP_OPT_X_TLS_CACERTFILE, + &bc->sb_tls_cacert ); + if ( !bc->sb_tls_cacertdir ) + ldap_pvt_tls_get_option( lload_tls_ld, LDAP_OPT_X_TLS_CACERTDIR, + &bc->sb_tls_cacertdir ); + if ( !bc->sb_tls_cert ) + ldap_pvt_tls_get_option( + lload_tls_ld, LDAP_OPT_X_TLS_CERTFILE, &bc->sb_tls_cert ); + if ( !bc->sb_tls_key ) + ldap_pvt_tls_get_option( + lload_tls_ld, LDAP_OPT_X_TLS_KEYFILE, &bc->sb_tls_key ); + if ( !bc->sb_tls_cipher_suite ) + ldap_pvt_tls_get_option( lload_tls_ld, LDAP_OPT_X_TLS_CIPHER_SUITE, + &bc->sb_tls_cipher_suite ); + if ( !bc->sb_tls_reqcert ) bc->sb_tls_reqcert = ch_strdup( "demand" ); +#ifdef HAVE_OPENSSL_CRL + if ( !bc->sb_tls_crlcheck ) + lload_tls_get_config( lload_tls_ld, LDAP_OPT_X_TLS_CRLCHECK, + &bc->sb_tls_crlcheck ); +#endif + } +#endif +} + +/* -------------------------------------- */ + +static char * +strtok_quote( char *line, char *sep, char **quote_ptr, int *iqp ) +{ + int inquote; + char *tmp; + static char *next; + + *quote_ptr = NULL; + if ( line != NULL ) { + next = line; + } + while ( *next && strchr( sep, *next ) ) { + next++; + } + + if ( *next == '\0' ) { + next = NULL; + return NULL; + } + tmp = next; + + for ( inquote = 0; *next; ) { + switch ( *next ) { + case '"': + if ( inquote ) { + inquote = 0; + } else { + inquote = 1; + } + AC_MEMCPY( next, next + 1, strlen( next + 1 ) + 1 ); + break; + + case '\\': + if ( next[1] ) + AC_MEMCPY( next, next + 1, strlen( next + 1 ) + 1 ); + next++; /* dont parse the escaped character */ + break; + + default: + if ( !inquote ) { + if ( strchr( sep, *next ) != NULL ) { + *quote_ptr = next; + *next++ = '\0'; + return tmp; + } + } + next++; + break; + } + } + *iqp = inquote; + + return tmp; +} + +static char buf[AC_LINE_MAX]; +static char *line; +static size_t lmax, lcur; + +#define CATLINE( buf ) \ + do { \ + size_t len = strlen( buf ); \ + while ( lcur + len + 1 > lmax ) { \ + lmax += AC_LINE_MAX; \ + line = (char *)ch_realloc( line, lmax ); \ + } \ + strcpy( line + lcur, buf ); \ + lcur += len; \ + } while (0) + +static void +fp_getline_init( ConfigArgs *c ) +{ + c->lineno = -1; + buf[0] = '\0'; +} + +static int +fp_getline( FILE *fp, ConfigArgs *c ) +{ + char *p; + + lcur = 0; + CATLINE( buf ); + c->lineno++; + + /* avoid stack of bufs */ + if ( strncasecmp( line, "include", STRLENOF("include") ) == 0 ) { + buf[0] = '\0'; + c->line = line; + return 1; + } + + while ( fgets( buf, sizeof(buf), fp ) ) { + p = strchr( buf, '\n' ); + if ( p ) { + if ( p > buf && p[-1] == '\r' ) { + --p; + } + *p = '\0'; + } + /* XXX ugly */ + c->line = line; + if ( line[0] && ( p = line + strlen( line ) - 1 )[0] == '\\' && + p[-1] != '\\' ) { + p[0] = '\0'; + lcur--; + + } else { + if ( !isspace( (unsigned char)buf[0] ) ) { + return 1; + } + buf[0] = ' '; + } + CATLINE( buf ); + c->lineno++; + } + + buf[0] = '\0'; + c->line = line; + return ( line[0] ? 1 : 0 ); +} + +int +lload_config_fp_parse_line( ConfigArgs *c ) +{ + char *token; + static char *const hide[] = { "bindconf", NULL }; + static char *const raw[] = { NULL }; + char *quote_ptr; + int i = (int)( sizeof(hide) / sizeof(hide[0]) ) - 1; + int inquote = 0; + + c->tline = ch_strdup( c->line ); + c->linelen = strlen( c->line ); + token = strtok_quote( c->tline, " \t", "e_ptr, &inquote ); + + if ( token ) + for ( i = 0; hide[i]; i++ ) + if ( !strcasecmp( token, hide[i] ) ) break; + if ( quote_ptr ) *quote_ptr = ' '; + Debug( LDAP_DEBUG_CONFIG, "%s (%s%s)\n", + c->log, hide[i] ? hide[i] : c->line, hide[i] ? " ***" : "" ); + if ( quote_ptr ) *quote_ptr = '\0'; + + for ( ;; token = strtok_quote( NULL, " \t", "e_ptr, &inquote ) ) { + if ( c->argc >= c->argv_size ) { + char **tmp; + tmp = ch_realloc( c->argv, + ( c->argv_size + ARGS_STEP ) * sizeof(*c->argv) ); + if ( !tmp ) { + Debug( LDAP_DEBUG_ANY, "%s: out of memory\n", c->log ); + return -1; + } + c->argv = tmp; + c->argv_size += ARGS_STEP; + } + if ( token == NULL ) break; + c->argv[c->argc++] = token; + } + c->argv[c->argc] = NULL; + if ( inquote ) { + /* these directives parse c->line independently of argv tokenizing */ + for ( i = 0; raw[i]; i++ ) + if ( !strcasecmp( c->argv[0], raw[i] ) ) return 0; + + Debug( LDAP_DEBUG_ANY, "%s: unterminated quoted string \"%s\"\n", + c->log, c->argv[c->argc - 1] ); + return -1; + } + return 0; +} + +void +lload_config_destroy( void ) +{ + free( line ); + if ( slapd_args_file ) free( slapd_args_file ); + if ( slapd_pid_file ) free( slapd_pid_file ); + slap_loglevel_destroy(); +} + +/* See if the given URL (in plain and parsed form) matches + * any of the server's listener addresses. Return matching + * LloadListener or NULL for no match. + */ +LloadListener * +lload_config_check_my_url( const char *url, LDAPURLDesc *lud ) +{ + LloadListener **l = lloadd_get_listeners(); + int i, isMe; + + /* Try a straight compare with LloadListener strings */ + for ( i = 0; l && l[i]; i++ ) { + if ( !strcasecmp( url, l[i]->sl_url.bv_val ) ) { + return l[i]; + } + } + + isMe = 0; + /* If hostname is empty, or is localhost, or matches + * our hostname, this url refers to this host. + * Compare it against listeners and ports. + */ + if ( !lud->lud_host || !lud->lud_host[0] || + !strncasecmp( + "localhost", lud->lud_host, STRLENOF("localhost") ) || + !strcasecmp( global_host, lud->lud_host ) ) { + for ( i = 0; l && l[i]; i++ ) { + LDAPURLDesc *lu2; + ldap_url_parse_ext( + l[i]->sl_url.bv_val, &lu2, LDAP_PVT_URL_PARSE_DEF_PORT ); + do { + if ( strcasecmp( lud->lud_scheme, lu2->lud_scheme ) ) break; + if ( lud->lud_port != lu2->lud_port ) break; + /* Listener on ANY address */ + if ( !lu2->lud_host || !lu2->lud_host[0] ) { + isMe = 1; + break; + } + /* URL on ANY address */ + if ( !lud->lud_host || !lud->lud_host[0] ) { + isMe = 1; + break; + } + /* Listener has specific host, must + * match it + */ + if ( !strcasecmp( lud->lud_host, lu2->lud_host ) ) { + isMe = 1; + break; + } + } while (0); + ldap_free_urldesc( lu2 ); + if ( isMe ) { + return l[i]; + } + } + } + return NULL; +} + +#ifdef BALANCER_MODULE +static int +backend_cf_gen( ConfigArgs *c ) +{ + LloadBackend *b = c->ca_private; + enum lcf_backend flag = 0; + int rc = LDAP_SUCCESS; + + assert( b != NULL ); + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch ( c->type ) { + case CFG_URI: + c->value_bv = b->b_uri; + break; + case CFG_NUMCONNS: + c->value_uint = b->b_numconns; + break; + case CFG_BINDCONNS: + c->value_uint = b->b_numbindconns; + break; + case CFG_RETRY: + c->value_uint = b->b_retry_timeout; + break; + case CFG_MAX_PENDING_CONNS: + c->value_uint = b->b_max_conn_pending; + break; + case CFG_MAX_PENDING_OPS: + c->value_uint = b->b_max_pending; + break; + case CFG_STARTTLS: + enum_to_verb( tlskey, b->b_tls_conf, &c->value_bv ); + break; + case CFG_WEIGHT: + c->value_uint = b->b_weight; + break; + default: + rc = 1; + break; + } + + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { + /* We only need to worry about deletions to multi-value or MAY + * attributes */ + switch ( c->type ) { + case CFG_STARTTLS: + b->b_tls_conf = LLOAD_CLEARTEXT; + break; + default: + break; + } + return rc; + } + + switch ( c->type ) { + case CFG_URI: + rc = backend_config_url( b, &c->value_bv ); + if ( rc ) { + backend_config_url( b, &b->b_uri ); + goto fail; + } + if ( !BER_BVISNULL( &b->b_uri ) ) { + ch_free( b->b_uri.bv_val ); + } + b->b_uri = c->value_bv; + flag = LLOAD_BACKEND_MOD_OTHER; + break; + case CFG_NUMCONNS: + if ( !c->value_uint ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "invalid connection pool configuration" ); + goto fail; + } + b->b_numconns = c->value_uint; + flag = LLOAD_BACKEND_MOD_CONNS; + break; + case CFG_BINDCONNS: + if ( !c->value_uint ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "invalid connection pool configuration" ); + goto fail; + } + b->b_numbindconns = c->value_uint; + flag = LLOAD_BACKEND_MOD_CONNS; + break; + case CFG_RETRY: + b->b_retry_timeout = c->value_uint; + break; + case CFG_MAX_PENDING_CONNS: + b->b_max_conn_pending = c->value_uint; + break; + case CFG_MAX_PENDING_OPS: + b->b_max_pending = c->value_uint; + break; + case CFG_STARTTLS: { + int i = bverb_to_mask( &c->value_bv, tlskey ); + if ( BER_BVISNULL( &tlskey[i].word ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "invalid starttls configuration" ); + goto fail; + } +#ifndef HAVE_TLS + if ( tlskey[i].mask == LLOAD_STARTTLS_OPTIONAL ) { + Debug( LDAP_DEBUG_ANY, "%s: " + "lloadd compiled without TLS but starttls specified, " + "it will be ignored\n", + c->log ); + } else if ( tlskey[i].mask != LLOAD_CLEARTEXT ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "invalid starttls configuration when compiled without " + "TLS support" ); + goto fail; + } +#endif /* ! HAVE_TLS */ + b->b_tls_conf = tlskey[i].mask; + } break; + case CFG_WEIGHT: + b->b_weight = c->value_uint; + break; + default: + rc = 1; + break; + } + + /* do not set this if it has already been set by another callback, e.g. + * lload_backend_ldadd */ + if ( lload_change.type == LLOAD_CHANGE_UNDEFINED ) { + lload_change.type = LLOAD_CHANGE_MODIFY; + } + lload_change.object = LLOAD_BACKEND; + lload_change.target = b; + lload_change.flags.backend |= flag; + + config_push_cleanup( c, lload_backend_finish ); + return rc; + +fail: + if ( lload_change.type == LLOAD_CHANGE_ADD ) { + /* Abort the ADD */ + lload_change.type = LLOAD_CHANGE_DEL; + } + + Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg ); + return 1; +} + +int +lload_back_init_cf( BackendInfo *bi ) +{ + /* Make sure we don't exceed the bits reserved for userland */ + config_check_userland( CFG_LAST ); + + bi->bi_cf_ocs = lloadocs; + + return config_register_schema( config_back_cf_table, lloadocs ); +} + +static int +lload_tier_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca ) +{ + LloadTier *tier; + Attribute *a; + AttributeDescription *ad = NULL; + struct lload_tier_type *tier_impl; + struct berval bv, type, rdn; + const char *text; + char *name; + + Debug( LDAP_DEBUG_TRACE, "lload_tier_ldadd: " + "a new tier is being added\n" ); + + if ( p->ce_type != Cft_Backend || !p->ce_bi || + p->ce_bi->bi_cf_ocs != lloadocs ) + return LDAP_CONSTRAINT_VIOLATION; + + dnRdn( &e->e_name, &rdn ); + type.bv_len = strchr( rdn.bv_val, '=' ) - rdn.bv_val; + type.bv_val = rdn.bv_val; + + /* Find attr */ + slap_bv2ad( &type, &ad, &text ); + if ( ad != slap_schema.si_ad_cn ) return LDAP_NAMING_VIOLATION; + + a = attr_find( e->e_attrs, ad ); + if ( !a || a->a_numvals != 1 ) return LDAP_NAMING_VIOLATION; + bv = a->a_vals[0]; + + if ( bv.bv_val[0] == '{' && ( name = strchr( bv.bv_val, '}' ) ) ) { + name++; + bv.bv_len -= name - bv.bv_val; + bv.bv_val = name; + } + + ad = NULL; + slap_str2ad( "olcBkLloadTierType", &ad, &text ); + assert( ad != NULL ); + + a = attr_find( e->e_attrs, ad ); + if ( !a || a->a_numvals != 1 ) return LDAP_OBJECT_CLASS_VIOLATION; + + tier_impl = lload_tier_find( a->a_vals[0].bv_val ); + if ( !tier_impl ) { + Debug( LDAP_DEBUG_ANY, "lload_tier_ldadd: " + "tier type %s not recongnised\n", + bv.bv_val ); + return LDAP_OTHER; + } + + tier = tier_impl->tier_init(); + if ( !tier ) { + return LDAP_OTHER; + } + + ber_dupbv( &tier->t_name, &bv ); + + ca->bi = p->ce_bi; + ca->ca_private = tier; + + if ( !lloadd_inited ) { + if ( LDAP_STAILQ_EMPTY( &tiers ) ) { + LDAP_STAILQ_INSERT_HEAD( &tiers, tier, t_next ); + } else { + LDAP_STAILQ_INSERT_TAIL( &tiers, tier, t_next ); + } + } + + /* ca cleanups are only run in the case of online config but we use it to + * save the new config when done with the entry */ + ca->lineno = 0; + + lload_change.type = LLOAD_CHANGE_ADD; + lload_change.object = LLOAD_TIER; + lload_change.target = tier; + + return LDAP_SUCCESS; +} + +static int +lload_backend_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca ) +{ + LloadTier *tier = p->ce_private; + LloadBackend *b; + Attribute *a; + AttributeDescription *ad = NULL; + struct berval bv, type, rdn; + const char *text; + char *name; + + Debug( LDAP_DEBUG_TRACE, "lload_backend_ldadd: " + "a new backend-server is being added\n" ); + + if ( p->ce_type != Cft_Misc || !p->ce_bi || + p->ce_bi->bi_cf_ocs != lloadocs ) + return LDAP_CONSTRAINT_VIOLATION; + + dnRdn( &e->e_name, &rdn ); + type.bv_len = strchr( rdn.bv_val, '=' ) - rdn.bv_val; + type.bv_val = rdn.bv_val; + + /* Find attr */ + slap_bv2ad( &type, &ad, &text ); + if ( ad != slap_schema.si_ad_cn ) return LDAP_NAMING_VIOLATION; + + a = attr_find( e->e_attrs, ad ); + if ( !a || a->a_numvals != 1 ) return LDAP_NAMING_VIOLATION; + bv = a->a_vals[0]; + + if ( bv.bv_val[0] == '{' && ( name = strchr( bv.bv_val, '}' ) ) ) { + name++; + bv.bv_len -= name - bv.bv_val; + bv.bv_val = name; + } + + b = lload_backend_new(); + ber_dupbv( &b->b_name, &bv ); + b->b_tier = tier; + + ca->bi = p->ce_bi; + ca->ca_private = b; + config_push_cleanup( ca, lload_backend_finish ); + + /* ca cleanups are only run in the case of online config but we use it to + * save the new config when done with the entry */ + ca->lineno = 0; + + lload_change.type = LLOAD_CHANGE_ADD; + lload_change.object = LLOAD_BACKEND; + lload_change.target = b; + + return LDAP_SUCCESS; +} + +#ifdef SLAP_CONFIG_DELETE +static int +lload_backend_lddel( CfEntryInfo *ce, Operation *op ) +{ + LloadBackend *b = ce->ce_private; + + lload_change.type = LLOAD_CHANGE_DEL; + lload_change.object = LLOAD_BACKEND; + lload_change.target = b; + + return LDAP_SUCCESS; +} + +static int +lload_tier_lddel( CfEntryInfo *ce, Operation *op ) +{ + LloadTier *tier = ce->ce_private; + + lload_change.type = LLOAD_CHANGE_DEL; + lload_change.object = LLOAD_TIER; + lload_change.target = tier; + + return LDAP_SUCCESS; +} +#endif /* SLAP_CONFIG_DELETE */ + +static int +lload_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *c ) +{ + struct berval bv; + LloadTier *tier; + int i = 0; + + bv.bv_val = c->cr_msg; + LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) { + LloadBackend *b; + ConfigOCs *coc; + Entry *e; + int j = 0; + + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), + "cn=" SLAP_X_ORDERED_FMT "%s", i, tier->t_name.bv_val ); + + c->ca_private = tier; + c->valx = i; + + for ( coc = lloadocs; coc->co_type; coc++ ) { + if ( !ber_bvcmp( coc->co_name, &tier->t_type.tier_oc ) ) { + break; + } + } + assert( coc->co_type ); + + e = config_build_entry( op, rs, p->e_private, c, &bv, coc, NULL ); + if ( !e ) { + return 1; + } + + LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) { + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), + "cn=" SLAP_X_ORDERED_FMT "%s", j, b->b_name.bv_val ); + + for ( coc = lloadocs; coc->co_type; coc++ ) { + if ( !ber_bvcmp( + coc->co_name, &tier->t_type.tier_backend_oc ) ) { + break; + } + } + assert( coc->co_type ); + + c->ca_private = b; + c->valx = j; + + if ( !config_build_entry( + op, rs, e->e_private, c, &bv, coc, NULL ) ) { + return 1; + } + + j++; + } + + i++; + } + return LDAP_SUCCESS; +} +#endif /* BALANCER_MODULE */ diff --git a/servers/lloadd/connection.c b/servers/lloadd/connection.c new file mode 100644 index 0000000..a3f5323 --- /dev/null +++ b/servers/lloadd/connection.c @@ -0,0 +1,644 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2022 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 + * . + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include +#ifdef HAVE_LIMITS_H +#include +#endif + +#include +#include +#include +#include +#include + +#include "lload.h" + +#include "lutil.h" +#include "lutil_ldap.h" + +static unsigned long conn_nextid = 0; + +static void +lload_connection_assign_nextid( LloadConnection *conn ) +{ + conn->c_connid = __atomic_fetch_add( &conn_nextid, 1, __ATOMIC_RELAXED ); +} + +/* + * We start off with the connection muted and c_currentber holding the pdu we + * received. + * + * We run c->c_pdu_cb for each pdu, stopping once we hit an error, have to wait + * on reading or after we process lload_conn_max_pdus_per_cycle pdus so as to + * maintain fairness and not hog the worker thread forever. + * + * If we've run out of pdus immediately available from the stream or hit the + * budget, we unmute the connection. + * + * c->c_pdu_cb might return an 'error' and not free the connection. That can + * happen when changing the state or when client is blocked on writing and + * already has a pdu pending on the same operation, it's their job to make sure + * we're woken up again. + */ +void * +handle_pdus( void *ctx, void *arg ) +{ + LloadConnection *c = arg; + int pdus_handled = 0; + epoch_t epoch; + + /* A reference was passed on to us */ + assert( IS_ALIVE( c, c_refcnt ) ); + + epoch = epoch_join(); + for ( ;; ) { + BerElement *ber; + ber_tag_t tag; + ber_len_t len; + + if ( c->c_pdu_cb( c ) ) { + /* Error/reset, get rid ouf our reference and bail */ + goto done; + } + + if ( !IS_ALIVE( c, c_live ) ) { + break; + } + + if ( ++pdus_handled >= lload_conn_max_pdus_per_cycle ) { + /* Do not read now, re-enable read event instead */ + break; + } + + ber = c->c_currentber; + if ( ber == NULL && (ber = ber_alloc()) == NULL ) { + Debug( LDAP_DEBUG_ANY, "handle_pdus: " + "connid=%lu, ber_alloc failed\n", + c->c_connid ); + CONNECTION_LOCK_DESTROY(c); + goto done; + } + c->c_currentber = ber; + + checked_lock( &c->c_io_mutex ); + if ( (lload_features & LLOAD_FEATURE_PAUSE) && + (c->c_io_state & LLOAD_C_READ_PAUSE) ) { + goto pause; + } + tag = ber_get_next( c->c_sb, &len, ber ); + checked_unlock( &c->c_io_mutex ); + if ( tag != LDAP_TAG_MESSAGE ) { + int err = sock_errno(); + + if ( err != EWOULDBLOCK && err != EAGAIN ) { + if ( err || tag == LBER_ERROR ) { + char ebuf[128]; + Debug( LDAP_DEBUG_ANY, "handle_pdus: " + "ber_get_next on fd=%d failed errno=%d (%s)\n", + c->c_fd, err, + sock_errstr( err, ebuf, sizeof(ebuf) ) ); + } else { + Debug( LDAP_DEBUG_STATS, "handle_pdus: " + "ber_get_next on fd=%d connid=%lu received " + "a strange PDU tag=%lx\n", + c->c_fd, c->c_connid, tag ); + } + + c->c_currentber = NULL; + ber_free( ber, 1 ); + CONNECTION_LOCK_DESTROY(c); + goto done; + } + break; + } + + assert( IS_ALIVE( c, c_refcnt ) ); + epoch_leave( epoch ); + epoch = epoch_join(); + assert( IS_ALIVE( c, c_refcnt ) ); + } + + checked_lock( &c->c_io_mutex ); + if ( !(lload_features & LLOAD_FEATURE_PAUSE) || + !(c->c_io_state & LLOAD_C_READ_PAUSE) ) { + event_add( c->c_read_event, c->c_read_timeout ); + Debug( LDAP_DEBUG_CONNS, "handle_pdus: " + "re-enabled read event on connid=%lu\n", + c->c_connid ); + } +pause: + c->c_io_state &= ~LLOAD_C_READ_HANDOVER; + checked_unlock( &c->c_io_mutex ); + +done: + RELEASE_REF( c, c_refcnt, c->c_destroy ); + epoch_leave( epoch ); + return NULL; +} + +/* + * Initial read on the connection, if we get an LDAP PDU, submit the + * processing of this and successive ones to the work queue. + * + * If we can't submit it to the queue (overload), process this one and return + * to the event loop immediately after. + */ +void +connection_read_cb( evutil_socket_t s, short what, void *arg ) +{ + LloadConnection *c = arg; + BerElement *ber; + ber_tag_t tag; + ber_len_t len; + epoch_t epoch; + int pause; + + if ( !IS_ALIVE( c, c_live ) ) { + event_del( c->c_read_event ); + Debug( LDAP_DEBUG_CONNS, "connection_read_cb: " + "suspended read event on a dead connid=%lu\n", + c->c_connid ); + return; + } + + if ( what & EV_TIMEOUT ) { + Debug( LDAP_DEBUG_CONNS, "connection_read_cb: " + "connid=%lu, timeout reached, destroying\n", + c->c_connid ); + /* Make sure the connection stays around for us to unlock it */ + epoch = epoch_join(); + CONNECTION_LOCK_DESTROY(c); + epoch_leave( epoch ); + return; + } + + if ( !acquire_ref( &c->c_refcnt ) ) { + return; + } + epoch = epoch_join(); + + Debug( LDAP_DEBUG_CONNS, "connection_read_cb: " + "connection connid=%lu ready to read\n", + c->c_connid ); + + ber = c->c_currentber; + if ( ber == NULL && (ber = ber_alloc()) == NULL ) { + Debug( LDAP_DEBUG_ANY, "connection_read_cb: " + "connid=%lu, ber_alloc failed\n", + c->c_connid ); + goto out; + } + c->c_currentber = ber; + + checked_lock( &c->c_io_mutex ); + assert( !(c->c_io_state & LLOAD_C_READ_HANDOVER) ); + tag = ber_get_next( c->c_sb, &len, ber ); + pause = c->c_io_state & LLOAD_C_READ_PAUSE; + checked_unlock( &c->c_io_mutex ); + + if ( tag != LDAP_TAG_MESSAGE ) { + int err = sock_errno(); + + if ( err != EWOULDBLOCK && err != EAGAIN ) { + if ( err || tag == LBER_ERROR ) { + char ebuf[128]; + Debug( LDAP_DEBUG_STATS, "connection_read_cb: " + "ber_get_next on fd=%d failed errno=%d (%s)\n", + c->c_fd, err, + sock_errstr( err, ebuf, sizeof(ebuf) ) ); + } else { + Debug( LDAP_DEBUG_STATS, "connection_read_cb: " + "ber_get_next on fd=%d connid=%lu received " + "a strange PDU tag=%lx\n", + c->c_fd, c->c_connid, tag ); + } + + c->c_currentber = NULL; + ber_free( ber, 1 ); + + event_del( c->c_read_event ); + Debug( LDAP_DEBUG_CONNS, "connection_read_cb: " + "suspended read event on dying connid=%lu\n", + c->c_connid ); + CONNECTION_LOCK_DESTROY(c); + goto out; + } + if ( !(lload_features & LLOAD_FEATURE_PAUSE) || !pause ) { + event_add( c->c_read_event, c->c_read_timeout ); + Debug( LDAP_DEBUG_CONNS, "connection_read_cb: " + "re-enabled read event on connid=%lu\n", + c->c_connid ); + } + goto out; + } + + checked_lock( &c->c_io_mutex ); + c->c_io_state |= LLOAD_C_READ_HANDOVER; + checked_unlock( &c->c_io_mutex ); + event_del( c->c_read_event ); + + if ( !lload_conn_max_pdus_per_cycle || + ldap_pvt_thread_pool_submit( &connection_pool, handle_pdus, c ) ) { + /* If we're overloaded or configured as such, process one and resume in + * the next cycle. */ + int rc = c->c_pdu_cb( c ); + + checked_lock( &c->c_io_mutex ); + c->c_io_state &= ~LLOAD_C_READ_HANDOVER; + if ( rc == LDAP_SUCCESS && + ( !(lload_features & LLOAD_FEATURE_PAUSE) || + !(c->c_io_state & LLOAD_C_READ_PAUSE) ) ) { + event_add( c->c_read_event, c->c_read_timeout ); + } + checked_unlock( &c->c_io_mutex ); + goto out; + } + + Debug( LDAP_DEBUG_CONNS, "connection_read_cb: " + "suspended read event on connid=%lu\n", + c->c_connid ); + + /* + * We have scheduled a call to handle_pdus to take care of handling this + * and further requests, its reference is now owned by that task. + */ + epoch_leave( epoch ); + return; + +out: + RELEASE_REF( c, c_refcnt, c->c_destroy ); + epoch_leave( epoch ); +} + +void +connection_write_cb( evutil_socket_t s, short what, void *arg ) +{ + LloadConnection *c = arg; + epoch_t epoch; + + Debug( LDAP_DEBUG_CONNS, "connection_write_cb: " + "considering writing to%s connid=%lu what=%hd\n", + c->c_live ? " live" : " dead", c->c_connid, what ); + if ( !IS_ALIVE( c, c_live ) ) { + return; + } + + if ( what & EV_TIMEOUT ) { + Debug( LDAP_DEBUG_CONNS, "connection_write_cb: " + "connid=%lu, timeout reached, destroying\n", + c->c_connid ); + /* Make sure the connection stays around for us to unlock it */ + epoch = epoch_join(); + CONNECTION_LOCK_DESTROY(c); + epoch_leave( epoch ); + return; + } + + /* Before we acquire any locks */ + event_del( c->c_write_event ); + + if ( !acquire_ref( &c->c_refcnt ) ) { + return; + } + + /* If what == 0, we have a caller as opposed to being a callback */ + if ( what ) { + epoch = epoch_join(); + } + + checked_lock( &c->c_io_mutex ); + Debug( LDAP_DEBUG_CONNS, "connection_write_cb: " + "have something to write to connection connid=%lu\n", + c->c_connid ); + + /* We might have been beaten to flushing the data by another thread */ + if ( c->c_pendingber && ber_flush( c->c_sb, c->c_pendingber, 1 ) ) { + int err = sock_errno(); + + if ( err != EWOULDBLOCK && err != EAGAIN ) { + char ebuf[128]; + checked_unlock( &c->c_io_mutex ); + Debug( LDAP_DEBUG_ANY, "connection_write_cb: " + "ber_flush on fd=%d failed errno=%d (%s)\n", + c->c_fd, err, sock_errstr( err, ebuf, sizeof(ebuf) ) ); + CONNECTION_LOCK_DESTROY(c); + goto done; + } + + if ( !(c->c_io_state & LLOAD_C_READ_PAUSE) ) { + Debug( LDAP_DEBUG_CONNS, "connection_write_cb: " + "connection connid=%lu blocked on writing, marking " + "paused\n", + c->c_connid ); + } + c->c_io_state |= LLOAD_C_READ_PAUSE; + + /* TODO: Do not reset write timeout unless we wrote something */ + event_add( c->c_write_event, lload_write_timeout ); + } else { + c->c_pendingber = NULL; + if ( c->c_io_state & LLOAD_C_READ_PAUSE ) { + c->c_io_state ^= LLOAD_C_READ_PAUSE; + Debug( LDAP_DEBUG_CONNS, "connection_write_cb: " + "Unpausing connection connid=%lu\n", + c->c_connid ); + if ( !(c->c_io_state & LLOAD_C_READ_HANDOVER) ) { + event_add( c->c_read_event, c->c_read_timeout ); + } + } + } + checked_unlock( &c->c_io_mutex ); + +done: + RELEASE_REF( c, c_refcnt, c->c_destroy ); + if ( what ) { + epoch_leave( epoch ); + } +} + +void +connection_destroy( LloadConnection *c ) +{ + assert( c ); + Debug( LDAP_DEBUG_CONNS, "connection_destroy: " + "destroying connection connid=%lu\n", + c->c_connid ); + + CONNECTION_ASSERT_LOCKED(c); + assert( c->c_live == 0 ); + assert( c->c_refcnt == 0 ); + assert( c->c_state == LLOAD_C_INVALID ); + + ber_sockbuf_free( c->c_sb ); + + if ( c->c_currentber ) { + ber_free( c->c_currentber, 1 ); + c->c_currentber = NULL; + } + if ( c->c_pendingber ) { + ber_free( c->c_pendingber, 1 ); + c->c_pendingber = NULL; + } + + if ( !BER_BVISNULL( &c->c_sasl_bind_mech ) ) { + ber_memfree( c->c_sasl_bind_mech.bv_val ); + BER_BVZERO( &c->c_sasl_bind_mech ); + } +#ifdef HAVE_CYRUS_SASL + if ( c->c_sasl_defaults ) { + lutil_sasl_freedefs( c->c_sasl_defaults ); + c->c_sasl_defaults = NULL; + } + if ( c->c_sasl_authctx ) { +#ifdef SASL_CHANNEL_BINDING /* 2.1.25+ */ + if ( c->c_sasl_cbinding ) { + ch_free( c->c_sasl_cbinding ); + } +#endif + sasl_dispose( &c->c_sasl_authctx ); + } +#endif /* HAVE_CYRUS_SASL */ + + CONNECTION_UNLOCK(c); + + ldap_pvt_thread_mutex_destroy( &c->c_io_mutex ); + ldap_pvt_thread_mutex_destroy( &c->c_mutex ); + + ch_free( c ); + + listeners_reactivate(); +} + +/* + * Called holding mutex, will walk cq calling cb on all connections whose + * c_connid <= cq_last->c_connid that still exist at the time we get to them. + */ +void +connections_walk_last( + ldap_pvt_thread_mutex_t *cq_mutex, + lload_c_head *cq, + LloadConnection *cq_last, + CONNCB cb, + void *arg ) +{ + LloadConnection *c = cq_last; + uintptr_t last_connid; + + if ( LDAP_CIRCLEQ_EMPTY( cq ) ) { + return; + } + assert_locked( cq_mutex ); + + last_connid = c->c_connid; + c = LDAP_CIRCLEQ_LOOP_NEXT( cq, c, c_next ); + + while ( !acquire_ref( &c->c_refcnt ) ) { + c = LDAP_CIRCLEQ_LOOP_NEXT( cq, c, c_next ); + if ( c->c_connid >= last_connid ) { + assert_locked( cq_mutex ); + return; + } + } + + /* + * Notes: + * - we maintain the connections in the cq CIRCLEQ_ in ascending c_connid + * order + * - the connection with the highest c_connid is passed in cq_last + * - we can only use cq when we hold cq_mutex + * - connections might be added to or removed from cq while we're busy + * processing connections + * - we need a way to detect we've finished looping around cq for some + * definition of looping around + */ + do { + int rc; + + checked_unlock( cq_mutex ); + + rc = cb( c, arg ); + RELEASE_REF( c, c_refcnt, c->c_destroy ); + + checked_lock( cq_mutex ); + if ( rc || LDAP_CIRCLEQ_EMPTY( cq ) ) { + break; + } + + do { + LloadConnection *old = c; + c = LDAP_CIRCLEQ_LOOP_NEXT( cq, c, c_next ); + if ( c->c_connid <= old->c_connid || c->c_connid > last_connid ) { + assert_locked( cq_mutex ); + return; + } + } while ( !acquire_ref( &c->c_refcnt ) ); + } while ( c->c_connid <= last_connid ); + assert_locked( cq_mutex ); +} + +void +connections_walk( + ldap_pvt_thread_mutex_t *cq_mutex, + lload_c_head *cq, + CONNCB cb, + void *arg ) +{ + LloadConnection *cq_last = LDAP_CIRCLEQ_LAST( cq ); + return connections_walk_last( cq_mutex, cq, cq_last, cb, arg ); +} + +int +lload_connection_close( LloadConnection *c, void *arg ) +{ + int unlock, gentle = *(int *)arg; + LloadOperation *op; + + Debug( LDAP_DEBUG_CONNS, "lload_connection_close: " + "marking connection connid=%lu closing\n", + c->c_connid ); + + /* We were approached from the connection list or cn=monitor */ + assert( IS_ALIVE( c, c_refcnt ) ); + + /* Need to acquire this first, even if we won't need it */ + unlock = 1; + checked_lock( &c->c_io_mutex ); + CONNECTION_LOCK(c); + + /* Only if it's a usable client */ + if ( ( c->c_state == LLOAD_C_READY || c->c_state == LLOAD_C_BINDING ) && + c->c_destroy == client_destroy ) { + if ( c->c_pendingber != NULL || + (c->c_pendingber = ber_alloc()) != NULL ) { + ber_printf( c->c_pendingber, "t{tit{essts}}", LDAP_TAG_MESSAGE, + LDAP_TAG_MSGID, LDAP_RES_UNSOLICITED, + LDAP_RES_EXTENDED, LDAP_UNAVAILABLE, "", + "connection closing", + LDAP_TAG_EXOP_RES_OID, LDAP_NOTICE_OF_DISCONNECTION ); + unlock = 0; + checked_unlock( &c->c_io_mutex ); + CONNECTION_UNLOCK(c); + connection_write_cb( -1, 0, c ); + CONNECTION_LOCK(c); + } + } + if ( unlock ) + checked_unlock( &c->c_io_mutex ); + + if ( !gentle || !c->c_ops ) { + CONNECTION_DESTROY(c); + return LDAP_SUCCESS; + } + + /* The first thing we do is make sure we don't get new Operations in */ + c->c_state = LLOAD_C_CLOSING; + + do { + TAvlnode *node = ldap_tavl_end( c->c_ops, TAVL_DIR_LEFT ); + op = node->avl_data; + + /* Close operations that would need client action to resolve, + * only SASL binds in progress do that right now */ + if ( op->o_client_msgid || op->o_upstream_msgid ) { + break; + } + + CONNECTION_UNLOCK(c); + OPERATION_UNLINK(op); + CONNECTION_LOCK(c); + } while ( c->c_ops ); + + CONNECTION_UNLOCK(c); + return LDAP_SUCCESS; +} + +LloadConnection * +lload_connection_init( ber_socket_t s, const char *peername, int flags ) +{ + LloadConnection *c; + + assert( peername != NULL ); + + if ( s == AC_SOCKET_INVALID ) { + Debug( LDAP_DEBUG_ANY, "lload_connection_init: " + "init of socket fd=%ld invalid\n", + (long)s ); + return NULL; + } + + assert( s >= 0 ); + + c = ch_calloc( 1, sizeof(LloadConnection) ); + + c->c_fd = s; + c->c_sb = ber_sockbuf_alloc(); + ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_FD, &s ); + +#ifdef LDAP_PF_LOCAL + if ( flags & CONN_IS_IPC ) { +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_debug, + LBER_SBIOD_LEVEL_PROVIDER, (void *)"ipc_" ); +#endif + ber_sockbuf_add_io( c->c_sb, &ber_sockbuf_io_fd, + LBER_SBIOD_LEVEL_PROVIDER, (void *)&s ); + } else +#endif /* LDAP_PF_LOCAL */ + { +#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 ); + } + +#ifdef LDAP_DEBUG + ber_sockbuf_add_io( + c->c_sb, &ber_sockbuf_io_debug, INT_MAX, (void *)"lload_" ); +#endif + + c->c_next_msgid = 1; + c->c_refcnt = c->c_live = 1; + c->c_destroy = connection_destroy; + + LDAP_CIRCLEQ_ENTRY_INIT( c, c_next ); + + ldap_pvt_thread_mutex_init( &c->c_mutex ); + ldap_pvt_thread_mutex_init( &c->c_io_mutex ); + + lload_connection_assign_nextid( c ); + + Debug( LDAP_DEBUG_CONNS, "lload_connection_init: " + "connection connid=%lu allocated for socket fd=%d peername=%s\n", + c->c_connid, s, peername ); + + c->c_state = LLOAD_C_ACTIVE; + + return c; +} diff --git a/servers/lloadd/daemon.c b/servers/lloadd/daemon.c new file mode 100644 index 0000000..a93879e --- /dev/null +++ b/servers/lloadd/daemon.c @@ -0,0 +1,1978 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2022 The OpenLDAP Foundation. + * Portions Copyright 2007 by Howard Chu, Symas Corporation. + * 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 + * . + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "lload.h" +#include "ldap_pvt_thread.h" +#include "lutil.h" + +#include "ldap_rq.h" + +#ifdef HAVE_SYSTEMD_SD_DAEMON_H +#include +#endif + +#ifdef LDAP_PF_LOCAL +#include +/* this should go in as soon as it is accepted */ +#define LDAPI_MOD_URLEXT "x-mod" +#endif /* LDAP_PF_LOCAL */ + +#ifndef BALANCER_MODULE +#ifdef LDAP_PF_INET6 +int slap_inet4or6 = AF_UNSPEC; +#else /* ! INETv6 */ +int slap_inet4or6 = AF_INET; +#endif /* ! INETv6 */ + +/* globals */ +time_t starttime; +struct runqueue_s slapd_rq; + +#ifdef LDAP_TCP_BUFFER +int slapd_tcp_rmem; +int slapd_tcp_wmem; +#endif /* LDAP_TCP_BUFFER */ + +volatile sig_atomic_t slapd_shutdown = 0; +volatile sig_atomic_t slapd_gentle_shutdown = 0; +volatile sig_atomic_t slapd_abrupt_shutdown = 0; +#endif /* !BALANCER_MODULE */ + +static int emfile; + +ldap_pvt_thread_mutex_t lload_wait_mutex; +ldap_pvt_thread_cond_t lload_wait_cond; +ldap_pvt_thread_cond_t lload_pause_cond; + +#ifndef SLAPD_MAX_DAEMON_THREADS +#define SLAPD_MAX_DAEMON_THREADS 16 +#endif +int lload_daemon_threads = 1; +int lload_daemon_mask; + +struct event_base *listener_base = NULL; +LloadListener **lload_listeners = NULL; +static ldap_pvt_thread_t listener_tid, *daemon_tid; + +#ifndef RESOLV_CONF_PATH +#define RESOLV_CONF_PATH "/etc/resolv.conf" +#endif +char *lload_resolvconf_path = RESOLV_CONF_PATH; + +struct event_base *daemon_base = NULL; +struct evdns_base *dnsbase; + +struct event *lload_timeout_event; +struct event *lload_stats_event; + +/* + * global lload statistics. Not mutex protected to preserve performance - + * increment is atomic, at most we risk a bit of inconsistency + */ +lload_global_stats_t lload_stats = {}; + +#ifndef SLAPD_LISTEN_BACKLOG +#define SLAPD_LISTEN_BACKLOG 1024 +#endif /* ! SLAPD_LISTEN_BACKLOG */ + +#define DAEMON_ID(fd) ( fd & lload_daemon_mask ) + +#ifdef HAVE_WINSOCK +ldap_pvt_thread_mutex_t slapd_ws_mutex; +SOCKET *slapd_ws_sockets; +#define SD_READ 1 +#define SD_WRITE 2 +#define SD_ACTIVE 4 +#define SD_LISTENER 8 +#endif + +#ifdef HAVE_TCPD +static ldap_pvt_thread_mutex_t sd_tcpd_mutex; +#endif /* TCP Wrappers */ + +typedef struct listener_item { + struct evconnlistener *listener; + ber_socket_t fd; +} listener_item; + +typedef struct lload_daemon_st { + ldap_pvt_thread_mutex_t sd_mutex; + + struct event_base *base; + struct event *wakeup_event; +} lload_daemon_st; + +static lload_daemon_st lload_daemon[SLAPD_MAX_DAEMON_THREADS]; + +static void daemon_wakeup_cb( evutil_socket_t sig, short what, void *arg ); + +static void +lloadd_close( ber_socket_t s ) +{ + Debug( LDAP_DEBUG_CONNS, "lloadd_close: " + "closing fd=%ld\n", + (long)s ); + tcp_close( s ); +} + +static void +lload_free_listener_addresses( struct sockaddr **sal ) +{ + struct sockaddr **sap; + if ( sal == NULL ) return; + for ( sap = sal; *sap != NULL; sap++ ) + ch_free(*sap); + ch_free( sal ); +} + +#if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD) +static int +get_url_perms( char **exts, mode_t *perms, int *crit ) +{ + int i; + + assert( exts != NULL ); + assert( perms != NULL ); + assert( crit != NULL ); + + *crit = 0; + for ( i = 0; exts[i]; i++ ) { + char *type = exts[i]; + int c = 0; + + if ( type[0] == '!' ) { + c = 1; + type++; + } + + if ( strncasecmp( type, LDAPI_MOD_URLEXT "=", + sizeof(LDAPI_MOD_URLEXT "=") - 1 ) == 0 ) { + char *value = type + ( sizeof(LDAPI_MOD_URLEXT "=") - 1 ); + mode_t p = 0; + int j; + + switch ( strlen( value ) ) { + case 4: + /* skip leading '0' */ + if ( value[0] != '0' ) return LDAP_OTHER; + value++; + + case 3: + for ( j = 0; j < 3; j++ ) { + int v; + + v = value[j] - '0'; + + if ( v < 0 || v > 7 ) return LDAP_OTHER; + + p |= v << 3 * ( 2 - j ); + } + break; + + case 10: + for ( j = 1; j < 10; j++ ) { + static mode_t m[] = { 0, S_IRUSR, S_IWUSR, S_IXUSR, + S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, + S_IXOTH }; + static const char c[] = "-rwxrwxrwx"; + + if ( value[j] == c[j] ) { + p |= m[j]; + + } else if ( value[j] != '-' ) { + return LDAP_OTHER; + } + } + break; + + default: + return LDAP_OTHER; + } + + *crit = c; + *perms = p; + + return LDAP_SUCCESS; + } + } + + return LDAP_OTHER; +} +#endif /* LDAP_PF_LOCAL || SLAP_X_LISTENER_MOD */ + +/* port = 0 indicates AF_LOCAL */ +static int +lload_get_listener_addresses( + const char *host, + unsigned short port, + struct sockaddr ***sal ) +{ + struct sockaddr **sap; + +#ifdef LDAP_PF_LOCAL + if ( port == 0 ) { + sap = *sal = ch_malloc( 2 * sizeof(void *) ); + + *sap = ch_calloc( 1, sizeof(struct sockaddr_un) ); + sap[1] = NULL; + + if ( strlen( host ) > + ( sizeof( ((struct sockaddr_un *)*sap)->sun_path ) - 1 ) ) { + Debug( LDAP_DEBUG_ANY, "lload_get_listener_addresses: " + "domain socket path (%s) too long in URL\n", + host ); + goto errexit; + } + + (*sap)->sa_family = AF_LOCAL; + strcpy( ((struct sockaddr_un *)*sap)->sun_path, host ); + } else +#endif /* LDAP_PF_LOCAL */ + { +#ifdef HAVE_GETADDRINFO + struct addrinfo hints, *res, *sai; + int n, err; + char serv[7]; + + memset( &hints, '\0', sizeof(hints) ); + hints.ai_flags = AI_PASSIVE; + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = slap_inet4or6; + snprintf( serv, sizeof(serv), "%d", port ); + + if ( (err = getaddrinfo( host, serv, &hints, &res )) ) { + Debug( LDAP_DEBUG_ANY, "lload_get_listener_addresses: " + "getaddrinfo() failed: %s\n", + AC_GAI_STRERROR(err) ); + return -1; + } + + sai = res; + for ( n = 2; ( sai = sai->ai_next ) != NULL; n++ ) { + /* EMPTY */; + } + sap = *sal = ch_calloc( n, sizeof(void *) ); + + *sap = NULL; + + for ( sai = res; sai; sai = sai->ai_next ) { + if ( sai->ai_addr == NULL ) { + Debug( LDAP_DEBUG_ANY, "lload_get_listener_addresses: " + "getaddrinfo ai_addr is NULL?\n" ); + freeaddrinfo( res ); + goto errexit; + } + + switch ( sai->ai_family ) { +#ifdef LDAP_PF_INET6 + case AF_INET6: + *sap = ch_malloc( sizeof(struct sockaddr_in6) ); + *(struct sockaddr_in6 *)*sap = + *((struct sockaddr_in6 *)sai->ai_addr); + break; +#endif /* LDAP_PF_INET6 */ + case AF_INET: + *sap = ch_malloc( sizeof(struct sockaddr_in) ); + *(struct sockaddr_in *)*sap = + *((struct sockaddr_in *)sai->ai_addr); + break; + default: + *sap = NULL; + break; + } + + if ( *sap != NULL ) { + (*sap)->sa_family = sai->ai_family; + sap++; + *sap = NULL; + } + } + + freeaddrinfo( res ); + +#else /* ! HAVE_GETADDRINFO */ + int i, n = 1; + struct in_addr in; + struct hostent *he = NULL; + + if ( host == NULL ) { + in.s_addr = htonl( INADDR_ANY ); + + } else if ( !inet_aton( host, &in ) ) { + he = gethostbyname( host ); + if ( he == NULL ) { + Debug( LDAP_DEBUG_ANY, "lload_get_listener_addresses: " + "invalid host %s\n", + host ); + return -1; + } + for ( n = 0; he->h_addr_list[n]; n++ ) /* empty */; + } + + sap = *sal = ch_malloc( ( n + 1 ) * sizeof(void *) ); + + for ( i = 0; i < n; i++ ) { + sap[i] = ch_calloc( 1, sizeof(struct sockaddr_in) ); + sap[i]->sa_family = AF_INET; + ((struct sockaddr_in *)sap[i])->sin_port = htons( port ); + AC_MEMCPY( &((struct sockaddr_in *)sap[i])->sin_addr, + he ? (struct in_addr *)he->h_addr_list[i] : &in, + sizeof(struct in_addr) ); + } + sap[i] = NULL; +#endif /* ! HAVE_GETADDRINFO */ + } + + return 0; + +errexit: + lload_free_listener_addresses(*sal); + return -1; +} + +static int +lload_open_listener( + const char *url, + LDAPURLDesc *lud, + int *listeners, + int *cur ) +{ + int num, tmp, rc; + LloadListener l; + LloadListener *li; + unsigned short port; + int err, addrlen = 0; + struct sockaddr **sal = NULL, **psal; + int socktype = SOCK_STREAM; /* default to COTS */ + ber_socket_t s; + char ebuf[128]; + +#if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD) + /* + * use safe defaults + */ + int crit = 1; +#endif /* LDAP_PF_LOCAL || SLAP_X_LISTENER_MOD */ + + assert( url ); + assert( lud ); + + l.sl_url.bv_val = NULL; + l.sl_mute = 0; + l.sl_busy = 0; + +#ifndef HAVE_TLS + if ( ldap_pvt_url_scheme2tls( lud->lud_scheme ) ) { + Debug( LDAP_DEBUG_ANY, "lload_open_listener: " + "TLS not supported (%s)\n", + url ); + ldap_free_urldesc( lud ); + return -1; + } + + if ( !lud->lud_port ) lud->lud_port = LDAP_PORT; + +#else /* HAVE_TLS */ + 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; +#endif /* LDAP_TCP_BUFFER */ + + port = (unsigned short)lud->lud_port; + + tmp = ldap_pvt_url_scheme2proto( lud->lud_scheme ); + if ( tmp == LDAP_PROTO_IPC ) { +#ifdef LDAP_PF_LOCAL + if ( lud->lud_host == NULL || lud->lud_host[0] == '\0' ) { + err = lload_get_listener_addresses( LDAPI_SOCK, 0, &sal ); + } else { + err = lload_get_listener_addresses( lud->lud_host, 0, &sal ); + } +#else /* ! LDAP_PF_LOCAL */ + + Debug( LDAP_DEBUG_ANY, "lload_open_listener: " + "URL scheme not supported: %s\n", + url ); + ldap_free_urldesc( lud ); + return -1; +#endif /* ! LDAP_PF_LOCAL */ + } else { + if ( lud->lud_host == NULL || lud->lud_host[0] == '\0' || + strcmp( lud->lud_host, "*" ) == 0 ) { + err = lload_get_listener_addresses( NULL, port, &sal ); + } else { + err = lload_get_listener_addresses( lud->lud_host, port, &sal ); + } + } + +#if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD) + if ( lud->lud_exts ) { + err = get_url_perms( lud->lud_exts, &l.sl_perms, &crit ); + } else { + l.sl_perms = S_IRWXU | S_IRWXO; + } +#endif /* LDAP_PF_LOCAL || SLAP_X_LISTENER_MOD */ + + ldap_free_urldesc( lud ); + if ( err ) { + lload_free_listener_addresses( sal ); + return -1; + } + + /* If we got more than one address returned, we need to make space + * for it in the lload_listeners array. + */ + for ( num = 0; sal[num]; num++ ) /* empty */; + if ( num > 1 ) { + *listeners += num - 1; + lload_listeners = ch_realloc( lload_listeners, + ( *listeners + 1 ) * sizeof(LloadListener *) ); + } + + psal = sal; + while ( *sal != NULL ) { + char *af; + switch ( (*sal)->sa_family ) { + case AF_INET: + af = "IPv4"; + break; +#ifdef LDAP_PF_INET6 + case AF_INET6: + af = "IPv6"; + break; +#endif /* LDAP_PF_INET6 */ +#ifdef LDAP_PF_LOCAL + case AF_LOCAL: + af = "Local"; + break; +#endif /* LDAP_PF_LOCAL */ + default: + sal++; + continue; + } + + s = socket( (*sal)->sa_family, socktype, 0 ); + if ( s == AC_SOCKET_INVALID ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "lload_open_listener: " + "%s socket() failed errno=%d (%s)\n", + af, err, sock_errstr( err, ebuf, sizeof(ebuf) ) ); + sal++; + continue; + } + ber_pvt_socket_set_nonblock( s, 1 ); + l.sl_sd = s; + +#ifdef LDAP_PF_LOCAL + if ( (*sal)->sa_family == AF_LOCAL ) { + unlink( ((struct sockaddr_un *)*sal)->sun_path ); + } else +#endif /* LDAP_PF_LOCAL */ + { +#ifdef SO_REUSEADDR + /* enable address reuse */ + tmp = 1; + rc = setsockopt( + s, SOL_SOCKET, SO_REUSEADDR, (char *)&tmp, sizeof(tmp) ); + if ( rc == AC_SOCKET_ERROR ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "lload_open_listener(%ld): " + "setsockopt(SO_REUSEADDR) failed errno=%d (%s)\n", + (long)l.sl_sd, err, + sock_errstr( err, ebuf, sizeof(ebuf) ) ); + } +#endif /* SO_REUSEADDR */ + } + + switch ( (*sal)->sa_family ) { + case AF_INET: + addrlen = sizeof(struct sockaddr_in); + break; +#ifdef LDAP_PF_INET6 + case AF_INET6: +#ifdef IPV6_V6ONLY + /* Try to use IPv6 sockets for IPv6 only */ + tmp = 1; + rc = setsockopt( s, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&tmp, + sizeof(tmp) ); + if ( rc == AC_SOCKET_ERROR ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "lload_open_listener(%ld): " + "setsockopt(IPV6_V6ONLY) failed errno=%d (%s)\n", + (long)l.sl_sd, err, + sock_errstr( err, ebuf, sizeof(ebuf) ) ); + } +#endif /* IPV6_V6ONLY */ + addrlen = sizeof(struct sockaddr_in6); + break; +#endif /* LDAP_PF_INET6 */ + +#ifdef LDAP_PF_LOCAL + case AF_LOCAL: +#ifdef LOCAL_CREDS + { + int one = 1; + setsockopt( s, 0, LOCAL_CREDS, &one, sizeof(one) ); + } +#endif /* LOCAL_CREDS */ + + addrlen = sizeof(struct sockaddr_un); + break; +#endif /* LDAP_PF_LOCAL */ + } + +#ifdef LDAP_PF_LOCAL + /* create socket with all permissions set for those systems + * that honor permissions on sockets (e.g. Linux); typically, + * only write is required. To exploit filesystem permissions, + * place the socket in a directory and use directory's + * permissions. Need write perms to the directory to + * create/unlink the socket; likely need exec perms to access + * the socket (ITS#4709) */ + { + mode_t old_umask = 0; + + if ( (*sal)->sa_family == AF_LOCAL ) { + old_umask = umask( 0 ); + } +#endif /* LDAP_PF_LOCAL */ + rc = bind( s, *sal, addrlen ); +#ifdef LDAP_PF_LOCAL + if ( old_umask != 0 ) { + umask( old_umask ); + } + } +#endif /* LDAP_PF_LOCAL */ + if ( rc ) { + err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "lload_open_listener: " + "bind(%ld) failed errno=%d (%s)\n", + (long)l.sl_sd, err, + sock_errstr( err, ebuf, sizeof(ebuf) ) ); + tcp_close( s ); + sal++; + continue; + } + + switch ( (*sal)->sa_family ) { +#ifdef LDAP_PF_LOCAL + case AF_LOCAL: { + char *path = ((struct sockaddr_un *)*sal)->sun_path; + l.sl_name.bv_len = strlen( path ) + STRLENOF("PATH="); + l.sl_name.bv_val = ch_malloc( l.sl_name.bv_len + 1 ); + snprintf( l.sl_name.bv_val, l.sl_name.bv_len + 1, "PATH=%s", + path ); + } break; +#endif /* LDAP_PF_LOCAL */ + + case AF_INET: { + char addr[INET_ADDRSTRLEN]; + const char *s; +#if defined(HAVE_GETADDRINFO) && defined(HAVE_INET_NTOP) + s = inet_ntop( AF_INET, + &((struct sockaddr_in *)*sal)->sin_addr, addr, + sizeof(addr) ); +#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + s = inet_ntoa( ((struct sockaddr_in *)*sal)->sin_addr ); +#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + if ( !s ) s = SLAP_STRING_UNKNOWN; + port = ntohs( ((struct sockaddr_in *)*sal)->sin_port ); + l.sl_name.bv_val = + ch_malloc( sizeof("IP=255.255.255.255:65535") ); + snprintf( l.sl_name.bv_val, + sizeof("IP=255.255.255.255:65535"), "IP=%s:%d", s, + port ); + l.sl_name.bv_len = strlen( l.sl_name.bv_val ); + } break; + +#ifdef LDAP_PF_INET6 + case AF_INET6: { + char addr[INET6_ADDRSTRLEN]; + const char *s; + s = inet_ntop( AF_INET6, + &((struct sockaddr_in6 *)*sal)->sin6_addr, addr, + sizeof(addr) ); + if ( !s ) s = SLAP_STRING_UNKNOWN; + port = ntohs( ((struct sockaddr_in6 *)*sal)->sin6_port ); + l.sl_name.bv_len = strlen( s ) + sizeof("IP=[]:65535"); + l.sl_name.bv_val = ch_malloc( l.sl_name.bv_len ); + snprintf( l.sl_name.bv_val, l.sl_name.bv_len, "IP=[%s]:%d", s, + port ); + l.sl_name.bv_len = strlen( l.sl_name.bv_val ); + } break; +#endif /* LDAP_PF_INET6 */ + + default: + Debug( LDAP_DEBUG_ANY, "lload_open_listener: " + "unsupported address family (%d)\n", + (int)(*sal)->sa_family ); + break; + } + + AC_MEMCPY( &l.sl_sa, *sal, addrlen ); + ber_str2bv( url, 0, 1, &l.sl_url ); + li = ch_malloc( sizeof(LloadListener) ); + *li = l; + lload_listeners[*cur] = li; + (*cur)++; + sal++; + } + + lload_free_listener_addresses( psal ); + + if ( l.sl_url.bv_val == NULL ) { + Debug( LDAP_DEBUG_ANY, "lload_open_listener: " + "failed on %s\n", + url ); + return -1; + } + + Debug( LDAP_DEBUG_TRACE, "lload_open_listener: " + "listener initialized %s\n", + l.sl_url.bv_val ); + + return 0; +} + +int +lload_open_new_listener( const char *url, LDAPURLDesc *lud ) +{ + int rc, i, j = 0; + + for ( i = 0; lload_listeners && lload_listeners[i] != NULL; + i++ ) /* count */ + ; + j = i; + + i++; + lload_listeners = ch_realloc( + lload_listeners, ( i + 1 ) * sizeof(LloadListener *) ); + + rc = lload_open_listener( url, lud, &i, &j ); + lload_listeners[j] = NULL; + return rc; +} + +int lloadd_inited = 0; + +int +lloadd_listeners_init( const char *urls ) +{ + int i, j, n; + char **u; + LDAPURLDesc *lud; + + Debug( LDAP_DEBUG_ARGS, "lloadd_listeners_init: %s\n", + urls ? urls : "" ); + +#ifdef HAVE_TCPD + ldap_pvt_thread_mutex_init( &sd_tcpd_mutex ); +#endif /* TCP Wrappers */ + + if ( urls == NULL ) urls = "ldap:///"; + + u = ldap_str2charray( urls, " " ); + + if ( u == NULL || u[0] == NULL ) { + Debug( LDAP_DEBUG_ANY, "lloadd_listeners_init: " + "no urls (%s) provided\n", + urls ); + if ( u ) ldap_charray_free( u ); + return -1; + } + + for ( i = 0; u[i] != NULL; i++ ) { + Debug( LDAP_DEBUG_TRACE, "lloadd_listeners_init: " + "listen on %s\n", + u[i] ); + } + + if ( i == 0 ) { + Debug( LDAP_DEBUG_ANY, "lloadd_listeners_init: " + "no listeners to open (%s)\n", + urls ); + ldap_charray_free( u ); + return -1; + } + + Debug( LDAP_DEBUG_TRACE, "lloadd_listeners_init: " + "%d listeners to open...\n", + i ); + lload_listeners = ch_malloc( ( i + 1 ) * sizeof(LloadListener *) ); + + for ( n = 0, j = 0; u[n]; n++ ) { + if ( ldap_url_parse_ext( u[n], &lud, LDAP_PVT_URL_PARSE_DEF_PORT ) ) { + Debug( LDAP_DEBUG_ANY, "lloadd_listeners_init: " + "could not parse url %s\n", + u[n] ); + ldap_charray_free( u ); + return -1; + } + + if ( lload_open_listener( u[n], lud, &i, &j ) ) { + ldap_charray_free( u ); + return -1; + } + } + lload_listeners[j] = NULL; + + Debug( LDAP_DEBUG_TRACE, "lloadd_listeners_init: " + "%d listeners opened\n", + i ); + + ldap_charray_free( u ); + + return !i; +} + +int +lloadd_daemon_destroy( void ) +{ + epoch_shutdown(); + if ( lloadd_inited ) { + int i; + + for ( i = 0; i < lload_daemon_threads; i++ ) { + ldap_pvt_thread_mutex_destroy( &lload_daemon[i].sd_mutex ); + if ( lload_daemon[i].wakeup_event ) { + event_free( lload_daemon[i].wakeup_event ); + } + if ( lload_daemon[i].base ) { + event_base_free( lload_daemon[i].base ); + } + } + + event_free( lload_stats_event ); + event_free( lload_timeout_event ); + + event_base_free( daemon_base ); + daemon_base = NULL; + + lloadd_inited = 0; +#ifdef HAVE_TCPD + ldap_pvt_thread_mutex_destroy( &sd_tcpd_mutex ); +#endif /* TCP Wrappers */ + } + + return 0; +} + +static void +destroy_listeners( void ) +{ + LloadListener *lr, **ll = lload_listeners; + + if ( ll == NULL ) return; + + ldap_pvt_thread_join( listener_tid, (void *)NULL ); + + while ( (lr = *ll++) != NULL ) { + if ( lr->sl_url.bv_val ) { + ber_memfree( lr->sl_url.bv_val ); + } + + if ( lr->sl_name.bv_val ) { + ber_memfree( lr->sl_name.bv_val ); + } + +#ifdef LDAP_PF_LOCAL + if ( lr->sl_sa.sa_addr.sa_family == AF_LOCAL ) { + unlink( lr->sl_sa.sa_un_addr.sun_path ); + } +#endif /* LDAP_PF_LOCAL */ + + evconnlistener_free( lr->listener ); + + free( lr ); + } + + free( lload_listeners ); + lload_listeners = NULL; + + if ( listener_base ) { + event_base_free( listener_base ); + } +} + +static void +lload_listener( + struct evconnlistener *listener, + ber_socket_t s, + struct sockaddr *a, + int len, + void *arg ) +{ + LloadListener *sl = arg; + LloadConnection *c; + Sockaddr *from = (Sockaddr *)a; + char peername[LDAP_IPADDRLEN]; + struct berval peerbv = BER_BVC(peername); + int cflag; + int tid; + char ebuf[128]; + + Debug( LDAP_DEBUG_TRACE, ">>> lload_listener(%s)\n", sl->sl_url.bv_val ); + + peername[0] = '\0'; + + /* Resume the listener FD to allow concurrent-processing of + * additional incoming connections. + */ + sl->sl_busy = 0; + + tid = DAEMON_ID(s); + + Debug( LDAP_DEBUG_CONNS, "lload_listener: " + "listen=%ld, new connection fd=%ld\n", + (long)sl->sl_sd, (long)s ); + +#if defined(SO_KEEPALIVE) || defined(TCP_NODELAY) +#ifdef LDAP_PF_LOCAL + /* for IPv4 and IPv6 sockets only */ + if ( from->sa_addr.sa_family != AF_LOCAL ) +#endif /* LDAP_PF_LOCAL */ + { + int rc; + int tmp; +#ifdef SO_KEEPALIVE + /* enable keep alives */ + tmp = 1; + rc = setsockopt( + s, SOL_SOCKET, SO_KEEPALIVE, (char *)&tmp, sizeof(tmp) ); + if ( rc == AC_SOCKET_ERROR ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "lload_listener(%ld): " + "setsockopt(SO_KEEPALIVE) failed errno=%d (%s)\n", + (long)s, err, sock_errstr( err, ebuf, sizeof(ebuf) ) ); + } +#endif /* SO_KEEPALIVE */ +#ifdef TCP_NODELAY + /* enable no delay */ + tmp = 1; + rc = setsockopt( + s, IPPROTO_TCP, TCP_NODELAY, (char *)&tmp, sizeof(tmp) ); + if ( rc == AC_SOCKET_ERROR ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "lload_listener(%ld): " + "setsockopt(TCP_NODELAY) failed errno=%d (%s)\n", + (long)s, err, sock_errstr( err, ebuf, sizeof(ebuf) ) ); + } +#endif /* TCP_NODELAY */ + } +#endif /* SO_KEEPALIVE || TCP_NODELAY */ + + if ( sl->sl_is_proxied ) { + if ( !proxyp( s, from ) ) { + Debug( LDAP_DEBUG_ANY, "lload_listener: " + "proxyp(%ld) failed\n", + (long)s ); + lloadd_close( s ); + return; + } + } + + cflag = 0; + switch ( from->sa_addr.sa_family ) { +#ifdef LDAP_PF_LOCAL + case AF_LOCAL: + cflag |= CONN_IS_IPC; + + /* FIXME: apparently accept doesn't fill the sun_path member */ + sprintf( peername, "PATH=%s", sl->sl_sa.sa_un_addr.sun_path ); + break; +#endif /* LDAP_PF_LOCAL */ + +#ifdef LDAP_PF_INET6 + case AF_INET6: +#endif /* LDAP_PF_INET6 */ + case AF_INET: + ldap_pvt_sockaddrstr( from, &peerbv ); + break; + + default: + lloadd_close( s ); + return; + } + +#ifdef HAVE_TLS + if ( sl->sl_is_tls ) cflag |= CONN_IS_TLS; +#endif + c = client_init( s, peername, lload_daemon[tid].base, cflag ); + + if ( !c ) { + Debug( LDAP_DEBUG_ANY, "lload_listener: " + "client_init(%ld, %s, %s) failed\n", + (long)s, peername, sl->sl_name.bv_val ); + lloadd_close( s ); + } + + return; +} + +static void * +lload_listener_thread( void *ctx ) +{ + /* ITS#9984 Survive the listeners being paused if we run out of fds */ + int rc = event_base_loop( listener_base, EVLOOP_NO_EXIT_ON_EMPTY ); + Debug( LDAP_DEBUG_ANY, "lload_listener_thread: " + "event loop finished: rc=%d\n", + rc ); + + return (void *)NULL; +} + +static void +listener_error_cb( struct evconnlistener *lev, void *arg ) +{ + LloadListener *l = arg; + int err = EVUTIL_SOCKET_ERROR(); + + assert( l->listener == lev ); + if ( +#ifdef EMFILE + err == EMFILE || +#endif /* EMFILE */ +#ifdef ENFILE + err == ENFILE || +#endif /* ENFILE */ + 0 ) { + ldap_pvt_thread_mutex_lock( &lload_daemon[0].sd_mutex ); + emfile++; + /* Stop listening until an existing session closes */ + l->sl_mute = 1; + evconnlistener_disable( lev ); + ldap_pvt_thread_mutex_unlock( &lload_daemon[0].sd_mutex ); + Debug( LDAP_DEBUG_ANY, "listener_error_cb: " + "too many open files, cannot accept new connections on " + "url=%s\n", + l->sl_url.bv_val ); + } else { + char ebuf[128]; + Debug( LDAP_DEBUG_ANY, "listener_error_cb: " + "received an error on a listener, shutting down: '%s'\n", + sock_errstr( err, ebuf, sizeof(ebuf) ) ); + event_base_loopexit( l->base, NULL ); + } +} + +void +listeners_reactivate( void ) +{ + int i; + + ldap_pvt_thread_mutex_lock( &lload_daemon[0].sd_mutex ); + for ( i = 0; emfile && lload_listeners[i] != NULL; i++ ) { + LloadListener *lr = lload_listeners[i]; + + if ( lr->sl_sd == AC_SOCKET_INVALID ) continue; + if ( lr->sl_mute ) { + emfile--; + evconnlistener_enable( lr->listener ); + lr->sl_mute = 0; + Debug( LDAP_DEBUG_CONNS, "listeners_reactivate: " + "reactivated listener url=%s\n", + lr->sl_url.bv_val ); + } + } + if ( emfile && lload_listeners[i] == NULL ) { + /* Walked the entire list without enabling anything; emfile + * counter is stale. Reset it. */ + emfile = 0; + } + ldap_pvt_thread_mutex_unlock( &lload_daemon[0].sd_mutex ); +} + +static int +lload_listener_activate( void ) +{ + struct evconnlistener *listener; + int l, rc; + char ebuf[128]; + + listener_base = event_base_new(); + if ( !listener_base ) return -1; + + for ( l = 0; lload_listeners[l] != NULL; l++ ) { + if ( lload_listeners[l]->sl_sd == AC_SOCKET_INVALID ) continue; + + /* FIXME: TCP-only! */ +#ifdef LDAP_TCP_BUFFER + if ( 1 ) { + int origsize, size, realsize, rc; + socklen_t optlen; + + size = 0; + if ( lload_listeners[l]->sl_tcp_rmem > 0 ) { + size = lload_listeners[l]->sl_tcp_rmem; + } else if ( slapd_tcp_rmem > 0 ) { + size = slapd_tcp_rmem; + } + + if ( size > 0 ) { + optlen = sizeof(origsize); + rc = getsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET, + SO_RCVBUF, (void *)&origsize, &optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "lload_listener_activate: " + "getsockopt(SO_RCVBUF) failed errno=%d (%s)\n", + err, AC_STRERROR_R( err, ebuf, sizeof(ebuf) ) ); + } + + optlen = sizeof(size); + rc = setsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET, + SO_RCVBUF, (const void *)&size, optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "lload_listener_activate: " + "setsockopt(SO_RCVBUF) failed errno=%d (%s)\n", + err, sock_errstr( err, ebuf, sizeof(ebuf) ) ); + } + + optlen = sizeof(realsize); + rc = getsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET, + SO_RCVBUF, (void *)&realsize, &optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "lload_listener_activate: " + "getsockopt(SO_RCVBUF) failed errno=%d (%s)\n", + err, sock_errstr( err, ebuf, sizeof(ebuf) ) ); + } + + Debug( LDAP_DEBUG_ANY, "lload_listener_activate: " + "url=%s (#%d) RCVBUF original size=%d requested " + "size=%d real size=%d\n", + lload_listeners[l]->sl_url.bv_val, l, origsize, size, + realsize ); + } + + size = 0; + if ( lload_listeners[l]->sl_tcp_wmem > 0 ) { + size = lload_listeners[l]->sl_tcp_wmem; + } else if ( slapd_tcp_wmem > 0 ) { + size = slapd_tcp_wmem; + } + + if ( size > 0 ) { + optlen = sizeof(origsize); + rc = getsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET, + SO_SNDBUF, (void *)&origsize, &optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "lload_listener_activate: " + "getsockopt(SO_SNDBUF) failed errno=%d (%s)\n", + err, sock_errstr( err, ebuf, sizeof(ebuf) ) ); + } + + optlen = sizeof(size); + rc = setsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET, + SO_SNDBUF, (const void *)&size, optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "lload_listener_activate: " + "setsockopt(SO_SNDBUF) failed errno=%d (%s)\n", + err, sock_errstr( err, ebuf, sizeof(ebuf) ) ); + } + + optlen = sizeof(realsize); + rc = getsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET, + SO_SNDBUF, (void *)&realsize, &optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "lload_listener_activate: " + "getsockopt(SO_SNDBUF) failed errno=%d (%s)\n", + err, sock_errstr( err, ebuf, sizeof(ebuf) ) ); + } + + Debug( LDAP_DEBUG_ANY, "lload_listener_activate: " + "url=%s (#%d) SNDBUF original size=%d requested " + "size=%d real size=%d\n", + lload_listeners[l]->sl_url.bv_val, l, origsize, size, + realsize ); + } + } +#endif /* LDAP_TCP_BUFFER */ + + lload_listeners[l]->sl_busy = 1; + listener = evconnlistener_new( listener_base, lload_listener, + lload_listeners[l], + LEV_OPT_THREADSAFE|LEV_OPT_DEFERRED_ACCEPT, + SLAPD_LISTEN_BACKLOG, lload_listeners[l]->sl_sd ); + if ( !listener ) { + int err = sock_errno(); + +#ifdef LDAP_PF_INET6 + /* If error is EADDRINUSE, we are trying to listen to INADDR_ANY and + * we are already listening to in6addr_any, then we want to ignore + * this and continue. + */ + if ( err == EADDRINUSE ) { + int i; + struct sockaddr_in sa = lload_listeners[l]->sl_sa.sa_in_addr; + struct sockaddr_in6 sa6; + + if ( sa.sin_family == AF_INET && + sa.sin_addr.s_addr == htonl( INADDR_ANY ) ) { + for ( i = 0; i < l; i++ ) { + sa6 = lload_listeners[i]->sl_sa.sa_in6_addr; + if ( sa6.sin6_family == AF_INET6 && + !memcmp( &sa6.sin6_addr, &in6addr_any, + sizeof(struct in6_addr) ) ) { + break; + } + } + + if ( i < l ) { + /* We are already listening to in6addr_any */ + Debug( LDAP_DEBUG_CONNS, "lload_listener_activate: " + "Attempt to listen to 0.0.0.0 failed, " + "already listening on ::, assuming IPv4 " + "included\n" ); + lloadd_close( lload_listeners[l]->sl_sd ); + lload_listeners[l]->sl_sd = AC_SOCKET_INVALID; + continue; + } + } + } +#endif /* LDAP_PF_INET6 */ + Debug( LDAP_DEBUG_ANY, "lload_listener_activate: " + "listen(%s, 5) failed errno=%d (%s)\n", + lload_listeners[l]->sl_url.bv_val, err, + sock_errstr( err, ebuf, sizeof(ebuf) ) ); + return -1; + } + + lload_listeners[l]->base = listener_base; + lload_listeners[l]->listener = listener; + evconnlistener_set_error_cb( listener, listener_error_cb ); + } + + rc = ldap_pvt_thread_create( + &listener_tid, 0, lload_listener_thread, lload_listeners[l] ); + + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "lload_listener_activate(%d): " + "submit failed (%d)\n", + lload_listeners[l]->sl_sd, rc ); + } + return rc; +} + +static void * +lloadd_io_task( void *ptr ) +{ + int rc; + int tid = (ldap_pvt_thread_t *)ptr - daemon_tid; + struct event_base *base = lload_daemon[tid].base; + struct event *event; + + event = event_new( base, -1, EV_WRITE, daemon_wakeup_cb, ptr ); + if ( !event ) { + Debug( LDAP_DEBUG_ANY, "lloadd_io_task: " + "failed to set up the wakeup event\n" ); + return (void *)-1; + } + event_add( event, NULL ); + lload_daemon[tid].wakeup_event = event; + + /* run */ + rc = event_base_dispatch( base ); + Debug( LDAP_DEBUG_ANY, "lloadd_io_task: " + "Daemon %d, event loop finished: rc=%d\n", + tid, rc ); + + if ( !slapd_gentle_shutdown ) { + slapd_abrupt_shutdown = 1; + } + + return NULL; +} + +int +lloadd_daemon( struct event_base *daemon_base ) +{ + int i, rc; + LloadTier *tier; + struct event_base *base; + struct event *event; + struct timeval second = { 1, 0 }; + + assert( daemon_base != NULL ); + + dnsbase = evdns_base_new( daemon_base, 0 ); + if ( !dnsbase ) { + Debug( LDAP_DEBUG_ANY, "lloadd startup: " + "failed to set up for async name resolution\n" ); + return -1; + } + + /* + * ITS#10070: Allow both operation without working DNS (test environments) + * and e.g. containers that don't have a /etc/resolv.conf but do have a + * server listening on 127.0.0.1 which is the default. + */ + (void)evdns_base_resolv_conf_parse( dnsbase, + DNS_OPTION_NAMESERVERS|DNS_OPTION_HOSTSFILE, + lload_resolvconf_path ); + + if ( lload_daemon_threads > SLAPD_MAX_DAEMON_THREADS ) + lload_daemon_threads = SLAPD_MAX_DAEMON_THREADS; + + daemon_tid = + ch_malloc( lload_daemon_threads * sizeof(ldap_pvt_thread_t) ); + + for ( i = 0; i < lload_daemon_threads; i++ ) { + base = event_base_new(); + if ( !base ) { + Debug( LDAP_DEBUG_ANY, "lloadd startup: " + "failed to acquire event base for an I/O thread\n" ); + return -1; + } + lload_daemon[i].base = base; + + ldap_pvt_thread_mutex_init( &lload_daemon[i].sd_mutex ); + /* threads that handle client and upstream sockets */ + rc = ldap_pvt_thread_create( + &daemon_tid[i], 0, lloadd_io_task, &daemon_tid[i] ); + + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "lloadd startup: " + "listener ldap_pvt_thread_create failed (%d)\n", + rc ); + return rc; + } + } + + if ( (rc = lload_listener_activate()) != 0 ) { + return rc; + } + + LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) { + if ( tier->t_type.tier_startup( tier ) ) { + return -1; + } + } + + event = event_new( daemon_base, -1, EV_TIMEOUT|EV_PERSIST, + lload_tiers_update, NULL ); + if ( !event ) { + Debug( LDAP_DEBUG_ANY, "lloadd: " + "failed to allocate stats update event\n" ); + return -1; + } + lload_stats_event = event; + event_add( event, &second ); + + event = evtimer_new( daemon_base, operations_timeout, event_self_cbarg() ); + if ( !event ) { + Debug( LDAP_DEBUG_ANY, "lloadd: " + "failed to allocate timeout event\n" ); + return -1; + } + lload_timeout_event = event; + + /* TODO: should we just add it with any timeout and re-add when the timeout + * changes? */ + if ( lload_timeout_api ) { + event_add( event, lload_timeout_api ); + } + + checked_lock( &lload_wait_mutex ); + lloadd_inited = 1; + ldap_pvt_thread_cond_signal( &lload_wait_cond ); + checked_unlock( &lload_wait_mutex ); +#if !defined(BALANCER_MODULE) && defined(HAVE_SYSTEMD) + rc = sd_notify( 1, "READY=1" ); + if ( rc < 0 ) { + Debug( LDAP_DEBUG_ANY, "lloadd startup: " + "systemd sd_notify failed (%d)\n", rc ); + } +#endif /* !BALANCER_MODULE && HAVE_SYSTEMD */ + + rc = event_base_dispatch( daemon_base ); + Debug( LDAP_DEBUG_ANY, "lloadd shutdown: " + "Main event loop finished: rc=%d\n", + rc ); + + /* shutdown */ + event_base_loopexit( listener_base, 0 ); + + /* wait for the listener threads to complete */ + destroy_listeners(); + + /* Mark upstream connections closing and prevent from opening new ones */ + lload_tiers_shutdown(); + + /* Do the same for clients */ + clients_destroy( 1 ); + + for ( i = 0; i < lload_daemon_threads; i++ ) { + /* + * https://github.com/libevent/libevent/issues/623 + * deleting the event doesn't notify the base, just activate it and + * let it delete itself + */ + event_active( lload_daemon[i].wakeup_event, EV_READ, 0 ); + } + + for ( i = 0; i < lload_daemon_threads; i++ ) { + ldap_pvt_thread_join( daemon_tid[i], (void *)NULL ); + } + +#ifndef BALANCER_MODULE + if ( LogTest( LDAP_DEBUG_ANY ) ) { + int t = ldap_pvt_thread_pool_backload( &connection_pool ); + Debug( LDAP_DEBUG_ANY, "lloadd shutdown: " + "waiting for %d operations/tasks to finish\n", + t ); + } + ldap_pvt_thread_pool_close( &connection_pool, 1 ); +#endif + + lload_tiers_destroy(); + clients_destroy( 0 ); + lload_bindconf_free( &bindconf ); + evdns_base_free( dnsbase, 0 ); + + ch_free( daemon_tid ); + daemon_tid = NULL; + + lloadd_daemon_destroy(); + + /* If we're a slapd module, let the thread that initiated the shut down + * know we've finished */ + checked_lock( &lload_wait_mutex ); + ldap_pvt_thread_cond_signal( &lload_wait_cond ); + checked_unlock( &lload_wait_mutex ); + + return 0; +} + +static void +daemon_wakeup_cb( evutil_socket_t sig, short what, void *arg ) +{ + int tid = (ldap_pvt_thread_t *)arg - daemon_tid; + + Debug( LDAP_DEBUG_TRACE, "daemon_wakeup_cb: " + "Daemon thread %d woken up\n", + tid ); + event_del( lload_daemon[tid].wakeup_event ); +} + +LloadChange lload_change = { .type = LLOAD_CHANGE_UNDEFINED }; + +#ifdef BALANCER_MODULE +int +backend_conn_cb( ldap_pvt_thread_start_t *start, void *startarg, void *arg ) +{ + LloadConnection *c = startarg; + LloadBackend *b = arg; + + if ( b == NULL || c->c_backend == b ) { + CONNECTION_LOCK_DESTROY(c); + return 1; + } + return 0; +} + +#ifdef HAVE_TLS +int +client_tls_cb( ldap_pvt_thread_start_t *start, void *startarg, void *arg ) +{ + LloadConnection *c = startarg; + + if ( c->c_destroy == client_destroy && + c->c_is_tls == LLOAD_TLS_ESTABLISHED ) { + CONNECTION_LOCK_DESTROY(c); + return 1; + } + return 0; +} +#endif /* HAVE_TLS */ + +static int +detach_linked_backend_cb( LloadConnection *client, LloadBackend *b ) +{ + int rc = LDAP_SUCCESS; + + if ( client->c_backend != b ) { + return rc; + } + + Debug( LDAP_DEBUG_CONNS, "detach_linked_backend_cb: " + "detaching backend '%s' from connid=%lu%s\n", + b->b_name.bv_val, client->c_connid, + client->c_restricted == LLOAD_OP_RESTRICTED_BACKEND ? + " and closing the connection" : + "" ); + + /* We were approached from the connection list */ + assert( IS_ALIVE( client, c_refcnt ) ); + + assert( client->c_restricted == LLOAD_OP_RESTRICTED_WRITE || + client->c_restricted == LLOAD_OP_RESTRICTED_BACKEND ); + if ( client->c_restricted == LLOAD_OP_RESTRICTED_BACKEND ) { + int gentle = 1; + CONNECTION_LOCK(client); + rc = lload_connection_close( client, &gentle ); + CONNECTION_UNLOCK(client); + } + + client->c_restricted = LLOAD_OP_NOT_RESTRICTED; + client->c_restricted_at = 0; + client->c_restricted_inflight = 0; + + return rc; +} + +void +lload_handle_backend_invalidation( LloadChange *change ) +{ + LloadBackend *b = change->target; + LloadTier *tier = b->b_tier; + + assert( change->object == LLOAD_BACKEND ); + + if ( change->type == LLOAD_CHANGE_ADD ) { + BackendInfo *mi = backend_info( "monitor" ); + + if ( mi ) { + monitor_extra_t *mbe = mi->bi_extra; + if ( mbe->is_configured() ) { + lload_monitor_backend_init( mi, tier->t_monitor, b ); + } + } + + if ( tier->t_type.tier_change ) { + tier->t_type.tier_change( tier, change ); + } + + checked_lock( &b->b_mutex ); + backend_retry( b ); + checked_unlock( &b->b_mutex ); + return; + } else if ( change->type == LLOAD_CHANGE_DEL ) { + ldap_pvt_thread_pool_walk( + &connection_pool, handle_pdus, backend_conn_cb, b ); + ldap_pvt_thread_pool_walk( + &connection_pool, upstream_bind, backend_conn_cb, b ); + + checked_lock( &clients_mutex ); + connections_walk( + &clients_mutex, &clients, + (CONNCB)detach_linked_backend_cb, b ); + checked_unlock( &clients_mutex ); + + if ( tier->t_type.tier_change ) { + tier->t_type.tier_change( tier, change ); + } + lload_backend_destroy( b ); + return; + } + assert( change->type == LLOAD_CHANGE_MODIFY ); + + /* + * A change that can't be handled gracefully, terminate all connections and + * start over. + */ + if ( change->flags.backend & LLOAD_BACKEND_MOD_OTHER ) { + ldap_pvt_thread_pool_walk( + &connection_pool, handle_pdus, backend_conn_cb, b ); + ldap_pvt_thread_pool_walk( + &connection_pool, upstream_bind, backend_conn_cb, b ); + checked_lock( &b->b_mutex ); + backend_reset( b, 0 ); + backend_retry( b ); + checked_unlock( &b->b_mutex ); + return; + } + + /* + * Handle changes to number of connections: + * - a change might get the connection limit above the pool size: + * - consider closing (in order of priority?): + * - connections awaiting connect() completion + * - connections currently preparing + * - bind connections over limit (which is 0 if 'feature vc' is on + * - regular connections over limit + * - below pool size + * - call backend_retry if there are no opening connections + * - one pool size above and one below the configured size + * - still close the ones above limit, it should sort itself out + * the only issue is if a closing connection isn't guaranteed to do + * that at some point + */ + if ( change->flags.backend & LLOAD_BACKEND_MOD_CONNS ) { + int bind_requested = 0, need_close = 0, need_open = 0; + LloadConnection *c; + + bind_requested = +#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS + (lload_features & LLOAD_FEATURE_VC) ? 0 : +#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */ + b->b_numbindconns; + + if ( b->b_bindavail > bind_requested ) { + need_close += b->b_bindavail - bind_requested; + } else if ( b->b_bindavail < bind_requested ) { + need_open = 1; + } + + if ( b->b_active > b->b_numconns ) { + need_close += b->b_active - b->b_numconns; + } else if ( b->b_active < b->b_numconns ) { + need_open = 1; + } + + if ( !need_open ) { + need_close += b->b_opening; + + while ( !LDAP_LIST_EMPTY( &b->b_connecting ) ) { + LloadPendingConnection *p = LDAP_LIST_FIRST( &b->b_connecting ); + + LDAP_LIST_REMOVE( p, next ); + event_free( p->event ); + evutil_closesocket( p->fd ); + ch_free( p ); + b->b_opening--; + need_close--; + } + } + + if ( need_close || !need_open ) { + /* It might be too late to repurpose a preparing connection, just + * close them all */ + while ( !LDAP_CIRCLEQ_EMPTY( &b->b_preparing ) ) { + c = LDAP_CIRCLEQ_FIRST( &b->b_preparing ); + + event_del( c->c_read_event ); + CONNECTION_LOCK_DESTROY(c); + assert( c == NULL ); + b->b_opening--; + need_close--; + } + if ( event_pending( b->b_retry_event, EV_TIMEOUT, NULL ) ) { + event_del( b->b_retry_event ); + b->b_opening--; + } + assert( b->b_opening == 0 ); + } + + if ( b->b_bindavail > bind_requested ) { + int diff = b->b_bindavail - bind_requested; + + assert( need_close >= diff ); + + LDAP_CIRCLEQ_FOREACH ( c, &b->b_bindconns, c_next ) { + int gentle = 1; + + lload_connection_close( c, &gentle ); + need_close--; + diff--; + if ( !diff ) { + break; + } + } + assert( diff == 0 ); + } + + if ( b->b_active > b->b_numconns ) { + int diff = b->b_active - b->b_numconns; + + assert( need_close >= diff ); + + LDAP_CIRCLEQ_FOREACH ( c, &b->b_conns, c_next ) { + int gentle = 1; + + lload_connection_close( c, &gentle ); + need_close--; + diff--; + if ( !diff ) { + break; + } + } + assert( diff == 0 ); + } + assert( need_close == 0 ); + + if ( need_open ) { + checked_lock( &b->b_mutex ); + backend_retry( b ); + checked_unlock( &b->b_mutex ); + } + } +} + +void +lload_handle_tier_invalidation( LloadChange *change ) +{ + LloadTier *tier; + + assert( change->object == LLOAD_TIER ); + tier = change->target; + + if ( change->type == LLOAD_CHANGE_ADD ) { + BackendInfo *mi = backend_info( "monitor" ); + + if ( mi ) { + monitor_extra_t *mbe = mi->bi_extra; + if ( mbe->is_configured() ) { + lload_monitor_tier_init( mi, tier ); + } + } + + tier->t_type.tier_startup( tier ); + if ( LDAP_STAILQ_EMPTY( &tiers ) ) { + LDAP_STAILQ_INSERT_HEAD( &tiers, tier, t_next ); + } else { + LDAP_STAILQ_INSERT_TAIL( &tiers, tier, t_next ); + } + return; + } else if ( change->type == LLOAD_CHANGE_DEL ) { + LDAP_STAILQ_REMOVE( &tiers, tier, LloadTier, t_next ); + tier->t_type.tier_reset( tier, 1 ); + tier->t_type.tier_destroy( tier ); + return; + } + assert( change->type == LLOAD_CHANGE_MODIFY ); + + if ( tier->t_type.tier_change ) { + tier->t_type.tier_change( tier, change ); + } +} + +void +lload_handle_global_invalidation( LloadChange *change ) +{ + assert( change->type == LLOAD_CHANGE_MODIFY ); + assert( change->object == LLOAD_DAEMON ); + + if ( change->flags.daemon & LLOAD_DAEMON_MOD_THREADS ) { + /* walk the task queue to remove any tasks belonging to us. */ + /* TODO: initiate a full module restart, everything will fall into + * place at that point */ + ldap_pvt_thread_pool_walk( + &connection_pool, handle_pdus, backend_conn_cb, NULL ); + ldap_pvt_thread_pool_walk( + &connection_pool, upstream_bind, backend_conn_cb, NULL ); + assert(0); + return; + } + + if ( change->flags.daemon & LLOAD_DAEMON_MOD_FEATURES ) { + lload_features_t feature_diff = + lload_features ^ ( ~(uintptr_t)change->target ); + /* Feature change handling: + * - VC (TODO): + * - on: terminate all bind connections + * - off: cancel all bind operations in progress, reopen bind connections + * - ProxyAuthz: + * - on: nothing needed + * - off: clear c_auth/privileged on each client + * - read pause (WIP): + * - nothing needed? + */ + + assert( change->target ); + if ( feature_diff & LLOAD_FEATURE_VC ) { + assert(0); + feature_diff &= ~LLOAD_FEATURE_VC; + } + if ( feature_diff & LLOAD_FEATURE_PAUSE ) { + feature_diff &= ~LLOAD_FEATURE_PAUSE; + } + if ( feature_diff & LLOAD_FEATURE_PROXYAUTHZ ) { + if ( !(lload_features & LLOAD_FEATURE_PROXYAUTHZ) ) { + LloadConnection *c; + /* We switched proxyauthz off */ + LDAP_CIRCLEQ_FOREACH ( c, &clients, c_next ) { + if ( !BER_BVISNULL( &c->c_auth ) ) { + ber_memfree( c->c_auth.bv_val ); + BER_BVZERO( &c->c_auth ); + } + if ( c->c_type == LLOAD_C_PRIVILEGED ) { + c->c_type = LLOAD_C_OPEN; + } + } + } + feature_diff &= ~LLOAD_FEATURE_PROXYAUTHZ; + } + assert( !feature_diff ); + } + +#ifdef HAVE_TLS + if ( change->flags.daemon & LLOAD_DAEMON_MOD_TLS ) { + /* terminate all clients with TLS set up */ + ldap_pvt_thread_pool_walk( + &connection_pool, handle_pdus, client_tls_cb, NULL ); + if ( !LDAP_CIRCLEQ_EMPTY( &clients ) ) { + LloadConnection *c = LDAP_CIRCLEQ_FIRST( &clients ); + unsigned long first_connid = c->c_connid; + + while ( c ) { + LloadConnection *next = + LDAP_CIRCLEQ_LOOP_NEXT( &clients, c, c_next ); + if ( c->c_is_tls ) { + CONNECTION_LOCK_DESTROY(c); + assert( c == NULL ); + } + c = next; + if ( c->c_connid <= first_connid ) { + c = NULL; + } + } + } + } +#endif /* HAVE_TLS */ + + if ( change->flags.daemon & LLOAD_DAEMON_MOD_BINDCONF ) { + LloadConnection *c; + + /* + * Only timeout changes can be handled gracefully, terminate all + * connections and start over. + */ + ldap_pvt_thread_pool_walk( + &connection_pool, handle_pdus, backend_conn_cb, NULL ); + ldap_pvt_thread_pool_walk( + &connection_pool, upstream_bind, backend_conn_cb, NULL ); + + lload_tiers_reset( 0 ); + + /* Reconsider the PRIVILEGED flag on all clients */ + LDAP_CIRCLEQ_FOREACH ( c, &clients, c_next ) { + int privileged = ber_bvstrcasecmp( &c->c_auth, &lloadd_identity ); + + /* We have just terminated all pending operations (even pins), there + * should be no connections still binding/closing */ + assert( c->c_state == LLOAD_C_READY ); + + c->c_type = privileged ? LLOAD_C_PRIVILEGED : LLOAD_C_OPEN; + } + } +} + +int +lload_handle_invalidation( LloadChange *change ) +{ + if ( (change->type == LLOAD_CHANGE_MODIFY) && + change->flags.generic == 0 ) { + Debug( LDAP_DEBUG_ANY, "lload_handle_invalidation: " + "a modify where apparently nothing changed\n" ); + } + + switch ( change->object ) { + case LLOAD_BACKEND: + lload_handle_backend_invalidation( change ); + break; + case LLOAD_TIER: + lload_handle_tier_invalidation( change ); + break; + case LLOAD_DAEMON: + lload_handle_global_invalidation( change ); + break; + default: + Debug( LDAP_DEBUG_ANY, "lload_handle_invalidation: " + "unrecognised change\n" ); + assert(0); + } + + return LDAP_SUCCESS; +} + +static void +lload_pause_event_cb( evutil_socket_t s, short what, void *arg ) +{ + /* + * We are pausing, signal the pausing thread we've finished and + * wait until the thread pool resumes operation. + * + * Do this in lockstep with the pausing thread. + */ + checked_lock( &lload_wait_mutex ); + ldap_pvt_thread_cond_signal( &lload_wait_cond ); + + /* Now wait until we unpause, then we can resume operation */ + ldap_pvt_thread_cond_wait( &lload_pause_cond, &lload_wait_mutex ); + checked_unlock( &lload_wait_mutex ); +} + +/* + * Signal the event base to terminate processing as soon as it can and wait for + * lload_pause_event_cb to notify us this has happened. + */ +static int +lload_pause_base( struct event_base *base ) +{ + int rc; + + checked_lock( &lload_wait_mutex ); + event_base_once( base, -1, EV_TIMEOUT, lload_pause_event_cb, base, NULL ); + rc = ldap_pvt_thread_cond_wait( &lload_wait_cond, &lload_wait_mutex ); + checked_unlock( &lload_wait_mutex ); + + return rc; +} + +void +lload_pause_server( void ) +{ + LloadChange ch = { .type = LLOAD_CHANGE_UNDEFINED }; + int i; + + lload_pause_base( listener_base ); + lload_pause_base( daemon_base ); + + for ( i = 0; i < lload_daemon_threads; i++ ) { + lload_pause_base( lload_daemon[i].base ); + } + + lload_change = ch; +} + +void +lload_unpause_server( void ) +{ + if ( lload_change.type != LLOAD_CHANGE_UNDEFINED ) { + lload_handle_invalidation( &lload_change ); + } + + /* + * Make sure lloadd is completely ready to unpause by now: + * + * After the broadcast, we handle I/O and begin filling the thread pool, in + * high load conditions, we might hit the pool limits and start processing + * operations in the I/O threads (one PDU per socket at a time for fairness + * sake) even before a pause has finished from slapd's point of view! + * + * When (max_pdus_per_cycle == 0) we don't use the pool for these at all and + * most lload processing starts immediately making this even more prominent. + */ + ldap_pvt_thread_cond_broadcast( &lload_pause_cond ); +} +#endif /* BALANCER_MODULE */ + +void +lload_sig_shutdown( evutil_socket_t sig, short what, void *arg ) +{ + struct event_base *daemon_base = arg; + int save_errno = errno; + int i; + + /* + * If the NT Service Manager is controlling the server, we don't + * want SIGBREAK to kill the server. For some strange reason, + * SIGBREAK is generated when a user logs out. + */ + +#if defined(HAVE_NT_SERVICE_MANAGER) && defined(SIGBREAK) + if ( is_NT_Service && sig == SIGBREAK ) { + /* empty */; + } else +#endif /* HAVE_NT_SERVICE_MANAGER && SIGBREAK */ +#ifdef SIGHUP + if ( sig == SIGHUP && global_gentlehup && slapd_gentle_shutdown == 0 ) { + slapd_gentle_shutdown = 1; + } else +#endif /* SIGHUP */ + { + slapd_shutdown = 1; + } + + for ( i = 0; i < lload_daemon_threads; i++ ) { + event_base_loopexit( lload_daemon[i].base, NULL ); + } + event_base_loopexit( daemon_base, NULL ); + + errno = save_errno; +} + +struct event_base * +lload_get_base( ber_socket_t s ) +{ + int tid = DAEMON_ID(s); + return lload_daemon[tid].base; +} + +LloadListener ** +lloadd_get_listeners( void ) +{ + /* Could return array with no listeners if !listening, but current + * callers mostly look at the URLs. E.g. syncrepl uses this to + * identify the server, which means it wants the startup arguments. + */ + return lload_listeners; +} + +/* Reject all incoming requests */ +void +lload_suspend_listeners( void ) +{ + int i; + for ( i = 0; lload_listeners[i]; i++ ) { + lload_listeners[i]->sl_mute = 1; + evconnlistener_disable( lload_listeners[i]->listener ); + listen( lload_listeners[i]->sl_sd, 0 ); + } +} + +/* Resume after a suspend */ +void +lload_resume_listeners( void ) +{ + int i; + for ( i = 0; lload_listeners[i]; i++ ) { + lload_listeners[i]->sl_mute = 0; + listen( lload_listeners[i]->sl_sd, SLAPD_LISTEN_BACKLOG ); + evconnlistener_enable( lload_listeners[i]->listener ); + } +} diff --git a/servers/lloadd/epoch.c b/servers/lloadd/epoch.c new file mode 100644 index 0000000..72b6d5d --- /dev/null +++ b/servers/lloadd/epoch.c @@ -0,0 +1,321 @@ +/* epoch.c - epoch based memory reclamation */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2018-2022 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 + * . + */ + +/** @file epoch.c + * + * Implementation of epoch based memory reclamation, in principle + * similar to the algorithm presented in + * https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-579.pdf + * + * Not completely lock-free at the moment. + * + * Also the problems with epoch based memory reclamation are still + * present - a thread actively observing an epoch getting stuck will + * prevent managed objects (in our case connections and operations) + * from being freed, potentially running out of memory. + */ + +#include "portable.h" + +#include "lload.h" +#include + +/* Has to be >= 3 */ +#define EPOCH_MASK ( 1 << 2 ) +#define EPOCH_PREV(epoch) ( ( (epoch) + EPOCH_MASK - 1 ) % EPOCH_MASK ) +#define EPOCH_NEXT(epoch) ( ( (epoch) + 1 ) % EPOCH_MASK ) + +struct pending_ref { + void *object; + dispose_cb *dispose; + struct pending_ref *next; +}; + +ldap_pvt_thread_rdwr_t epoch_mutex; + +static epoch_t current_epoch; +static uintptr_t epoch_threads[EPOCH_MASK]; +static struct pending_ref *references[EPOCH_MASK]; + +void +epoch_init( void ) +{ + epoch_t epoch; + + current_epoch = 0; + for ( epoch = 0; epoch < EPOCH_MASK; epoch++ ) { + assert( !epoch_threads[epoch] ); + assert( !references[epoch] ); + } + + ldap_pvt_thread_rdwr_init( &epoch_mutex ); +} + +void +epoch_shutdown( void ) +{ + epoch_t epoch; + struct pending_ref *old, *next; + + for ( epoch = 0; epoch < EPOCH_MASK; epoch++ ) { + assert( !epoch_threads[epoch] ); + } + + /* + * Even with the work in epoch_leave(), shutdown code doesn't currently + * observe any epoch, so there might still be references left to free. + */ + epoch = EPOCH_PREV(current_epoch); + next = references[epoch]; + references[epoch] = NULL; + for ( old = next; old; old = next ) { + next = old->next; + + old->dispose( old->object ); + ch_free( old ); + } + + epoch = current_epoch; + next = references[epoch]; + references[epoch] = NULL; + for ( old = next; old; old = next ) { + next = old->next; + + old->dispose( old->object ); + ch_free( old ); + } + + /* No references should exist anywhere now */ + for ( epoch = 0; epoch < EPOCH_MASK; epoch++ ) { + assert( !references[epoch] ); + } + + ldap_pvt_thread_rdwr_destroy( &epoch_mutex ); +} + +epoch_t +epoch_join( void ) +{ + epoch_t epoch; + struct pending_ref *old, *ref = NULL; + +retry: + /* TODO: make this completely lock-free */ + ldap_pvt_thread_rdwr_rlock( &epoch_mutex ); + epoch = current_epoch; + __atomic_add_fetch( &epoch_threads[epoch], 1, __ATOMIC_ACQ_REL ); + ldap_pvt_thread_rdwr_runlock( &epoch_mutex ); + + if ( __atomic_load_n( + &epoch_threads[EPOCH_PREV(epoch)], __ATOMIC_ACQUIRE ) ) { + return epoch; + } + + __atomic_exchange( + &references[EPOCH_PREV(epoch)], &ref, &ref, __ATOMIC_ACQ_REL ); + + Debug( LDAP_DEBUG_TRACE, "epoch_join: " + "advancing epoch to %zu with %s objects to free\n", + EPOCH_NEXT(epoch), ref ? "some" : "no" ); + + ldap_pvt_thread_rdwr_wlock( &epoch_mutex ); + current_epoch = EPOCH_NEXT(epoch); + ldap_pvt_thread_rdwr_wunlock( &epoch_mutex ); + + if ( !ref ) { + return epoch; + } + + /* + * The below is now safe to free outside epochs and we don't want to make + * the current epoch last any longer than necessary. + * + * Looks like there might be fairness issues in massively parallel + * environments but they haven't been observed on 32-core machines. + */ + epoch_leave( epoch ); + + for ( old = ref; old; old = ref ) { + ref = old->next; + + old->dispose( old->object ); + ch_free( old ); + } + + goto retry; +} + +void +epoch_leave( epoch_t epoch ) +{ + struct pending_ref *p, *next, *old_refs = NULL, *current_refs = NULL; + + /* Are there other threads observing our epoch? */ + if ( __atomic_sub_fetch( &epoch_threads[epoch], 1, __ATOMIC_ACQ_REL ) ) { + return; + } + + /* + * Optimisation for the case when we're mostly idle. Otherwise we won't + * release resources until another thread comes by and joins the epoch + * (twice), and there's no idea how soon (or late) that is going to happen. + * + * NB. There is no limit to the number of threads executing the following + * code in parallel. + */ + ldap_pvt_thread_rdwr_rlock( &epoch_mutex ); + /* + * Anything could happen between the subtract and the lock being acquired + * above, so check again. But once we hold this lock (and confirm no more + * threads still observe either prospective epoch), noone will be able to + * finish epoch_join until we've released epoch_mutex since we *first* make + * sure it holds that: + * + * epoch_threads[EPOCH_PREV(current_epoch)] == 0 + * + * and that leads epoch_join() to acquire a write lock on &epoch_mutex. + */ + if ( epoch != current_epoch && epoch != EPOCH_PREV(current_epoch) ) { + /* Epoch counter has run away from us, no need to do anything */ + ldap_pvt_thread_rdwr_runlock( &epoch_mutex ); + return; + } + if ( __atomic_load_n( + &epoch_threads[EPOCH_PREV(current_epoch)], + __ATOMIC_ACQUIRE ) ) { + /* There is another thread still running */ + ldap_pvt_thread_rdwr_runlock( &epoch_mutex ); + return; + } + if ( __atomic_load_n( &epoch_threads[current_epoch], __ATOMIC_ACQUIRE ) ) { + /* There is another thread still running */ + ldap_pvt_thread_rdwr_runlock( &epoch_mutex ); + return; + } + + /* + * We're all alone (apart from anyone who reached epoch_leave() at the same + * time), it's safe to claim all references and free them. + */ + __atomic_exchange( + &references[EPOCH_PREV(current_epoch)], &old_refs, &old_refs, + __ATOMIC_ACQ_REL ); + __atomic_exchange( + &references[current_epoch], ¤t_refs, ¤t_refs, + __ATOMIC_ACQ_REL ); + ldap_pvt_thread_rdwr_runlock( &epoch_mutex ); + + for ( p = old_refs; p; p = next ) { + next = p->next; + + p->dispose( p->object ); + ch_free( p ); + } + + for ( p = current_refs; p; p = next ) { + next = p->next; + + p->dispose( p->object ); + ch_free( p ); + } +} + +/* + * Add the object to the "current global epoch", not the epoch our thread + * entered. + */ +void +epoch_append( void *ptr, dispose_cb *cb ) +{ + struct pending_ref *new; + epoch_t epoch = __atomic_load_n( ¤t_epoch, __ATOMIC_ACQUIRE ); + + /* + * BTW, the following is not appropriate here: + * assert( __atomic_load_n( &epoch_threads[epoch], __ATOMIC_RELAXED ) ); + * + * We might be a thread lagging behind in the "previous epoch" with no + * other threads executing at all. + */ + + new = ch_malloc( sizeof(struct pending_ref) ); + new->object = ptr; + new->dispose = cb; + new->next = __atomic_load_n( &references[epoch], __ATOMIC_ACQUIRE ); + + while ( !__atomic_compare_exchange( &references[epoch], &new->next, &new, 0, + __ATOMIC_RELEASE, __ATOMIC_RELAXED ) ) + /* iterate until we succeed */; +} + +int +acquire_ref( uintptr_t *refp ) +{ + uintptr_t refcnt, new_refcnt; + + refcnt = __atomic_load_n( refp, __ATOMIC_ACQUIRE ); + + /* + * If we just incremented the refcnt and checked for zero after, another + * thread might falsely believe the object was going to stick around. + * + * Checking whether the object is still dead at disposal time might not be + * able to distinguish it from being freed in a later epoch. + */ + do { + if ( !refcnt ) { + return refcnt; + } + + new_refcnt = refcnt + 1; + } while ( !__atomic_compare_exchange( refp, &refcnt, &new_refcnt, 0, + __ATOMIC_RELEASE, __ATOMIC_RELAXED ) ); + assert( new_refcnt == refcnt + 1 ); + + return refcnt; +} + +int +try_release_ref( + uintptr_t *refp, + void *object, + dispose_cb *unlink_cb, + dispose_cb *destroy_cb ) +{ + uintptr_t refcnt, new_refcnt; + + refcnt = __atomic_load_n( refp, __ATOMIC_ACQUIRE ); + + /* We promise the caller that we won't decrease refcnt below 0 */ + do { + if ( !refcnt ) { + return refcnt; + } + + new_refcnt = refcnt - 1; + } while ( !__atomic_compare_exchange( refp, &refcnt, &new_refcnt, 0, + __ATOMIC_RELEASE, __ATOMIC_RELAXED ) ); + assert( new_refcnt == refcnt - 1 ); + + if ( !new_refcnt ) { + if ( unlink_cb ) { + unlink_cb( object ); + } + epoch_append( object, destroy_cb ); + } + + return refcnt; +} diff --git a/servers/lloadd/epoch.h b/servers/lloadd/epoch.h new file mode 100644 index 0000000..06b70be --- /dev/null +++ b/servers/lloadd/epoch.h @@ -0,0 +1,148 @@ +/* epoch.h - epoch based memory reclamation */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2018-2022 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 + * . + */ + +#ifndef __LLOAD_EPOCH_H +#define __LLOAD_EPOCH_H + +/** @file epoch.h + * + * Implementation of epoch based memory reclamation, in principle + * similar to the algorithm presented in + * https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-579.pdf + */ + +typedef uintptr_t epoch_t; + +/** @brief A callback function used to free object and associated data */ +typedef void (dispose_cb)( void *object ); + +/** @brief Initiate global state */ +void epoch_init( void ); + +/** @brief Finalise global state and free any objects still pending */ +void epoch_shutdown( void ); + +/** @brief Register thread as active + * + * In order to safely access managed objects, a thread should call + * this function or make sure no other thread is running (e.g. config + * pause, late shutdown). After calling this, it is guaranteed that no + * reachable objects will be freed before all threads have called + * `epoch_leave( current_epoch + 1 )` so it is essential that there + * is an upper limit to the amount of time between #epoch_join and + * corresponding #epoch_leave or the number of unfreed objects might + * grow without bounds. + * + * To simplify locking, memory is only freed when the current epoch + * is advanced rather than on leaving it. + * + * Can be safely called multiple times by the same thread as long as + * a matching #epoch_leave() call is made eventually. + * + * @return The observed epoch, to be passed to #epoch_leave() + */ +epoch_t epoch_join( void ); + +/** @brief Register thread as inactive + * + * A thread should call this after they are finished with work + * performed since matching call to #epoch_join(). It is not safe + * to keep a local reference to managed objects after this call + * unless other precautions have been made to prevent it being + * released. + * + * @param[in] epoch Epoch identifier returned by a previous call to + * #epoch_join(). + */ +void epoch_leave( epoch_t epoch ); + +/** @brief Return an unreachable object to be freed + * + * The object should already be unreachable at the point of call and + * cb will be invoked when no other thread that could have seen it + * is active any more. This happens when we have advanced by two + * epochs. + * + * @param[in] ptr Object to be released/freed + * @param[in] cb Callback to invoke when safe to do so + */ +void epoch_append( void *ptr, dispose_cb *cb ); + +/** + * \defgroup Reference counting helpers + */ +/**@{*/ + +/** @brief Acquire a reference if possible + * + * Atomically, check reference count is non-zero and increment if so. + * Returns old reference count. + * + * @param[in] refp Pointer to a reference counter + * @return 0 if reference was already zero, non-zero if reference + * count was successfully incremented + */ +int acquire_ref( uintptr_t *refp ); + +/** @brief Check reference count and try to decrement + * + * Atomically, decrement reference count if non-zero and register + * object if decremented to zero. Returning previous reference count. + * + * @param[in] refp Pointer to a reference counter + * @param[in] object The managed object + * @param[in] cb Callback to invoke when safe to do so + * @return 0 if reference was already zero, non-zero if reference + * count was non-zero at the time of call + */ +int try_release_ref( + uintptr_t *refp, + void *object, + dispose_cb *unlink_cb, + dispose_cb *destroy_cb ); + +/** @brief Read reference count + * + * @param[in] object Pointer to the managed object + * @param[in] ref_field Member where reference count is stored in + * the object + * @return Current value of reference counter + */ +#define IS_ALIVE( object, ref_field ) \ + __atomic_load_n( &(object)->ref_field, __ATOMIC_ACQUIRE ) + +/** @brief Release reference + * + * A cheaper alternative to #try_release_ref(), safe only when we know + * reference count was already non-zero. + * + * @param[in] object The managed object + * @param[in] ref_field Member where reference count is stored in + * the object + * @param[in] cb Callback to invoke when safe to do so + */ +#define RELEASE_REF( object, ref_field, cb ) \ + do { \ + assert( IS_ALIVE( (object), ref_field ) ); \ + if ( !__atomic_sub_fetch( \ + &(object)->ref_field, 1, __ATOMIC_ACQ_REL ) ) { \ + epoch_append( object, (dispose_cb *)cb ); \ + } \ + } while (0) + +/**@}*/ + +#endif /* __LLOAD_EPOCH_H */ diff --git a/servers/lloadd/extended.c b/servers/lloadd/extended.c new file mode 100644 index 0000000..54d9700 --- /dev/null +++ b/servers/lloadd/extended.c @@ -0,0 +1,220 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2022 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 + * . + */ + +#include "portable.h" + +#include + +#include "lutil.h" +#include "lload.h" + +Avlnode *lload_exop_handlers = NULL; + +#ifdef HAVE_TLS +void *lload_tls_ctx; +LDAP *lload_tls_ld, *lload_tls_backend_ld; +#ifdef BALANCER_MODULE +int lload_use_slap_tls_ctx = 0; +#endif +#endif /* HAVE_TLS */ + +int +handle_starttls( LloadConnection *c, LloadOperation *op ) +{ + struct event_base *base = event_get_base( c->c_read_event ); + LloadOperation *found; + BerElement *output; + char *msg = NULL; + int rc = LDAP_SUCCESS; + + CONNECTION_LOCK(c); + found = ldap_tavl_delete( &c->c_ops, op, operation_client_cmp ); + assert( op == found ); + c->c_n_ops_executing--; + +#ifdef HAVE_TLS + if ( c->c_is_tls == LLOAD_TLS_ESTABLISHED ) { + rc = LDAP_OPERATIONS_ERROR; + msg = "TLS layer already in effect"; + } else if ( c->c_state == LLOAD_C_BINDING ) { + rc = LDAP_OPERATIONS_ERROR; + msg = "bind in progress"; + } else if ( c->c_ops ) { + rc = LDAP_OPERATIONS_ERROR; + msg = "cannot start TLS when operations are outstanding"; + } else if ( !LLOAD_TLS_CTX ) { + rc = LDAP_UNAVAILABLE; + msg = "Could not initialize TLS"; + } +#else /* ! HAVE_TLS */ + rc = LDAP_UNAVAILABLE; + msg = "Could not initialize TLS"; +#endif /* ! HAVE_TLS */ + + CONNECTION_UNLOCK(c); + + Debug( LDAP_DEBUG_STATS, "handle_starttls: " + "handling StartTLS exop connid=%lu rc=%d msg=%s\n", + c->c_connid, rc, msg ); + + if ( rc ) { + /* We've already removed the operation from the queue */ + operation_send_reject( op, rc, msg, 1 ); + return LDAP_SUCCESS; + } + +#ifdef HAVE_TLS + event_del( c->c_read_event ); + event_del( c->c_write_event ); + /* + * At this point, we are the only thread handling the connection: + * - there are no upstream operations + * - the I/O callbacks have been successfully removed + * + * This means we can safely reconfigure both I/O events now. + */ + + checked_lock( &c->c_io_mutex ); + output = c->c_pendingber; + if ( output == NULL && (output = ber_alloc()) == NULL ) { + checked_unlock( &c->c_io_mutex ); + OPERATION_UNLINK(op); + CONNECTION_LOCK_DESTROY(c); + return -1; + } + c->c_pendingber = output; + ber_printf( output, "t{tit{ess}}", LDAP_TAG_MESSAGE, + LDAP_TAG_MSGID, op->o_client_msgid, + LDAP_RES_EXTENDED, LDAP_SUCCESS, "", "" ); + c->c_io_state &= ~LLOAD_C_READ_HANDOVER; + checked_unlock( &c->c_io_mutex ); + + CONNECTION_LOCK(c); + c->c_read_timeout = lload_timeout_net; + event_assign( c->c_read_event, base, c->c_fd, EV_READ|EV_PERSIST, + client_tls_handshake_cb, c ); + event_add( c->c_read_event, c->c_read_timeout ); + + event_assign( c->c_write_event, base, c->c_fd, EV_WRITE, + client_tls_handshake_cb, c ); + /* We already have something to write */ + event_add( c->c_write_event, lload_write_timeout ); + + op->o_res = LLOAD_OP_COMPLETED; + CONNECTION_UNLOCK(c); + + OPERATION_UNLINK(op); + + return -1; +#endif /* HAVE_TLS */ +} + +int +request_extended( LloadConnection *c, LloadOperation *op ) +{ + ExopHandler *handler, needle = {}; + struct restriction_entry *restriction, rneedle = {}; + BerElement *copy; + struct berval bv; + ber_tag_t tag; + + if ( (copy = ber_alloc()) == NULL ) { + operation_send_reject( op, LDAP_OTHER, "internal error", 0 ); + CONNECTION_LOCK_DESTROY(c); + return -1; + } + + ber_init2( copy, &op->o_request, 0 ); + + tag = ber_skip_element( copy, &bv ); + if ( tag != LDAP_TAG_EXOP_REQ_OID ) { + Debug( LDAP_DEBUG_STATS, "request_extended: " + "no OID present in extended request\n" ); + operation_send_reject( op, LDAP_PROTOCOL_ERROR, "decoding error", 0 ); + CONNECTION_LOCK_DESTROY(c); + return -1; + } + + needle.oid = bv; + + handler = ldap_avl_find( lload_exop_handlers, &needle, exop_handler_cmp ); + if ( handler ) { + Debug( LDAP_DEBUG_TRACE, "request_extended: " + "handling exop OID %.*s internally\n", + (int)bv.bv_len, bv.bv_val ); + ber_free( copy, 0 ); + return handler->func( c, op ); + } + ber_free( copy, 0 ); + + rneedle.oid = bv; + restriction = ldap_tavl_find( lload_exop_actions, &rneedle, + lload_restriction_cmp ); + if ( restriction ) { + op->o_restricted = restriction->action; + } else { + op->o_restricted = lload_default_exop_action; + } + + return request_process( c, op ); +} + +ExopHandler lload_exops[] = { + { BER_BVC(LDAP_EXOP_START_TLS), handle_starttls }, + { BER_BVNULL } +}; + +int +exop_handler_cmp( const void *left, const void *right ) +{ + const struct lload_exop_handlers_t *l = left, *r = right; + return ber_bvcmp( &l->oid, &r->oid ); +} + +int +lload_register_exop_handlers( struct lload_exop_handlers_t *handler ) +{ + for ( ; !BER_BVISNULL( &handler->oid ); handler++ ) { + Debug( LDAP_DEBUG_TRACE, "lload_register_exop_handlers: " + "registering handler for exop oid=%s\n", + handler->oid.bv_val ); + if ( ldap_avl_insert( &lload_exop_handlers, handler, exop_handler_cmp, + ldap_avl_dup_error ) ) { + Debug( LDAP_DEBUG_ANY, "lload_register_exop_handlers: " + "failed to register handler for exop oid=%s\n", + handler->oid.bv_val ); + return -1; + } + } + + return LDAP_SUCCESS; +} + +int +lload_exop_init( void ) +{ + if ( lload_register_exop_handlers( lload_exops ) ) { + return -1; + } + + return LDAP_SUCCESS; +} + +void +lload_exop_destroy( void ) +{ + ldap_avl_free( lload_exop_handlers, NULL ); + lload_exop_handlers = NULL; +} diff --git a/servers/lloadd/init.c b/servers/lloadd/init.c new file mode 100644 index 0000000..5607465 --- /dev/null +++ b/servers/lloadd/init.c @@ -0,0 +1,246 @@ +/* init.c - initialize various things */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2022 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 + * . + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include + +#include +#include +#include + +#include "lload.h" +#include "lber_pvt.h" + +#include "ldap_rq.h" + +#ifndef BALANCER_MODULE +/* + * read-only global variables or variables only written by the listener + * thread (after they are initialized) - no need to protect them with a mutex. + */ +int slap_debug = 0; + +#ifdef LDAP_DEBUG +int ldap_syslog = LDAP_DEBUG_STATS; +#else +int ldap_syslog; +#endif + +#ifdef LOG_DEBUG +int ldap_syslog_level = LOG_DEBUG; +#endif + +/* + * global variables that need mutex protection + */ +ldap_pvt_thread_pool_t connection_pool; +int connection_pool_max = SLAP_MAX_WORKER_THREADS; +int connection_pool_queues = 1; +int slap_tool_thread_max = 1; + +int slapMode = SLAP_UNDEFINED_MODE; +#endif /* !BALANCER_MODULE */ + +static const char *lload_name = NULL; + +int +lload_global_init( void ) +{ + int rc; + + if ( lload_libevent_init() ) { + return -1; + } + +#ifdef HAVE_TLS + if ( ldap_create( &lload_tls_backend_ld ) ) { + return -1; + } + if ( ldap_create( &lload_tls_ld ) ) { + return -1; + } + + /* Library defaults to full certificate checking. This is correct when + * a client is verifying a server because all servers should have a + * valid cert. But few clients have valid certs, so we want our default + * to be no checking. The config file can override this as usual. + */ + rc = LDAP_OPT_X_TLS_NEVER; + (void)ldap_pvt_tls_set_option( + lload_tls_ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &rc ); +#endif + + ldap_pvt_thread_mutex_init( &lload_wait_mutex ); + ldap_pvt_thread_cond_init( &lload_wait_cond ); + ldap_pvt_thread_cond_init( &lload_pause_cond ); + + ldap_pvt_thread_mutex_init( &clients_mutex ); + ldap_pvt_thread_mutex_init( &lload_pin_mutex ); + + if ( lload_exop_init() ) { + return -1; + } + return 0; +} + +int +lload_global_destroy( void ) +{ + if ( !BER_BVISNULL( &lloadd_identity ) ) { + ch_free( lloadd_identity.bv_val ); + BER_BVZERO( &lloadd_identity ); + } + + lload_exop_destroy(); + ldap_tavl_free( lload_control_actions, (AVL_FREE)lload_restriction_free ); + ldap_tavl_free( lload_exop_actions, (AVL_FREE)lload_restriction_free ); + +#ifdef HAVE_TLS + if ( lload_tls_backend_ld ) { + ldap_unbind_ext( lload_tls_backend_ld, NULL, NULL ); + } + if ( lload_tls_ld ) { + ldap_unbind_ext( lload_tls_ld, NULL, NULL ); + } + if ( lload_tls_ctx ) { + ldap_pvt_tls_ctx_free( lload_tls_ctx ); + } +#endif + + ldap_pvt_thread_mutex_destroy( &lload_wait_mutex ); + ldap_pvt_thread_cond_destroy( &lload_wait_cond ); + ldap_pvt_thread_cond_destroy( &lload_pause_cond ); + + ldap_pvt_thread_mutex_destroy( &clients_mutex ); + ldap_pvt_thread_mutex_destroy( &lload_pin_mutex ); + + lload_libevent_destroy(); + + return 0; +} + +int +lload_tls_init( void ) +{ +#ifdef HAVE_TLS + int rc, opt = 1; + + /* Force new ctx to be created */ + rc = ldap_pvt_tls_set_option( lload_tls_ld, LDAP_OPT_X_TLS_NEWCTX, &opt ); + if ( rc == 0 ) { + /* The ctx's refcount is bumped up here */ + ldap_pvt_tls_get_option( + lload_tls_ld, LDAP_OPT_X_TLS_CTX, &lload_tls_ctx ); + } else if ( rc != LDAP_NOT_SUPPORTED ) { + Debug( LDAP_DEBUG_ANY, "lload_global_init: " + "TLS init def ctx failed: %d\n", + rc ); + return -1; + } +#endif + return 0; +} + +int +lload_init( int mode, const char *name ) +{ + int rc = LDAP_SUCCESS; + + assert( mode ); + + if ( slapMode != SLAP_UNDEFINED_MODE ) { + /* Make sure we write something to stderr */ + slap_debug |= LDAP_DEBUG_NONE; + Debug( LDAP_DEBUG_ANY, "%s init: " + "init called twice (old=%d, new=%d)\n", + name, slapMode, mode ); + + return 1; + } + + slapMode = mode; + + switch ( slapMode & SLAP_MODE ) { + case SLAP_SERVER_MODE: + Debug( LDAP_DEBUG_TRACE, "%s init: " + "initiated server.\n", + name ); + + lload_name = name; + + ldap_pvt_thread_pool_init_q( &connection_pool, connection_pool_max, + 0, connection_pool_queues ); + + ldap_pvt_thread_mutex_init( &slapd_rq.rq_mutex ); + LDAP_STAILQ_INIT( &slapd_rq.task_list ); + LDAP_STAILQ_INIT( &slapd_rq.run_list ); + + rc = lload_global_init(); + break; + + default: + slap_debug |= LDAP_DEBUG_NONE; + Debug( LDAP_DEBUG_ANY, "%s init: " + "undefined mode (%d).\n", + name, mode ); + + rc = 1; + break; + } + + return rc; +} + +int +lload_destroy( void ) +{ + int rc = LDAP_SUCCESS; + + Debug( LDAP_DEBUG_TRACE, "%s destroy: " + "freeing system resources.\n", + lload_name ); + + ldap_pvt_thread_pool_free( &connection_pool ); + + switch ( slapMode & SLAP_MODE ) { + case SLAP_SERVER_MODE: + break; + + default: + Debug( LDAP_DEBUG_ANY, "lload_destroy(): " + "undefined mode (%d).\n", + slapMode ); + + rc = 1; + break; + } + + ldap_pvt_thread_destroy(); + + /* should destroy the above mutex */ + return rc; +} diff --git a/servers/lloadd/libevent_support.c b/servers/lloadd/libevent_support.c new file mode 100644 index 0000000..79e7845 --- /dev/null +++ b/servers/lloadd/libevent_support.c @@ -0,0 +1,169 @@ +/* libevent_support.c - routines to bridge libldap and libevent */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2017-2022 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 + * . + */ + +#include "portable.h" + +#include + +#include +#include + +#include "lload.h" +#include "ldap_pvt_thread.h" + +static void * +lload_libevent_mutex_init( unsigned locktype ) +{ + int rc; + ldap_pvt_thread_mutex_t *mutex = + ch_malloc( sizeof(ldap_pvt_thread_mutex_t) ); + + if ( locktype & EVTHREAD_LOCKTYPE_RECURSIVE ) { + rc = ldap_pvt_thread_mutex_recursive_init( mutex ); + } else { + rc = ldap_pvt_thread_mutex_init( mutex ); + } + if ( rc ) { + ch_free( mutex ); + mutex = NULL; + } + return mutex; +} + +static void +lload_libevent_mutex_destroy( void *lock, unsigned locktype ) +{ + int rc; + ldap_pvt_thread_mutex_t *mutex = lock; + + rc = ldap_pvt_thread_mutex_destroy( mutex ); + assert( rc == 0 ); + ch_free( mutex ); +} + +static int +lload_libevent_mutex_lock( unsigned mode, void *lock ) +{ + ldap_pvt_thread_mutex_t *mutex = lock; + + if ( mode & EVTHREAD_TRY ) { + return ldap_pvt_thread_mutex_trylock( mutex ); + } else { + return ldap_pvt_thread_mutex_lock( mutex ); + } +} + +static int +lload_libevent_mutex_unlock( unsigned mode, void *lock ) +{ + ldap_pvt_thread_mutex_t *mutex = lock; + + return ldap_pvt_thread_mutex_unlock( mutex ); +} + +static void * +lload_libevent_cond_init( unsigned condtype ) +{ + int rc; + ldap_pvt_thread_cond_t *cond = + ch_malloc( sizeof(ldap_pvt_thread_cond_t) ); + + assert( condtype == 0 ); + rc = ldap_pvt_thread_cond_init( cond ); + if ( rc ) { + ch_free( cond ); + cond = NULL; + } + return cond; +} + +static void +lload_libevent_cond_destroy( void *c ) +{ + int rc; + ldap_pvt_thread_cond_t *cond = c; + + rc = ldap_pvt_thread_cond_destroy( cond ); + assert( rc == 0 ); + ch_free( c ); +} + +static int +lload_libevent_cond_signal( void *c, int broadcast ) +{ + ldap_pvt_thread_cond_t *cond = c; + + if ( broadcast ) { + return ldap_pvt_thread_cond_broadcast( cond ); + } else { + return ldap_pvt_thread_cond_signal( cond ); + } +} + +static int +lload_libevent_cond_timedwait( + void *c, + void *lock, + const struct timeval *timeout ) +{ + ldap_pvt_thread_cond_t *cond = c; + ldap_pvt_thread_mutex_t *mutex = lock; + + /* + * libevent does not seem to request a timeout, this is true as of 2.1.8 + * that has just been marked the first stable release of the 2.1 series + */ + assert( timeout == NULL ); + + return ldap_pvt_thread_cond_wait( cond, mutex ); +} + +unsigned long +lload_libevent_thread_self( void ) +{ + return (unsigned long)ldap_pvt_thread_self(); +} + +int +lload_libevent_init( void ) +{ + struct evthread_lock_callbacks cbs = { + EVTHREAD_LOCK_API_VERSION, + EVTHREAD_LOCKTYPE_RECURSIVE, + lload_libevent_mutex_init, + lload_libevent_mutex_destroy, + lload_libevent_mutex_lock, + lload_libevent_mutex_unlock + }; + struct evthread_condition_callbacks cond_cbs = { + EVTHREAD_CONDITION_API_VERSION, + lload_libevent_cond_init, + lload_libevent_cond_destroy, + lload_libevent_cond_signal, + lload_libevent_cond_timedwait + }; + + evthread_set_lock_callbacks( &cbs ); + evthread_set_condition_callbacks( &cond_cbs ); + evthread_set_id_callback( lload_libevent_thread_self ); + return 0; +} + +void +lload_libevent_destroy( void ) +{ + libevent_global_shutdown(); +} diff --git a/servers/lloadd/lload-config.h b/servers/lloadd/lload-config.h new file mode 100644 index 0000000..e8ab431 --- /dev/null +++ b/servers/lloadd/lload-config.h @@ -0,0 +1,39 @@ +/* lload-config.h - configuration abstraction structure */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2022 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 + * . + */ + +#ifndef LLOAD_CONFIG_H /* not CONFIG_H because it overlaps with the one from slapd */ +#define LLOAD_CONFIG_H + +#include +#include "../slapd/slap-config.h" + +LDAP_BEGIN_DECL + +int lload_config_fp_parse_line( ConfigArgs *c ); + +int lload_config_get_vals( ConfigTable *ct, ConfigArgs *c ); +int lload_config_add_vals( ConfigTable *ct, ConfigArgs *c ); + +void lload_init_config_argv( ConfigArgs *c ); +int lload_read_config_file( const char *fname, int depth, ConfigArgs *cf, ConfigTable *cft ); + +ConfigTable *lload_config_find_keyword( ConfigTable *ct, ConfigArgs *c ); + +LloadListener *lload_config_check_my_url( const char *url, LDAPURLDesc *lud ); + +LDAP_END_DECL + +#endif /* LLOAD_CONFIG_H */ diff --git a/servers/lloadd/lload.h b/servers/lloadd/lload.h new file mode 100644 index 0000000..f9144a5 --- /dev/null +++ b/servers/lloadd/lload.h @@ -0,0 +1,601 @@ +/* lload.h - load balancer include file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2022 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 + * . + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#ifndef _LLOAD_H_ +#define _LLOAD_H_ + +#include "ldap_defaults.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ldap_avl.h" + +#include "../servers/slapd/slap.h" +#include "../slapd/back-monitor/back-monitor.h" + +#ifndef ldap_debug +#define ldap_debug slap_debug +#endif + +#include "ldap_log.h" + +#include +#include + +#include "lber_pvt.h" +#include "ldap_pvt.h" +#include "ldap_pvt_thread.h" +#include "ldap_queue.h" + +#include + +#ifdef HAVE_CYRUS_SASL +#ifdef HAVE_SASL_SASL_H +#include +#else +#include +#endif +#endif /* HAVE_CYRUS_SASL */ + +LDAP_BEGIN_DECL + +#ifdef SERVICE_NAME +#undef SERVICE_NAME +#endif + +#define SERVICE_NAME OPENLDAP_PACKAGE "-lloadd" + +#define LLOAD_SB_MAX_INCOMING_CLIENT ( ( 1 << 24 ) - 1 ) +#define LLOAD_SB_MAX_INCOMING_UPSTREAM ( ( 1 << 24 ) - 1 ) + +#define LLOAD_CONN_MAX_PDUS_PER_CYCLE_DEFAULT 10 + +#define BER_BV_OPTIONAL( bv ) ( BER_BVISNULL( bv ) ? NULL : ( bv ) ) + +#include + +#define checked_lock( mutex ) \ + if ( ldap_pvt_thread_mutex_lock( mutex ) != 0 ) assert(0) +#define checked_unlock( mutex ) \ + if ( ldap_pvt_thread_mutex_unlock( mutex ) != 0 ) assert(0) + +#ifdef LDAP_THREAD_DEBUG +#define assert_locked( mutex ) \ + if ( ldap_pvt_thread_mutex_trylock( mutex ) == 0 ) assert(0) +#else +#define assert_locked( mutex ) ( (void)0 ) +#endif + +typedef struct LloadTier LloadTier; +typedef struct LloadBackend LloadBackend; +typedef struct LloadPendingConnection LloadPendingConnection; +typedef struct LloadConnection LloadConnection; +typedef struct LloadOperation LloadOperation; +typedef struct LloadChange LloadChange; +/* end of forward declarations */ + +typedef LDAP_STAILQ_HEAD(TierSt, LloadTier) lload_t_head; +typedef LDAP_CIRCLEQ_HEAD(BeSt, LloadBackend) lload_b_head; +typedef LDAP_CIRCLEQ_HEAD(ConnSt, LloadConnection) lload_c_head; + +LDAP_SLAPD_V (lload_t_head) tiers; +LDAP_SLAPD_V (lload_c_head) clients; +LDAP_SLAPD_V (struct slap_bindconf) bindconf; +LDAP_SLAPD_V (struct berval) lloadd_identity; + +/* Used to coordinate server (un)pause, shutdown */ +LDAP_SLAPD_V (ldap_pvt_thread_mutex_t) lload_wait_mutex; +LDAP_SLAPD_V (ldap_pvt_thread_cond_t) lload_pause_cond; +LDAP_SLAPD_V (ldap_pvt_thread_cond_t) lload_wait_cond; + +typedef int lload_cf_aux_table_parse_x( struct berval *val, + void *bc, + slap_cf_aux_table *tab0, + const char *tabmsg, + int unparse ); + +typedef struct LloadListener LloadListener; + +enum lc_type { + LLOAD_CHANGE_UNDEFINED = 0, + LLOAD_CHANGE_MODIFY, + LLOAD_CHANGE_ADD, + LLOAD_CHANGE_DEL, +}; + +enum lc_object { + LLOAD_UNDEFINED = 0, + LLOAD_DAEMON, + /* + LLOAD_BINDCONF, + */ + LLOAD_TIER, + LLOAD_BACKEND, +}; + +enum lcf_daemon { + LLOAD_DAEMON_MOD_THREADS = 1 << 0, + LLOAD_DAEMON_MOD_FEATURES = 1 << 1, + LLOAD_DAEMON_MOD_TLS = 1 << 2, + LLOAD_DAEMON_MOD_LISTENER_ADD = 1 << 3, + LLOAD_DAEMON_MOD_LISTENER_REPLACE = 1 << 4, + LLOAD_DAEMON_MOD_BINDCONF = 1 << 5, +}; + +enum lcf_tier { + LLOAD_TIER_MOD_TYPE = 1 << 0, +}; + +enum lcf_backend { + LLOAD_BACKEND_MOD_OTHER = 1 << 0, + LLOAD_BACKEND_MOD_CONNS = 1 << 1, +}; + +struct LloadChange { + enum lc_type type; + enum lc_object object; + union { + int generic; + enum lcf_daemon daemon; + enum lcf_tier tier; + enum lcf_backend backend; + } flags; + void *target; +}; + +typedef enum { +#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS + LLOAD_FEATURE_VC = 1 << 0, +#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */ + LLOAD_FEATURE_PROXYAUTHZ = 1 << 1, + LLOAD_FEATURE_PAUSE = 1 << 2, +} lload_features_t; + +#define LLOAD_FEATURE_SUPPORTED_MASK ( \ + LLOAD_FEATURE_PROXYAUTHZ | \ + 0 ) + +#ifdef BALANCER_MODULE +#define LLOAD_TLS_CTX ( lload_use_slap_tls_ctx ? slap_tls_ctx : lload_tls_ctx ) +#else +#define LLOAD_TLS_CTX ( lload_tls_ctx ) +#endif + +enum lload_tls_type { + LLOAD_CLEARTEXT = 0, + LLOAD_LDAPS, + LLOAD_STARTTLS_OPTIONAL, + LLOAD_STARTTLS, + LLOAD_TLS_ESTABLISHED, +}; + +struct LloadPendingConnection { + LloadBackend *backend; + + struct event *event; + ber_socket_t fd; + + LDAP_LIST_ENTRY(LloadPendingConnection) next; +}; + +typedef struct lload_counters_t { + ldap_pvt_mp_t lc_ops_completed; + ldap_pvt_mp_t lc_ops_received; + ldap_pvt_mp_t lc_ops_forwarded; + ldap_pvt_mp_t lc_ops_rejected; + ldap_pvt_mp_t lc_ops_failed; +} lload_counters_t; + +enum { + LLOAD_STATS_OPS_BIND = 0, + LLOAD_STATS_OPS_OTHER, + LLOAD_STATS_OPS_LAST +}; + +typedef struct lload_global_stats_t { + ldap_pvt_mp_t global_incoming; + ldap_pvt_mp_t global_outgoing; + lload_counters_t counters[LLOAD_STATS_OPS_LAST]; +} lload_global_stats_t; + +typedef LloadTier *(LloadTierInit)( void ); +typedef int (LloadTierConfigCb)( LloadTier *tier, char *arg ); +typedef int (LloadTierBackendConfigCb)( LloadTier *tier, LloadBackend *b, char *arg ); +typedef int (LloadTierCb)( LloadTier *tier ); +typedef int (LloadTierResetCb)( LloadTier *tier, int shutdown ); +typedef int (LloadTierBackendCb)( LloadTier *tier, LloadBackend *b ); +typedef void (LloadTierChange)( LloadTier *tier, LloadChange *change ); +typedef int (LloadTierSelect)( LloadTier *tier, + LloadOperation *op, + LloadConnection **cp, + int *res, + char **message ); + +struct lload_tier_type { + char *tier_name; + + struct berval tier_oc, tier_backend_oc; + + LloadTierInit *tier_init; + LloadTierConfigCb *tier_config; + LloadTierBackendConfigCb *tier_backend_config; + LloadTierCb *tier_startup; + LloadTierCb *tier_update; + LloadTierResetCb *tier_reset; + LloadTierCb *tier_destroy; + + LloadTierBackendCb *tier_add_backend; + LloadTierBackendCb *tier_remove_backend; + LloadTierChange *tier_change; + + LloadTierSelect *tier_select; +}; + +struct LloadTier { + struct lload_tier_type t_type; + ldap_pvt_thread_mutex_t t_mutex; + + lload_b_head t_backends; + int t_nbackends; + + enum { + LLOAD_TIER_EXCLUSIVE = 1 << 0, /* Reject if busy */ + } t_flags; + + struct berval t_name; +#ifdef BALANCER_MODULE + monitor_subsys_t *t_monitor; +#endif /* BALANCER_MODULE */ + + void *t_private; + LDAP_STAILQ_ENTRY(LloadTier) t_next; +}; + +/* Can hold mutex when locking a linked connection */ +struct LloadBackend { + ldap_pvt_thread_mutex_t b_mutex; + + struct berval b_name, b_uri; + int b_proto, b_port; + enum lload_tls_type b_tls, b_tls_conf; + char *b_host; + + int b_retry_timeout, b_failed; + struct event *b_retry_event; + struct timeval b_retry_tv; + + int b_numconns, b_numbindconns; + int b_bindavail, b_active, b_opening; + lload_c_head b_conns, b_bindconns, b_preparing; + LDAP_LIST_HEAD(ConnectingSt, LloadPendingConnection) b_connecting; + LloadConnection *b_last_conn, *b_last_bindconn; + + long b_max_pending, b_max_conn_pending; + long b_n_ops_executing; + + lload_counters_t b_counters[LLOAD_STATS_OPS_LAST]; + + LloadTier *b_tier; + + time_t b_last_update; + uintptr_t b_fitness; + int b_weight; + + uintptr_t b_operation_count; + uintptr_t b_operation_time; + +#ifdef BALANCER_MODULE + monitor_subsys_t *b_monitor; +#endif /* BALANCER_MODULE */ + + struct evdns_getaddrinfo_request *b_dns_req; + void *b_cookie; + + LDAP_CIRCLEQ_ENTRY(LloadBackend) b_next; +}; + +typedef int (*LloadOperationHandler)( LloadConnection *client, + LloadOperation *op, + BerElement *ber ); +typedef int (*RequestHandler)( LloadConnection *c, LloadOperation *op ); +typedef struct lload_exop_handlers_t { + struct berval oid; + RequestHandler func; +} ExopHandler; + +typedef int (*CONNECTION_PDU_CB)( LloadConnection *c ); +typedef void (*CONNECTION_DESTROY_CB)( LloadConnection *c ); + +/* connection state (protected by c_mutex) */ +enum sc_state { + LLOAD_C_INVALID = 0, /* MUST BE ZERO (0) */ + LLOAD_C_READY, /* ready */ + LLOAD_C_CLOSING, /* closing */ + LLOAD_C_ACTIVE, /* exclusive operation (tls setup, ...) in progress */ + LLOAD_C_BINDING, /* binding */ + LLOAD_C_DYING, /* part-processed dead waiting to be freed, someone + * might still be observing it */ +}; +enum sc_type { + LLOAD_C_OPEN = 0, /* regular connection */ + LLOAD_C_PREPARING, /* upstream connection not assigned yet */ + LLOAD_C_BIND, /* connection used to handle bind client requests if VC not enabled */ + LLOAD_C_PRIVILEGED, /* connection can override proxyauthz control */ +}; +enum sc_io_state { + LLOAD_C_OPERATIONAL = 0, /* all is good */ + LLOAD_C_READ_HANDOVER = 1 << 0, /* A task to process PDUs is scheduled or + * running, do not re-enable c_read_event */ + LLOAD_C_READ_PAUSE = 1 << 1, /* We want to pause reading until the client + * has sufficiently caught up with what we + * sent */ +}; + +/* Tracking whether an operation might cause a client to restrict which + * upstreams are eligible */ +enum op_restriction { + LLOAD_OP_NOT_RESTRICTED, /* no restrictions in place */ + LLOAD_OP_RESTRICTED_WRITE, /* client is restricted to a certain backend with + * a timeout attached */ + LLOAD_OP_RESTRICTED_BACKEND, /* client is restricted to a certain backend, + * without a timeout */ + LLOAD_OP_RESTRICTED_UPSTREAM, /* client is restricted to a certain upstream */ + LLOAD_OP_RESTRICTED_ISOLATE, /* TODO: client is restricted to a certain + * upstream and removes the upstream from the + * pool */ + LLOAD_OP_RESTRICTED_REJECT, /* operation should not be forwarded to any + * backend, either it is processed internally + * or rejected */ +}; + +/* + * represents a connection from an ldap client/to ldap server + */ +struct LloadConnection { + enum sc_state c_state; /* connection state */ + enum sc_type c_type; + enum sc_io_state c_io_state; + ber_socket_t c_fd; + +/* + * LloadConnection reference counting: + * - connection has a reference counter in c_refcnt + * - also a liveness/validity token is added to c_refcnt during + * lload_connection_init, its existence is tracked in c_live and is usually the + * only one that prevents it from being destroyed + * - anyone who needs to be able to relock the connection after unlocking it has + * to use acquire_ref(), they need to make sure a matching + * RELEASE_REF( c, c_refcnt, c->c_destroy ); is run eventually + * - when a connection is considered dead, use CONNECTION_DESTROY on a locked + * connection, it will be made unreachable from normal places and either + * scheduled for reclamation when safe to do so or if anyone still holds a + * reference, it just gets unlocked and reclaimed after the last ref is + * released + * - CONNECTION_LOCK_DESTROY is a shorthand for locking and CONNECTION_DESTROY + */ + ldap_pvt_thread_mutex_t c_mutex; /* protect the connection */ + uintptr_t c_refcnt, c_live; + CONNECTION_DESTROY_CB c_unlink; + CONNECTION_DESTROY_CB c_destroy; + CONNECTION_PDU_CB c_pdu_cb; +#define CONNECTION_ASSERT_LOCKED(c) assert_locked( &(c)->c_mutex ) +#define CONNECTION_LOCK(c) \ + do { \ + checked_lock( &(c)->c_mutex ); \ + } while (0) +#define CONNECTION_UNLOCK(c) \ + do { \ + checked_unlock( &(c)->c_mutex ); \ + } while (0) +#define CONNECTION_UNLINK_(c) \ + do { \ + if ( __atomic_exchange_n( &(c)->c_live, 0, __ATOMIC_ACQ_REL ) ) { \ + (c)->c_unlink( (c) ); \ + RELEASE_REF( (c), c_refcnt, c->c_destroy ); \ + } \ + } while (0) +#define CONNECTION_DESTROY(c) \ + do { \ + CONNECTION_UNLINK_(c); \ + CONNECTION_UNLOCK(c); \ + } while (0) +#define CONNECTION_LOCK_DESTROY(c) \ + do { \ + CONNECTION_LOCK(c); \ + CONNECTION_DESTROY(c); \ + } while (0); + + Sockbuf *c_sb; /* ber connection stuff */ + + /* set by connection_init */ + unsigned long c_connid; /* unique id of this connection */ + struct berval c_peer_name; /* peer name (trans=addr:port) */ + time_t c_starttime; /* when the connection was opened */ + + time_t c_activitytime; /* when the connection was last used */ + ber_int_t c_next_msgid; /* msgid of the next message */ + + /* must not be used while holding either mutex */ + struct event *c_read_event, *c_write_event; + struct timeval *c_read_timeout; + + /* can only be changed by binding thread */ + struct berval c_sasl_bind_mech; /* mech in progress */ + struct berval c_auth; /* authcDN (possibly in progress) */ + + unsigned long c_pin_id; + +#ifdef HAVE_CYRUS_SASL + sasl_conn_t *c_sasl_authctx; + void *c_sasl_defaults; +#ifdef SASL_CHANNEL_BINDING /* 2.1.25+ */ + sasl_channel_binding_t *c_sasl_cbinding; /* Else cyrus-sasl would happily + * leak it on sasl_dispose */ +#endif /* SASL_CHANNEL_BINDING */ +#endif /* HAVE_CYRUS_SASL */ + +#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS + struct berval c_vc_cookie; +#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */ + + /* Can be held while acquiring c_mutex to inject things into c_ops or + * destroy the connection */ + ldap_pvt_thread_mutex_t c_io_mutex; /* only one pdu written at a time */ + + BerElement *c_currentber; /* ber we're attempting to read */ + BerElement *c_pendingber; /* ber we're attempting to write */ + + TAvlnode *c_ops; /* Operations pending on the connection */ + +#ifdef HAVE_TLS + enum lload_tls_type c_is_tls; /* true if this LDAP over raw TLS */ +#endif + + long c_n_ops_executing; /* num of ops currently executing */ + long c_n_ops_completed; /* num of ops completed */ + lload_counters_t c_counters; /* per connection operation counters */ + + enum op_restriction c_restricted; + uintptr_t c_restricted_inflight; + time_t c_restricted_at; + LloadBackend *c_backend; + LloadConnection *c_linked_upstream; + + TAvlnode *c_linked; + +#ifdef BALANCER_MODULE + struct berval c_monitor_dn; +#endif /* BALANCER_MODULE */ + + /* + * Protected by the CIRCLEQ mutex: + * - Client: clients_mutex + * - Upstream: b->b_mutex + */ + LDAP_CIRCLEQ_ENTRY(LloadConnection) c_next; +}; + +enum op_state { + LLOAD_OP_NOT_FREEING = 0, + LLOAD_OP_DETACHING_CLIENT = 1 << 1, + LLOAD_OP_DETACHING_UPSTREAM = 1 << 0, +}; + +#define LLOAD_OP_DETACHING_MASK \ + ( LLOAD_OP_DETACHING_UPSTREAM | LLOAD_OP_DETACHING_CLIENT ) + +/* operation result for monitoring purposes */ +enum op_result { + LLOAD_OP_REJECTED, /* operation was not forwarded */ + LLOAD_OP_COMPLETED, /* operation sent and response received */ + LLOAD_OP_FAILED, /* operation was forwarded, but no response was received */ +}; + +/* + * Operation reference tracking: + * - o_refcnt is set to 1, never incremented + * - OPERATION_UNLINK sets it to 0 and on transition from 1 clears both + * connection links (o_client, o_upstream) + */ +struct LloadOperation { + uintptr_t o_refcnt; +#define OPERATION_UNLINK(op) \ + try_release_ref( &(op)->o_refcnt, (op), \ + (dispose_cb *)operation_unlink, \ + (dispose_cb *)operation_destroy ) + + LloadConnection *o_client; + unsigned long o_client_connid; + ber_int_t o_client_msgid; + ber_int_t o_saved_msgid; + enum op_restriction o_restricted; + + LloadConnection *o_upstream; + unsigned long o_upstream_connid; + ber_int_t o_upstream_msgid; + struct timeval o_last_response; + + /* Protects o_client, o_upstream links */ + ldap_pvt_thread_mutex_t o_link_mutex; + + ber_tag_t o_tag; + struct timeval o_start; + unsigned long o_pin_id; + + enum op_result o_res; + BerElement *o_ber; + BerValue o_request, o_ctrls; +}; + +struct restriction_entry { + struct berval oid; + enum op_restriction action; +}; + +/* + * listener; need to access it from monitor backend + */ +struct LloadListener { + struct berval sl_url; + struct berval sl_name; + mode_t sl_perms; +#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 */ + int sl_busy; /* Listener is busy (accept thread activated) */ + ber_socket_t sl_sd; + Sockaddr sl_sa; +#define sl_addr sl_sa.sa_in_addr +#define LDAP_TCP_BUFFER +#ifdef LDAP_TCP_BUFFER + int sl_tcp_rmem; /* custom TCP read buffer size */ + int sl_tcp_wmem; /* custom TCP write buffer size */ +#endif +}; + +typedef int (*CONNCB)( LloadConnection *c, void *arg ); + +/* config requires a bi_private with configuration data - dummy for now */ +struct lload_conf_info { + int dummy; +}; +LDAP_END_DECL + +#include "proto-lload.h" +#endif /* _LLOAD_H_ */ diff --git a/servers/lloadd/lloadd.service b/servers/lloadd/lloadd.service new file mode 100644 index 0000000..062b8ca --- /dev/null +++ b/servers/lloadd/lloadd.service @@ -0,0 +1,13 @@ +[Unit] +Description=LDAP Load Balancer Daemon +After=syslog.target network-online.target +Documentation=man:lloadd.conf + +[Service] +Type=notify +Environment="LLOADD_URLS=ldap:/// ldapi:///" "LLOADD_OPTIONS=" +EnvironmentFile=/etc/sysconfig/lloadd +ExecStart=%LIBEXECDIR%/lloadd -d 0 -h ${LLOADD_URLS} $LLOADD_OPTIONS + +[Install] +WantedBy=multi-user.target diff --git a/servers/lloadd/main.c b/servers/lloadd/main.c new file mode 100644 index 0000000..b117726 --- /dev/null +++ b/servers/lloadd/main.c @@ -0,0 +1,955 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2022 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 + * . + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lload.h" +#include "lutil.h" +#include "ldif.h" + +#ifdef LDAP_SIGCHLD +static void wait4child( evutil_socket_t sig, short what, void *arg ); +#endif + +#ifdef SIGPIPE +static void sigpipe( evutil_socket_t sig, short what, void *arg ); +#endif + +#ifdef HAVE_NT_SERVICE_MANAGER +#define MAIN_RETURN(x) return +static struct sockaddr_in bind_addr; + +#define SERVICE_EXIT( e, n ) \ + do { \ + if ( is_NT_Service ) { \ + lutil_ServiceStatus.dwWin32ExitCode = (e); \ + lutil_ServiceStatus.dwServiceSpecificExitCode = (n); \ + } \ + } while (0) + +#else +#define SERVICE_EXIT( e, n ) +#define MAIN_RETURN(x) return (x) +#endif + +struct signal_handler { + int signal; + event_callback_fn handler; + struct event *event; +} signal_handlers[] = { + { LDAP_SIGUSR2, lload_sig_shutdown }, + +#ifdef SIGPIPE + { SIGPIPE, sigpipe }, +#endif +#ifdef SIGHUP + { SIGHUP, lload_sig_shutdown }, +#endif + { SIGINT, lload_sig_shutdown }, + { SIGTERM, lload_sig_shutdown }, +#ifdef SIGTRAP + { SIGTRAP, lload_sig_shutdown }, +#endif +#ifdef LDAP_SIGCHLD + { LDAP_SIGCHLD, wait4child }, +#endif +#ifdef SIGBREAK + /* SIGBREAK is generated when Ctrl-Break is pressed. */ + { SIGBREAK, lload_sig_shutdown }, +#endif + { 0, NULL } +}; + +/* in logging.c */ +extern char *serverName; +extern int slap_debug_orig; + +/* + * when more than one lloadd is running on one machine, each one might have + * it's own LOCAL for syslogging and must have its own pid/args files + */ + +#ifndef HAVE_MKVERSION +const char Versionstr[] = OPENLDAP_PACKAGE + " " OPENLDAP_VERSION " LDAP Load Balancer Server (lloadd)"; +#endif + +#define CHECK_NONE 0x00 +#define CHECK_CONFIG 0x01 +#define CHECK_LOGLEVEL 0x02 +static int check = CHECK_NONE; +static int version = 0; + +static int +slapd_opt_slp( const char *val, void *arg ) +{ +#ifdef HAVE_SLP + /* NULL is default */ + if ( val == NULL || *val == '(' || strcasecmp( val, "on" ) == 0 ) { + slapd_register_slp = 1; + slapd_slp_attrs = ( val != NULL && *val == '(' ) ? val : NULL; + + } else if ( strcasecmp( val, "off" ) == 0 ) { + slapd_register_slp = 0; + + /* NOTE: add support for URL specification? */ + + } else { + fprintf( stderr, "unrecognized value \"%s\" for SLP option\n", val ); + return -1; + } + + return 0; + +#else + fputs( "lloadd: SLP support is not available\n", stderr ); + return 0; +#endif +} + +/* + * Option helper structure: + * + * oh_nam is left-hand part of