summaryrefslogtreecommitdiffstats
path: root/servers/lloadd
diff options
context:
space:
mode:
Diffstat (limited to 'servers/lloadd')
-rw-r--r--servers/lloadd/Makefile.in50
-rw-r--r--servers/lloadd/Makefile_module.in28
-rw-r--r--servers/lloadd/Makefile_server.in79
-rw-r--r--servers/lloadd/backend.c765
-rw-r--r--servers/lloadd/bind.c996
-rw-r--r--servers/lloadd/client.c805
-rw-r--r--servers/lloadd/config.c4032
-rw-r--r--servers/lloadd/connection.c644
-rw-r--r--servers/lloadd/daemon.c1978
-rw-r--r--servers/lloadd/epoch.c321
-rw-r--r--servers/lloadd/epoch.h148
-rw-r--r--servers/lloadd/extended.c220
-rw-r--r--servers/lloadd/init.c246
-rw-r--r--servers/lloadd/libevent_support.c169
-rw-r--r--servers/lloadd/lload-config.h39
-rw-r--r--servers/lloadd/lload.h601
-rw-r--r--servers/lloadd/lloadd.service13
-rw-r--r--servers/lloadd/main.c955
-rw-r--r--servers/lloadd/module_init.c190
-rw-r--r--servers/lloadd/monitor.c1368
-rw-r--r--servers/lloadd/operation.c725
-rw-r--r--servers/lloadd/proto-lload.h254
-rw-r--r--servers/lloadd/tier.c168
-rw-r--r--servers/lloadd/tier_bestof.c328
-rw-r--r--servers/lloadd/tier_roundrobin.c136
-rw-r--r--servers/lloadd/tier_weighted.c224
-rw-r--r--servers/lloadd/upstream.c1184
-rw-r--r--servers/lloadd/value.c67
28 files changed, 16733 insertions, 0 deletions
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 <http://www.openldap.org/>.
+##
+## 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
+## <http://www.OpenLDAP.org/license.html>.
+
+
+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 <http://www.openldap.org/>.
+##
+## 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
+## <http://www.OpenLDAP.org/license.html>.
+
+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 <http://www.openldap.org/>.
+##
+## 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
+## <http://www.OpenLDAP.org/license.html>.
+
+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 <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/socket.h>
+#include <ac/errno.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#include <event2/event.h>
+#include <event2/dns.h>
+
+#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 <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/socket.h>
+#include <ac/errno.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#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 <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/socket.h>
+#include <ac/errno.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#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 *)&copy_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 <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* 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 <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/signal.h>
+#include <ac/socket.h>
+#include <ac/errno.h>
+#include <ac/unistd.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#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> <Mbyte> <hours", 4, 4, 0,
+ ARG_MAGIC|CFG_LOGFILE_ROTATE,
+ &config_logging,
+ NULL, NULL, NULL
+ },
+ { "loglevel", "level", 2, 0, 0,
+ ARG_MAGIC|CFG_LOGLEVEL,
+ &config_logging,
+ NULL, NULL, NULL
+ },
+ { "pidfile", "file", 2, 2, 0,
+ ARG_STRING,
+ &slapd_pid_file,
+ NULL, NULL, NULL
+ },
+ { "restrict", "op_list", 2, 0, 0,
+ ARG_MAGIC,
+ &config_restrict,
+ NULL, NULL, NULL
+ },
+ { "sockbuf_max_incoming_client", "max", 2, 2, 0,
+ ARG_BER_LEN_T|ARG_MAGIC|CFG_MAXBUF_CLIENT,
+ &config_generic,
+ "( OLcfgBkAt:13.6 "
+ "NAME 'olcBkLloadSockbufMaxClient' "
+ "DESC 'The maximum LDAP PDU size accepted coming from clients' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL,
+ { .v_ber_t = LLOAD_SB_MAX_INCOMING_CLIENT }
+ },
+ { "sockbuf_max_incoming_upstream", "max", 2, 2, 0,
+ ARG_BER_LEN_T|ARG_MAGIC|CFG_MAXBUF_UPSTREAM,
+ &config_generic,
+ "( OLcfgBkAt:13.7 "
+ "NAME 'olcBkLloadSockbufMaxUpstream' "
+ "DESC 'The maximum LDAP PDU size accepted coming from upstream' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL,
+ { .v_ber_t = LLOAD_SB_MAX_INCOMING_UPSTREAM }
+ },
+ { "tcp-buffer", "[listener=<listener>] [{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> <action", 3, 3, 0,
+ ARG_MAGIC|CFG_RESTRICT_EXOP,
+ &config_restrict_oid,
+ "( OLcfgBkAt:13.37 "
+ "NAME 'olcBkLloadRestrictExop' "
+ "DESC 'Restrict upstream selection after forwarding an extended operation' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL
+ },
+ { "restrict_control", "OID> <action", 3, 3, 0,
+ ARG_MAGIC|CFG_RESTRICT_CONTROL,
+ &config_restrict_oid,
+ "( OLcfgBkAt:13.38 "
+ "NAME 'olcBkLloadRestrictControl' "
+ "DESC 'Restrict upstream selection after forwarding a control' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL
+ },
+
+ /* cn=config only options */
+#ifdef BALANCER_MODULE
+ { "", "uri", 2, 2, 0,
+ ARG_BERVAL|ARG_MAGIC|CFG_URI,
+ &backend_cf_gen,
+ "( OLcfgBkAt:13.27 "
+ "NAME 'olcBkLloadBackendUri' "
+ "DESC 'URI to contact the server on' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "", NULL, 2, 2, 0,
+ ARG_UINT|ARG_MAGIC|CFG_NUMCONNS,
+ &backend_cf_gen,
+ "( OLcfgBkAt:13.28 "
+ "NAME 'olcBkLloadNumconns' "
+ "DESC 'Number of regular connections to maintain' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "", NULL, 2, 2, 0,
+ ARG_UINT|ARG_MAGIC|CFG_BINDCONNS,
+ &backend_cf_gen,
+ "( OLcfgBkAt:13.29 "
+ "NAME 'olcBkLloadBindconns' "
+ "DESC 'Number of bind connections to maintain' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "", NULL, 2, 2, 0,
+ ARG_UINT|ARG_MAGIC|CFG_RETRY,
+ &backend_cf_gen,
+ "( OLcfgBkAt:13.30 "
+ "NAME 'olcBkLloadRetry' "
+ "DESC 'Number of seconds to wait before trying to reconnect' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "", NULL, 2, 2, 0,
+ ARG_UINT|ARG_MAGIC|CFG_MAX_PENDING_OPS,
+ &backend_cf_gen,
+ "( OLcfgBkAt:13.31 "
+ "NAME 'olcBkLloadMaxPendingOps' "
+ "DESC 'Maximum number of pending operations for this backend' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "", NULL, 2, 2, 0,
+ ARG_UINT|ARG_MAGIC|CFG_MAX_PENDING_CONNS,
+ &backend_cf_gen,
+ "( OLcfgBkAt:13.32 "
+ "NAME 'olcBkLloadMaxPendingConns' "
+ "DESC 'Maximum number of pending operations on each connection' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "", NULL, 2, 2, 0,
+ ARG_BERVAL|ARG_MAGIC|CFG_STARTTLS,
+ &backend_cf_gen,
+ "( OLcfgBkAt:13.34 "
+ "NAME 'olcBkLloadStartTLS' "
+ "DESC 'Whether StartTLS should be attempted on the connection' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "", NULL, 2, 2, 0,
+ ARG_MAGIC|ARG_UINT|CFG_WEIGHT,
+ &backend_cf_gen,
+ "( OLcfgBkAt:13.40 "
+ "NAME 'olcBkLloadWeight' "
+ "DESC 'Backend weight' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL,
+ { .v_uint = 0 },
+ },
+#endif /* BALANCER_MODULE */
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED, NULL }
+};
+
+#ifdef BALANCER_MODULE
+static ConfigCfAdd lload_cfadd;
+
+static ConfigLDAPadd lload_backend_ldadd;
+static ConfigLDAPadd lload_tier_ldadd;
+
+#ifdef SLAP_CONFIG_DELETE
+static ConfigLDAPdel lload_backend_lddel;
+static ConfigLDAPdel lload_tier_lddel;
+#endif /* SLAP_CONFIG_DELETE */
+
+static ConfigOCs lloadocs[] = {
+ { "( OLcfgBkOc:13.1 "
+ "NAME 'olcBkLloadConfig' "
+ "DESC 'Lload backend configuration' "
+ "SUP olcBackendConfig "
+ "MUST ( olcBkLloadBindconf "
+ "$ olcBkLloadIOThreads "
+ "$ olcBkLloadListen "
+ "$ olcBkLloadSockbufMaxClient "
+ "$ olcBkLloadSockbufMaxUpstream "
+ "$ olcBkLloadMaxPDUPerCycle "
+ "$ olcBkLloadIOTimeout ) "
+ "MAY ( olcBkLloadFeature "
+ "$ olcBkLloadTcpBuffer "
+ "$ olcBkLloadTLSCACertificateFile "
+ "$ olcBkLloadTLSCACertificatePath "
+ "$ olcBkLloadTLSCertificateFile "
+ "$ olcBkLloadTLSCertificateKeyFile "
+ "$ olcBkLloadTLSCipherSuite "
+ "$ olcBkLloadTLSCRLCheck "
+ "$ olcBkLloadTLSRandFile "
+ "$ olcBkLloadVerifyClient "
+ "$ olcBkLloadTLSDHParamFile "
+ "$ olcBkLloadTLSECName "
+ "$ olcBkLloadTLSProtocolMin "
+ "$ olcBkLloadTLSCRLFile "
+ "$ olcBkLloadTLSShareSlapdCTX "
+ "$ olcBkLloadClientMaxPending "
+ "$ olcBkLloadWriteCoherence "
+ "$ olcBkLloadRestrictExop "
+ "$ olcBkLloadRestrictControl "
+ ") )",
+ Cft_Backend, config_back_cf_table,
+ NULL,
+ lload_cfadd,
+ },
+ { "( OLcfgBkOc:13.2 "
+ "NAME 'olcBkLloadBackendConfig' "
+ "DESC 'Lload backend server configuration' "
+ "SUP olcConfig STRUCTURAL "
+ "MUST ( cn "
+ "$ olcBkLloadBackendUri "
+ "$ olcBkLloadNumconns "
+ "$ olcBkLloadBindconns "
+ "$ olcBkLloadRetry "
+ "$ olcBkLloadMaxPendingOps "
+ "$ olcBkLloadMaxPendingConns ) "
+ "MAY ( olcBkLloadStartTLS "
+ "$ olcBkLloadWeight ) "
+ ") )",
+ Cft_Misc, config_back_cf_table,
+ lload_backend_ldadd,
+ NULL,
+#ifdef SLAP_CONFIG_DELETE
+ lload_backend_lddel,
+#endif /* SLAP_CONFIG_DELETE */
+ },
+ { "( OLcfgBkOc:13.3 "
+ "NAME 'olcBkLloadTierConfig' "
+ "DESC 'Lload tier configuration' "
+ "SUP olcConfig STRUCTURAL "
+ "MUST ( cn "
+ "$ olcBkLloadTierType "
+ ") )",
+ Cft_Misc, config_back_cf_table,
+ lload_tier_ldadd,
+ NULL,
+#ifdef SLAP_CONFIG_DELETE
+ lload_tier_lddel,
+#endif /* SLAP_CONFIG_DELETE */
+ },
+ { NULL, 0, NULL }
+};
+#endif /* BALANCER_MODULE */
+
+static int
+config_generic( ConfigArgs *c )
+{
+ enum lcf_daemon flag = 0;
+ int rc = LDAP_SUCCESS;
+
+ if ( c->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=<listener>] [{read|write}=]<size>
+ */
+
+#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", &quote_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", &quote_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 <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* 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 <stdio.h>
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#include <ac/socket.h>
+#include <ac/errno.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#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 <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* 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 <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#include <event2/event.h>
+#include <event2/dns.h>
+#include <event2/listener.h>
+
+#include "lload.h"
+#include "ldap_pvt_thread.h"
+#include "lutil.h"
+
+#include "ldap_rq.h"
+
+#ifdef HAVE_SYSTEMD_SD_DAEMON_H
+#include <systemd/sd-daemon.h>
+#endif
+
+#ifdef LDAP_PF_LOCAL
+#include <sys/stat.h>
+/* this should go in <ldap.h> 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 : "<null>" );
+
+#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 <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/** @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 <epoch.h>
+
+/* 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], &current_refs, &current_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( &current_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 <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#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 <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/string.h>
+
+#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 <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* 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 <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#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 <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/time.h>
+
+#include <event2/event.h>
+#include <event2/thread.h>
+
+#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 <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef LLOAD_CONFIG_H /* not CONFIG_H because it overlaps with the one from slapd */
+#define LLOAD_CONFIG_H
+
+#include <ac/string.h>
+#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 <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* 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 <stdio.h>
+#include <ac/stdlib.h>
+
+#include <sys/types.h>
+#include <ac/syslog.h>
+#include <ac/regex.h>
+#include <ac/signal.h>
+#include <ac/socket.h>
+#include <ac/time.h>
+#include <ac/param.h>
+
+#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 <ldap.h>
+#include <ldap_schema.h>
+
+#include "lber_pvt.h"
+#include "ldap_pvt.h"
+#include "ldap_pvt_thread.h"
+#include "ldap_queue.h"
+
+#include <event2/event.h>
+
+#ifdef HAVE_CYRUS_SASL
+#ifdef HAVE_SASL_SASL_H
+#include <sasl/sasl.h>
+#else
+#include <sasl.h>
+#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 <epoch.h>
+
+#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 <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* 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 <stdio.h>
+
+#include <ac/ctype.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+#include <ac/wait.h>
+#include <ac/errno.h>
+
+#include <event2/event.h>
+
+#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 <option>[=<value>]
+ * oh_fnc is handler function
+ * oh_arg is an optional arg to oh_fnc
+ * oh_usage is the one-line usage string related to the option,
+ * which is assumed to start with <option>[=<value>]
+ *
+ * please leave valid options in the structure, and optionally #ifdef
+ * their processing inside the helper, so that reasonable and helpful
+ * error messages can be generated if a disabled option is requested.
+ */
+struct option_helper {
+ struct berval oh_name;
+ int (*oh_fnc)( const char *val, void *arg );
+ void *oh_arg;
+ const char *oh_usage;
+} option_helpers[] = {
+ { BER_BVC("slp"), slapd_opt_slp, NULL,
+ "slp[={on|off|(attrs)}] enable/disable SLP using (attrs)" },
+ { BER_BVNULL, 0, NULL, NULL }
+};
+
+#if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG)
+#ifdef LOG_LOCAL4
+int
+parse_syslog_user( const char *arg, int *syslogUser )
+{
+ static slap_verbmasks syslogUsers[] = {
+ { BER_BVC("LOCAL0"), LOG_LOCAL0 },
+ { BER_BVC("LOCAL1"), LOG_LOCAL1 },
+ { BER_BVC("LOCAL2"), LOG_LOCAL2 },
+ { BER_BVC("LOCAL3"), LOG_LOCAL3 },
+ { BER_BVC("LOCAL4"), LOG_LOCAL4 },
+ { BER_BVC("LOCAL5"), LOG_LOCAL5 },
+ { BER_BVC("LOCAL6"), LOG_LOCAL6 },
+ { BER_BVC("LOCAL7"), LOG_LOCAL7 },
+#ifdef LOG_USER
+ { BER_BVC("USER"), LOG_USER },
+#endif /* LOG_USER */
+#ifdef LOG_DAEMON
+ { BER_BVC("DAEMON"), LOG_DAEMON },
+#endif /* LOG_DAEMON */
+ { BER_BVNULL, 0 }
+};
+ int i = verb_to_mask( arg, syslogUsers );
+
+ if ( BER_BVISNULL( &syslogUsers[i].word ) ) {
+ Debug( LDAP_DEBUG_ANY, "unrecognized syslog user \"%s\".\n", arg );
+ return 1;
+ }
+
+ *syslogUser = syslogUsers[i].mask;
+
+ return 0;
+}
+#endif /* LOG_LOCAL4 */
+
+int
+parse_syslog_level( const char *arg, int *levelp )
+{
+ static slap_verbmasks str2syslog_level[] = {
+ { BER_BVC("EMERG"), LOG_EMERG },
+ { BER_BVC("ALERT"), LOG_ALERT },
+ { BER_BVC("CRIT"), LOG_CRIT },
+ { BER_BVC("ERR"), LOG_ERR },
+ { BER_BVC("WARNING"), LOG_WARNING },
+ { BER_BVC("NOTICE"), LOG_NOTICE },
+ { BER_BVC("INFO"), LOG_INFO },
+ { BER_BVC("DEBUG"), LOG_DEBUG },
+ { BER_BVNULL, 0 }
+};
+ int i = verb_to_mask( arg, str2syslog_level );
+ if ( BER_BVISNULL( &str2syslog_level[i].word ) ) {
+ Debug( LDAP_DEBUG_ANY, "unknown syslog level \"%s\".\n", arg );
+ return 1;
+ }
+
+ *levelp = str2syslog_level[i].mask;
+
+ return 0;
+}
+#endif /* LDAP_DEBUG && LDAP_SYSLOG */
+
+int
+parse_debug_unknowns( char **unknowns, int *levelp )
+{
+ int i, level, rc = 0;
+
+ for ( i = 0; unknowns[i] != NULL; i++ ) {
+ level = 0;
+ if ( str2loglevel( unknowns[i], &level ) ) {
+ fprintf( stderr, "unrecognized log level \"%s\"\n", unknowns[i] );
+ rc = 1;
+ } else {
+ *levelp |= level;
+ }
+ }
+ return rc;
+}
+
+int
+parse_debug_level( const char *arg, int *levelp, char ***unknowns )
+{
+ int level;
+
+ if ( arg && arg[0] != '-' && !isdigit( (unsigned char)arg[0] ) ) {
+ int i;
+ char **levels;
+
+ levels = ldap_str2charray( arg, "," );
+
+ for ( i = 0; levels[i] != NULL; i++ ) {
+ level = 0;
+
+ if ( str2loglevel( levels[i], &level ) ) {
+ /* remember this for later */
+ ldap_charray_add( unknowns, levels[i] );
+ fprintf( stderr, "unrecognized log level \"%s\" (deferred)\n",
+ levels[i] );
+ } else {
+ *levelp |= level;
+ }
+ }
+
+ ldap_charray_free( levels );
+
+ } else {
+ int rc;
+
+ if ( arg[0] == '-' ) {
+ rc = lutil_atoix( &level, arg, 0 );
+ } else {
+ unsigned ulevel;
+
+ rc = lutil_atoux( &ulevel, arg, 0 );
+ level = (int)ulevel;
+ }
+
+ if ( rc ) {
+ fprintf( stderr,
+ "unrecognized log level "
+ "\"%s\"\n",
+ arg );
+ return 1;
+ }
+
+ if ( level == 0 ) {
+ *levelp = 0;
+
+ } else {
+ *levelp |= level;
+ }
+ }
+
+ return 0;
+}
+
+static void
+usage( char *name )
+{
+ fprintf( stderr, "usage: %s options\n", name );
+ fprintf( stderr,
+ "\t-4\t\tIPv4 only\n"
+ "\t-6\t\tIPv6 only\n"
+ "\t-d level\tDebug level"
+ "\n"
+ "\t-f filename\tConfiguration file\n"
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ "\t-g group\tGroup (id or name) to run as\n"
+#endif
+ "\t-h URLs\t\tList of URLs to serve\n"
+#ifdef SLAP_DEFAULT_SYSLOG_USER
+ "\t-l facility\tSyslog facility (default: LOCAL4)\n"
+#endif
+ "\t-n serverName\tService name\n"
+ "\t-o <opt>[=val] generic means to specify options" );
+ if ( !BER_BVISNULL( &option_helpers[0].oh_name ) ) {
+ int i;
+
+ fprintf( stderr, "; supported options:\n" );
+ for ( i = 0; !BER_BVISNULL( &option_helpers[i].oh_name ); i++ ) {
+ fprintf( stderr, "\t\t%s\n", option_helpers[i].oh_usage );
+ }
+ } else {
+ fprintf( stderr, "\n" );
+ }
+ fprintf( stderr,
+#ifdef HAVE_CHROOT
+ "\t-r directory\tSandbox directory to chroot to\n"
+#endif
+ "\t-s level\tSyslog level\n"
+ "\t-t\t\tCheck configuration file\n"
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ "\t-u user\t\tUser (id or name) to run as\n"
+#endif
+ "\t-V\t\tprint version info (-VV exit afterwards)\n" );
+}
+
+#ifdef HAVE_NT_SERVICE_MANAGER
+void WINAPI
+ServiceMain( DWORD argc, LPTSTR *argv )
+#else
+int
+main( int argc, char **argv )
+#endif
+{
+ int i, no_detach = 0;
+ int rc = 1;
+ char *urls = NULL;
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ char *username = NULL;
+ char *groupname = NULL;
+#endif
+#if defined(HAVE_CHROOT)
+ char *sandbox = NULL;
+#endif
+#ifdef SLAP_DEFAULT_SYSLOG_USER
+ int syslogUser = SLAP_DEFAULT_SYSLOG_USER;
+#endif
+
+#ifndef HAVE_WINSOCK
+ int pid, waitfds[2];
+#endif
+ int g_argc = argc;
+ char **g_argv = argv;
+
+ char *configfile = NULL;
+ char *configdir = NULL;
+ int serverMode = SLAP_SERVER_MODE;
+
+ char **debug_unknowns = NULL;
+ char **syslog_unknowns = NULL;
+
+ int slapd_pid_file_unlink = 0, slapd_args_file_unlink = 0;
+ int firstopt = 1;
+
+ slap_sl_mem_init();
+
+ (void) ldap_pvt_thread_initialize();
+ ldap_pvt_thread_mutex_init( &logfile_mutex );
+
+ serverName = lutil_progname( "lloadd", argc, argv );
+
+#ifdef HAVE_NT_SERVICE_MANAGER
+ {
+ int *ip;
+ char *newConfigFile;
+ char *newConfigDir;
+ char *newUrls;
+ char *regService = NULL;
+
+ if ( is_NT_Service ) {
+ lutil_CommenceStartupProcessing( serverName, lload_sig_shutdown );
+ if ( strcmp( serverName, SERVICE_NAME ) ) regService = serverName;
+ }
+
+ ip = (int *)lutil_getRegParam( regService, "DebugLevel" );
+ if ( ip != NULL ) {
+ slap_debug = *ip;
+ Debug( LDAP_DEBUG_ANY, "new debug level from registry is: %d\n",
+ slap_debug );
+ }
+
+ newUrls = (char *)lutil_getRegParam( regService, "Urls" );
+ if ( newUrls ) {
+ if ( urls ) ch_free( urls );
+
+ urls = ch_strdup( newUrls );
+ Debug( LDAP_DEBUG_ANY, "new urls from registry: %s\n", urls );
+ }
+
+ newConfigFile = (char *)lutil_getRegParam( regService, "ConfigFile" );
+ if ( newConfigFile != NULL ) {
+ configfile = ch_strdup( newConfigFile );
+ Debug( LDAP_DEBUG_ANY, "new config file from registry is: %s\n",
+ configfile );
+ }
+
+ newConfigDir = (char *)lutil_getRegParam( regService, "ConfigDir" );
+ if ( newConfigDir != NULL ) {
+ configdir = ch_strdup( newConfigDir );
+ Debug( LDAP_DEBUG_ANY, "new config dir from registry is: %s\n",
+ configdir );
+ }
+ }
+#endif
+
+ epoch_init();
+
+ while ( (i = getopt( argc, argv,
+ "c:d:f:F:h:n:o:s:tV"
+#ifdef LDAP_PF_INET6
+ "46"
+#endif
+#ifdef HAVE_CHROOT
+ "r:"
+#endif
+#if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG)
+ "S:"
+#ifdef LOG_LOCAL4
+ "l:"
+#endif
+#endif
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ "u:g:"
+#endif
+ )) != EOF ) {
+ switch ( i ) {
+#ifdef LDAP_PF_INET6
+ case '4':
+ slap_inet4or6 = AF_INET;
+ break;
+ case '6':
+ slap_inet4or6 = AF_INET6;
+ break;
+#endif
+
+ case 'h': /* listen URLs */
+ if ( urls != NULL ) free( urls );
+ urls = ch_strdup( optarg );
+ break;
+
+ case 'd': { /* set debug level and 'do not detach' flag */
+ int level = 0;
+
+ if ( strcmp( optarg, "?" ) == 0 ) {
+ check |= CHECK_LOGLEVEL;
+ break;
+ }
+
+ no_detach = 1;
+ if ( parse_debug_level( optarg, &level, &debug_unknowns ) ) {
+ goto destroy;
+ }
+#ifdef LDAP_DEBUG
+ slap_debug |= level;
+#else
+ if ( level != 0 )
+ fputs( "must compile with LDAP_DEBUG for debugging\n",
+ stderr );
+#endif
+ } break;
+
+ case 'f': /* read config file */
+ configfile = ch_strdup( optarg );
+ break;
+
+ case 'o': {
+ char *val = strchr( optarg, '=' );
+ struct berval opt;
+
+ opt.bv_val = optarg;
+
+ if ( val ) {
+ opt.bv_len = ( val - optarg );
+ val++;
+
+ } else {
+ opt.bv_len = strlen( optarg );
+ }
+
+ for ( i = 0; !BER_BVISNULL( &option_helpers[i].oh_name );
+ i++ ) {
+ if ( ber_bvstrcasecmp( &option_helpers[i].oh_name, &opt ) ==
+ 0 ) {
+ assert( option_helpers[i].oh_fnc != NULL );
+ if ( (*option_helpers[i].oh_fnc)(
+ val, option_helpers[i].oh_arg ) == -1 ) {
+ /* we assume the option parsing helper
+ * issues appropriate and self-explanatory
+ * error messages... */
+ goto stop;
+ }
+ break;
+ }
+ }
+
+ if ( BER_BVISNULL( &option_helpers[i].oh_name ) ) {
+ goto unhandled_option;
+ }
+ break;
+ }
+
+ case 's': /* set syslog level */
+ if ( strcmp( optarg, "?" ) == 0 ) {
+ check |= CHECK_LOGLEVEL;
+ break;
+ }
+
+ if ( parse_debug_level(
+ optarg, &ldap_syslog, &syslog_unknowns ) ) {
+ goto destroy;
+ }
+ break;
+
+#if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG)
+ case 'S':
+ if ( parse_syslog_level( optarg, &ldap_syslog_level ) ) {
+ goto destroy;
+ }
+ break;
+
+#ifdef LOG_LOCAL4
+ case 'l': /* set syslog local user */
+ if ( parse_syslog_user( optarg, &syslogUser ) ) {
+ goto destroy;
+ }
+ break;
+#endif
+#endif /* LDAP_DEBUG && LDAP_SYSLOG */
+
+#ifdef HAVE_CHROOT
+ case 'r':
+ if ( sandbox ) free( sandbox );
+ sandbox = ch_strdup( optarg );
+ break;
+#endif
+
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ case 'u': /* user name */
+ if ( username ) free( username );
+ username = ch_strdup( optarg );
+ break;
+
+ case 'g': /* group name */
+ if ( groupname ) free( groupname );
+ groupname = ch_strdup( optarg );
+ break;
+#endif /* SETUID && GETUID */
+
+ case 'n': /* NT service name */
+ serverName = ch_strdup( optarg );
+ break;
+
+ case 't':
+ check |= CHECK_CONFIG;
+ break;
+
+ case 'V':
+ version++;
+ break;
+
+ default:
+unhandled_option:;
+ usage( argv[0] );
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 15 );
+ goto stop;
+ }
+
+ if ( firstopt ) {
+ firstopt = 0;
+ }
+ }
+
+ if ( optind != argc ) goto unhandled_option;
+
+ ber_set_option( NULL, LBER_OPT_LOG_PRINT_FN, slap_debug_print );
+ ber_set_option( NULL, LBER_OPT_DEBUG_LEVEL, &slap_debug );
+ ldap_set_option( NULL, LDAP_OPT_DEBUG_LEVEL, &slap_debug );
+ ldif_debug = slap_debug;
+ slap_debug_orig = slap_debug;
+
+ if ( version ) {
+ fprintf( stderr, "%s\n", Versionstr );
+
+ if ( version > 1 ) goto stop;
+ }
+
+#if defined(LDAP_DEBUG) && defined(LDAP_SYSLOG)
+ {
+ char *logName;
+#ifdef HAVE_EBCDIC
+ logName = ch_strdup( serverName );
+ __atoe( logName );
+#else
+ logName = serverName;
+#endif
+
+#ifdef LOG_LOCAL4
+ openlog( logName, OPENLOG_OPTIONS, syslogUser );
+#elif defined LOG_DEBUG
+ openlog( logName, OPENLOG_OPTIONS );
+#endif
+#ifdef HAVE_EBCDIC
+ free( logName );
+#endif
+ }
+#endif /* LDAP_DEBUG && LDAP_SYSLOG */
+
+ Debug( LDAP_DEBUG_ANY, "%s", Versionstr );
+
+ global_host = ldap_pvt_get_fqdn( NULL );
+
+ if ( check == CHECK_NONE && lloadd_listeners_init( urls ) != 0 ) {
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 16 );
+ goto stop;
+ }
+
+#if defined(HAVE_CHROOT)
+ if ( sandbox ) {
+ if ( chdir( sandbox ) ) {
+ perror( "chdir" );
+ rc = 1;
+ goto stop;
+ }
+ if ( chroot( sandbox ) ) {
+ perror( "chroot" );
+ rc = 1;
+ goto stop;
+ }
+ if ( chdir( "/" ) ) {
+ perror( "chdir" );
+ rc = 1;
+ goto stop;
+ }
+ }
+#endif
+
+#if defined(HAVE_SETUID) && defined(HAVE_SETGID)
+ if ( username != NULL || groupname != NULL ) {
+ slap_init_user( username, groupname );
+ }
+#endif
+
+ rc = lload_init( serverMode, serverName );
+ if ( rc ) {
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 18 );
+ goto destroy;
+ }
+
+ if ( lload_read_config( configfile, configdir ) != 0 ) {
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 19 );
+
+ if ( check & CHECK_CONFIG ) {
+ fprintf( stderr, "config check failed\n" );
+ }
+
+ goto destroy;
+ }
+
+ if ( debug_unknowns ) {
+ rc = parse_debug_unknowns( debug_unknowns, &slap_debug );
+ ldap_charray_free( debug_unknowns );
+ debug_unknowns = NULL;
+ if ( rc ) goto destroy;
+ }
+ if ( syslog_unknowns ) {
+ rc = parse_debug_unknowns( syslog_unknowns, &ldap_syslog );
+ ldap_charray_free( syslog_unknowns );
+ syslog_unknowns = NULL;
+ if ( rc ) goto destroy;
+ }
+
+ if ( check & CHECK_LOGLEVEL ) {
+ rc = 0;
+ goto destroy;
+ }
+
+ if ( check & CHECK_CONFIG ) {
+ fprintf( stderr, "config check succeeded\n" );
+
+ check &= ~CHECK_CONFIG;
+ if ( check == CHECK_NONE ) {
+ rc = 0;
+ goto destroy;
+ }
+ }
+
+#ifdef HAVE_TLS
+ rc = ldap_pvt_tls_init( 1 );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "main: "
+ "TLS init failed: %d\n",
+ rc );
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 20 );
+ goto destroy;
+ }
+
+ if ( lload_tls_init() ) {
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 20 );
+ goto destroy;
+ }
+#endif
+
+ daemon_base = event_base_new();
+ if ( !daemon_base ) {
+ Debug( LDAP_DEBUG_ANY, "main: "
+ "main event base allocation failed\n" );
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 21 );
+ goto destroy;
+ }
+
+ for ( i = 0; signal_handlers[i].signal; i++ ) {
+ struct event *event;
+ event = evsignal_new( daemon_base, signal_handlers[i].signal,
+ signal_handlers[i].handler, daemon_base );
+ if ( !event || event_add( event, NULL ) ) {
+ Debug( LDAP_DEBUG_ANY, "main: "
+ "failed to register a handler for signal %d\n",
+ signal_handlers[i].signal );
+ rc = 1;
+ SERVICE_EXIT( ERROR_SERVICE_SPECIFIC_ERROR, 21 );
+ goto destroy;
+ }
+ signal_handlers[i].event = event;
+ }
+
+#ifndef HAVE_WINSOCK
+ if ( !no_detach ) {
+ if ( lutil_pair( waitfds ) < 0 ) {
+ Debug( LDAP_DEBUG_ANY, "main: "
+ "lutil_pair failed\n" );
+ rc = 1;
+ goto destroy;
+ }
+ pid = lutil_detach( no_detach, 0 );
+ if ( pid ) {
+ char buf[4];
+ rc = EXIT_SUCCESS;
+ close( waitfds[1] );
+ if ( read( waitfds[0], buf, 1 ) != 1 ) rc = EXIT_FAILURE;
+ _exit( rc );
+ } else {
+ close( waitfds[0] );
+ }
+ }
+#endif /* HAVE_WINSOCK */
+
+ if ( slapd_pid_file != NULL ) {
+ FILE *fp = fopen( slapd_pid_file, "w" );
+
+ if ( fp == NULL ) {
+ char ebuf[128];
+ int save_errno = errno;
+
+ Debug( LDAP_DEBUG_ANY, "unable to open pid file "
+ "\"%s\": %d (%s)\n",
+ slapd_pid_file, save_errno,
+ AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+
+ free( slapd_pid_file );
+ slapd_pid_file = NULL;
+
+ rc = 1;
+ goto destroy;
+ }
+ fprintf( fp, "%d\n", (int)getpid() );
+ fclose( fp );
+ slapd_pid_file_unlink = 1;
+ }
+
+ if ( slapd_args_file != NULL ) {
+ FILE *fp = fopen( slapd_args_file, "w" );
+
+ if ( fp == NULL ) {
+ char ebuf[128];
+ int save_errno = errno;
+
+ Debug( LDAP_DEBUG_ANY, "unable to open args file "
+ "\"%s\": %d (%s)\n",
+ slapd_args_file, save_errno,
+ AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+
+ free( slapd_args_file );
+ slapd_args_file = NULL;
+
+ rc = 1;
+ goto destroy;
+ }
+
+ for ( i = 0; i < g_argc; i++ ) {
+ fprintf( fp, "%s ", g_argv[i] );
+ }
+ fprintf( fp, "\n" );
+ fclose( fp );
+ slapd_args_file_unlink = 1;
+ }
+
+ /*
+ * FIXME: moved here from lloadd_daemon_task()
+ * because back-monitor db_open() needs it
+ */
+ time( &starttime );
+
+ Debug( LDAP_DEBUG_ANY, "lloadd starting\n" );
+
+#ifndef HAVE_WINSOCK
+ if ( !no_detach ) {
+ (void)!write( waitfds[1], "1", 1 );
+ close( waitfds[1] );
+ }
+#endif
+
+#ifdef HAVE_NT_EVENT_LOG
+ if ( is_NT_Service )
+ lutil_LogStartedEvent( serverName, slap_debug,
+ configfile ? configfile : LLOADD_DEFAULT_CONFIGFILE, urls );
+#endif
+
+ rc = lloadd_daemon( daemon_base );
+
+#ifdef HAVE_NT_SERVICE_MANAGER
+ /* Throw away the event that we used during the startup process. */
+ if ( is_NT_Service ) ldap_pvt_thread_cond_destroy( &started_event );
+#endif
+
+destroy:
+ if ( daemon_base ) {
+ for ( i = 0; signal_handlers[i].signal; i++ ) {
+ if ( signal_handlers[i].event ) {
+ event_del( signal_handlers[i].event );
+ event_free( signal_handlers[i].event );
+ }
+ }
+ event_base_free( daemon_base );
+ }
+
+ if ( check & CHECK_LOGLEVEL ) {
+ (void)loglevel_print( stdout );
+ }
+ /* remember an error during destroy */
+ rc |= lload_global_destroy();
+ rc |= lload_destroy();
+
+stop:
+#ifdef HAVE_NT_EVENT_LOG
+ if ( is_NT_Service ) lutil_LogStoppedEvent( serverName );
+#endif
+
+ Debug( LDAP_DEBUG_ANY, "lloadd stopped.\n" );
+
+#ifdef HAVE_NT_SERVICE_MANAGER
+ lutil_ReportShutdownComplete();
+#endif
+
+#ifdef LOG_DEBUG
+ closelog();
+#endif
+ lloadd_daemon_destroy();
+
+#ifdef HAVE_TLS
+ ldap_pvt_tls_destroy();
+#endif
+
+ if ( slapd_pid_file_unlink ) {
+ unlink( slapd_pid_file );
+ }
+ if ( slapd_args_file_unlink ) {
+ unlink( slapd_args_file );
+ }
+
+ lload_config_destroy();
+
+ if ( configfile ) ch_free( configfile );
+ if ( configdir ) ch_free( configdir );
+ if ( urls ) ch_free( urls );
+ if ( global_host ) ch_free( global_host );
+
+ /* kludge, get symbols referenced */
+ ldap_tavl_free( NULL, NULL );
+
+ ldap_pvt_thread_mutex_destroy( &logfile_mutex );
+ MAIN_RETURN(rc);
+}
+
+#ifdef SIGPIPE
+
+/*
+ * Catch and discard terminated child processes, to avoid zombies.
+ */
+
+static void
+sigpipe( evutil_socket_t sig, short what, void *arg )
+{
+}
+
+#endif /* SIGPIPE */
+
+#ifdef LDAP_SIGCHLD
+
+/*
+ * Catch and discard terminated child processes, to avoid zombies.
+ */
+
+static void
+wait4child( evutil_socket_t sig, short what, void *arg )
+{
+ int save_errno = errno;
+
+#ifdef WNOHANG
+ do
+ errno = 0;
+#ifdef HAVE_WAITPID
+ while ( waitpid( (pid_t)-1, NULL, WNOHANG ) > 0 || errno == EINTR );
+#else
+ while ( wait3( NULL, WNOHANG, NULL ) > 0 || errno == EINTR );
+#endif
+#else
+ (void)wait( NULL );
+#endif
+ errno = save_errno;
+}
+
+#endif /* LDAP_SIGCHLD */
diff --git a/servers/lloadd/module_init.c b/servers/lloadd/module_init.c
new file mode 100644
index 0000000..a122a52
--- /dev/null
+++ b/servers/lloadd/module_init.c
@@ -0,0 +1,190 @@
+/* module_init.c - module initialization functions */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* 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 <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "../servers/slapd/slap.h"
+#include "../servers/slapd/slap-config.h"
+
+#include "lload.h"
+#include "lber_pvt.h"
+
+#include "ldap_rq.h"
+
+ldap_pvt_thread_t lloadd_main_thread;
+struct lload_conf_info lload_info;
+
+void *
+lload_start_daemon( void *arg )
+{
+ int rc = 0;
+
+ daemon_base = event_base_new();
+ if ( !daemon_base ) {
+ Debug( LDAP_DEBUG_ANY, "lload_start_daemon: "
+ "main event base allocation failed\n" );
+ rc = 1;
+ goto done;
+ }
+
+ rc = lloadd_daemon( daemon_base );
+done:
+ if ( rc != LDAP_SUCCESS ) {
+ assert( lloadd_inited == 0 );
+ checked_lock( &lload_wait_mutex );
+ ldap_pvt_thread_cond_signal( &lload_wait_cond );
+ checked_unlock( &lload_wait_mutex );
+ }
+ return (void *)(uintptr_t)rc;
+}
+
+static int
+lload_pause_cb( BackendInfo *bi )
+{
+ if ( daemon_base ) {
+ lload_pause_server();
+ }
+ return 0;
+}
+
+static int
+lload_unpause_cb( BackendInfo *bi )
+{
+ if ( daemon_base ) {
+ lload_unpause_server();
+ }
+ return 0;
+}
+
+int
+lload_back_open( BackendInfo *bi )
+{
+ int rc = 0;
+
+ if ( slapMode & SLAP_TOOL_MODE ) {
+ return 0;
+ }
+
+ /* This will fail if we ever try to instantiate more than one lloadd within
+ * the process */
+ epoch_init();
+
+ if ( lload_tls_init() != 0 ) {
+ return -1;
+ }
+
+ if ( lload_monitor_open() != 0 ) {
+ return -1;
+ }
+
+ assert( lloadd_get_listeners() );
+
+ checked_lock( &lload_wait_mutex );
+ rc = ldap_pvt_thread_create( &lloadd_main_thread,
+ 0, lload_start_daemon, NULL );
+ if ( !rc ) {
+ ldap_pvt_thread_cond_wait( &lload_wait_cond, &lload_wait_mutex );
+ if ( lloadd_inited != 1 ) {
+ ldap_pvt_thread_join( lloadd_main_thread, (void *)NULL );
+ rc = -1;
+ }
+ }
+ checked_unlock( &lload_wait_mutex );
+ return rc;
+}
+
+int
+lload_back_close( BackendInfo *bi )
+{
+ if ( slapMode & SLAP_TOOL_MODE ) {
+ return 0;
+ }
+
+ assert( lloadd_inited == 1 );
+
+ checked_lock( &lload_wait_mutex );
+ event_base_loopexit( daemon_base, NULL );
+ ldap_pvt_thread_cond_wait( &lload_wait_cond, &lload_wait_mutex );
+ checked_unlock( &lload_wait_mutex );
+ ldap_pvt_thread_join( lloadd_main_thread, (void *)NULL );
+
+ return 0;
+}
+
+int
+lload_back_destroy( BackendInfo *bi )
+{
+ return lload_global_destroy();
+}
+
+int
+lload_back_initialize( BackendInfo *bi )
+{
+ bi->bi_flags = SLAP_BFLAG_STANDALONE;
+ bi->bi_open = lload_back_open;
+ bi->bi_pause = lload_pause_cb;
+ bi->bi_unpause = lload_unpause_cb;
+ bi->bi_close = lload_back_close;
+ bi->bi_destroy = lload_back_destroy;
+
+ bi->bi_db_init = 0;
+ bi->bi_db_config = 0;
+ bi->bi_db_open = 0;
+ bi->bi_db_close = 0;
+ bi->bi_db_destroy = 0;
+
+ bi->bi_op_bind = 0;
+ bi->bi_op_unbind = 0;
+ bi->bi_op_search = 0;
+ bi->bi_op_compare = 0;
+ bi->bi_op_modify = 0;
+ bi->bi_op_modrdn = 0;
+ bi->bi_op_add = 0;
+ bi->bi_op_delete = 0;
+ bi->bi_op_abandon = 0;
+
+ bi->bi_extended = 0;
+
+ bi->bi_chk_referrals = 0;
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = 0;
+
+ if ( lload_global_init() ) {
+ return -1;
+ }
+
+ bi->bi_private = &lload_info;
+ return lload_back_init_cf( bi );
+}
+
+SLAP_BACKEND_INIT_MODULE( lload )
diff --git a/servers/lloadd/monitor.c b/servers/lloadd/monitor.c
new file mode 100644
index 0000000..9eaeecf
--- /dev/null
+++ b/servers/lloadd/monitor.c
@@ -0,0 +1,1368 @@
+/* init.c - initialize various things */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* 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 <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "lload.h"
+#include "lber_pvt.h"
+#include "lutil.h"
+
+#include "ldap_rq.h"
+#include "lload-config.h"
+#include "../slapd/back-monitor/back-monitor.h"
+
+#define LLOAD_MONITOR_BALANCER_NAME "Load Balancer"
+#define LLOAD_MONITOR_BALANCER_RDN \
+ SLAPD_MONITOR_AT "=" LLOAD_MONITOR_BALANCER_NAME
+#define LLOAD_MONITOR_BALANCER_DN \
+ LLOAD_MONITOR_BALANCER_RDN "," SLAPD_MONITOR_BACKEND_DN
+
+#define LLOAD_MONITOR_INCOMING_NAME "Incoming Connections"
+#define LLOAD_MONITOR_INCOMING_RDN \
+ SLAPD_MONITOR_AT "=" LLOAD_MONITOR_INCOMING_NAME
+#define LLOAD_MONITOR_INCOMING_DN \
+ LLOAD_MONITOR_INCOMING_RDN "," LLOAD_MONITOR_BALANCER_DN
+
+#define LLOAD_MONITOR_OPERATIONS_NAME "Operations"
+#define LLOAD_MONITOR_OPERATIONS_RDN \
+ SLAPD_MONITOR_AT "=" LLOAD_MONITOR_OPERATIONS_NAME
+#define LLOAD_MONITOR_OPERATIONS_DN \
+ LLOAD_MONITOR_OPERATIONS_RDN "," LLOAD_MONITOR_BALANCER_DN
+
+#define LLOAD_MONITOR_TIERS_NAME "Backend Tiers"
+#define LLOAD_MONITOR_TIERS_RDN SLAPD_MONITOR_AT "=" LLOAD_MONITOR_TIERS_NAME
+#define LLOAD_MONITOR_TIERS_DN \
+ LLOAD_MONITOR_TIERS_RDN "," LLOAD_MONITOR_BALANCER_DN
+
+struct lload_monitor_ops_t {
+ struct berval rdn;
+} lload_monitor_op[] = {
+ { BER_BVC("cn=Bind") },
+ { BER_BVC("cn=Other") },
+
+ { BER_BVNULL }
+};
+
+static ObjectClass *oc_olmBalancer;
+static ObjectClass *oc_olmBalancerServer;
+static ObjectClass *oc_olmBalancerConnection;
+static ObjectClass *oc_olmBalancerOperation;
+
+static ObjectClass *oc_monitorContainer;
+static ObjectClass *oc_monitorCounterObject;
+
+static AttributeDescription *ad_olmServerURI;
+static AttributeDescription *ad_olmReceivedOps;
+static AttributeDescription *ad_olmForwardedOps;
+static AttributeDescription *ad_olmRejectedOps;
+static AttributeDescription *ad_olmCompletedOps;
+static AttributeDescription *ad_olmFailedOps;
+static AttributeDescription *ad_olmConnectionType;
+static AttributeDescription *ad_olmConnectionState;
+static AttributeDescription *ad_olmPendingOps;
+static AttributeDescription *ad_olmPendingConnections;
+static AttributeDescription *ad_olmActiveConnections;
+static AttributeDescription *ad_olmIncomingConnections;
+static AttributeDescription *ad_olmOutgoingConnections;
+
+monitor_subsys_t *lload_monitor_client_subsys;
+
+static struct {
+ char *name;
+ char *oid;
+} s_oid[] = {
+ { "olmBalancerAttributes", "olmModuleAttributes:1" },
+ { "olmBalancerObjectClasses", "olmModuleObjectClasses:1" },
+
+ { NULL }
+};
+
+static struct {
+ char *desc;
+ AttributeDescription **ad;
+} s_at[] = {
+ { "( olmBalancerAttributes:1 "
+ "NAME ( 'olmServerURI' ) "
+ "DESC 'URI of a backend server' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
+ "EQUALITY caseIgnoreMatch "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmServerURI },
+ { "( olmBalancerAttributes:2 "
+ "NAME ( 'olmReceivedOps' ) "
+ "DESC 'monitor received operations' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmReceivedOps },
+ { "( olmBalancerAttributes:3 "
+ "NAME ( 'olmForwardedOps' ) "
+ "DESC 'monitor forwarded operations' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmForwardedOps },
+ { "( olmBalancerAttributes:4 "
+ "NAME ( 'olmRejectedOps' ) "
+ "DESC 'monitor rejected operations' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmRejectedOps },
+ { "( olmBalancerAttributes:5 "
+ "NAME ( 'olmCompletedOps' ) "
+ "DESC 'monitor completed operations' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmCompletedOps },
+ { "( olmBalancerAttributes:6 "
+ "NAME ( 'olmFailedOps' ) "
+ "DESC 'monitor failed operations' "
+ "SUP monitorCounter "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmFailedOps },
+ { "( olmBalancerAttributes:7 "
+ "NAME ( 'olmPendingOps' ) "
+ "DESC 'monitor number of pending operations' "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmPendingOps },
+ { "( olmBalancerAttributes:8 "
+ "NAME ( 'olmPendingConnections' ) "
+ "DESC 'monitor number of pending connections' "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmPendingConnections },
+ { "( olmBalancerAttributes:9 "
+ "NAME ( 'olmActiveConnections' ) "
+ "DESC 'monitor number of active connections' "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmActiveConnections },
+ { "( olmBalancerAttributes:10 "
+ "NAME ( 'olmConnectionType' ) "
+ "DESC 'Connection type' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmConnectionType },
+ { "( olmBalancerAttributes:11 "
+ "NAME ( 'olmIncomingConnections' ) "
+ "DESC 'monitor number of incoming connections' "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmIncomingConnections },
+ { "( olmBalancerAttributes:12 "
+ "NAME ( 'olmOutgoingConnections' ) "
+ "DESC 'monitor number of active connections' "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ &ad_olmOutgoingConnections },
+ { "( olmBalancerAttributes:13 "
+ "NAME ( 'olmConnectionState' ) "
+ "DESC 'Connection state' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
+ "USAGE dSAOperation )",
+ &ad_olmConnectionState },
+
+ { NULL }
+};
+
+static struct {
+ char *name;
+ ObjectClass **oc;
+} s_moc[] = {
+ { "monitorContainer", &oc_monitorContainer },
+ { "monitorCounterObject", &oc_monitorCounterObject },
+
+ { NULL }
+};
+
+static struct {
+ char *desc;
+ ObjectClass **oc;
+} s_oc[] = {
+ { "( olmBalancerObjectClasses:1 "
+ "NAME ( 'olmBalancer' ) "
+ "SUP top STRUCTURAL "
+ "MAY ( "
+ "olmIncomingConnections "
+ "$ olmOutgoingConnections "
+ ") )",
+ &oc_olmBalancer },
+ { "( olmBalancerObjectClasses:2 "
+ "NAME ( 'olmBalancerServer' ) "
+ "SUP top STRUCTURAL "
+ "MAY ( "
+ "olmServerURI "
+ "$ olmActiveConnections "
+ "$ olmPendingConnections "
+ "$ olmPendingOps"
+ "$ olmReceivedOps "
+ "$ olmCompletedOps "
+ "$ olmFailedOps "
+ ") )",
+ &oc_olmBalancerServer },
+
+ { "( olmBalancerObjectClasses:3 "
+ "NAME ( 'olmBalancerOperation' ) "
+ "SUP top STRUCTURAL "
+ "MAY ( "
+ "olmReceivedOps "
+ "$ olmForwardedOps "
+ "$ olmRejectedOps "
+ "$ olmCompletedOps "
+ "$ olmFailedOps "
+ ") )",
+ &oc_olmBalancerOperation },
+ { "( olmBalancerObjectClasses:4 "
+ "NAME ( 'olmBalancerConnection' ) "
+ "SUP top STRUCTURAL "
+ "MAY ( "
+ "olmConnectionType "
+ "$ olmConnectionState "
+ "$ olmPendingOps "
+ "$ olmReceivedOps "
+ "$ olmCompletedOps "
+ "$ olmFailedOps "
+ ") )",
+ &oc_olmBalancerConnection },
+ { NULL }
+};
+
+static int
+lload_monitor_subsystem_destroy( BackendDB *be, monitor_subsys_t *ms )
+{
+ ch_free( ms->mss_dn.bv_val );
+ ch_free( ms->mss_ndn.bv_val );
+ return LDAP_SUCCESS;
+}
+
+static int
+lload_monitor_subsystem_free( BackendDB *be, monitor_subsys_t *ms )
+{
+ lload_monitor_subsystem_destroy( be, ms );
+ ch_free( ms );
+ return LDAP_SUCCESS;
+}
+
+static int
+lload_monitor_backend_destroy( BackendDB *be, monitor_subsys_t *ms )
+{
+ LloadBackend *b = ms->mss_private;
+ monitor_extra_t *mbe;
+ int rc = LDAP_SUCCESS;
+
+ ms->mss_destroy = lload_monitor_subsystem_free;
+
+ mbe = (monitor_extra_t *)be->bd_info->bi_extra;
+ if ( b->b_monitor ) {
+ assert( b->b_monitor == ms );
+ b->b_monitor = NULL;
+
+ rc = mbe->unregister_entry( &ms->mss_ndn );
+ }
+
+ return rc;
+}
+
+static int
+lload_monitor_tier_destroy( BackendDB *be, monitor_subsys_t *ms )
+{
+ LloadTier *tier = ms->mss_private;
+
+ assert( slapd_shutdown || ( tier && tier->t_monitor == ms ) );
+
+ ms->mss_destroy = lload_monitor_subsystem_free;
+
+ if ( !slapd_shutdown ) {
+ monitor_extra_t *mbe;
+
+ tier->t_monitor = NULL;
+
+ mbe = (monitor_extra_t *)be->bd_info->bi_extra;
+ return mbe->unregister_entry( &ms->mss_ndn );
+ }
+
+ return ms->mss_destroy( be, ms );
+}
+
+static void
+lload_monitor_balancer_dispose( void **priv )
+{
+ return;
+}
+
+static int
+lload_monitor_balancer_free( Entry *e, void **priv )
+{
+ return LDAP_SUCCESS;
+}
+
+static int
+lload_monitor_balancer_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ void *priv )
+{
+ Attribute *a;
+
+ a = attr_find( e->e_attrs, ad_olmIncomingConnections );
+ assert( a != NULL );
+
+ UI2BV( &a->a_vals[0], lload_stats.global_incoming );
+
+ a = attr_find( e->e_attrs, ad_olmOutgoingConnections );
+ assert( a != NULL );
+
+ UI2BV( &a->a_vals[0], lload_stats.global_outgoing );
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+lload_monitor_ops_update( Operation *op, SlapReply *rs, Entry *e, void *priv )
+{
+ Attribute *a;
+ lload_counters_t *counters = (lload_counters_t *)priv;
+
+ a = attr_find( e->e_attrs, ad_olmReceivedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], counters->lc_ops_received );
+
+ a = attr_find( e->e_attrs, ad_olmForwardedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], counters->lc_ops_forwarded );
+
+ a = attr_find( e->e_attrs, ad_olmRejectedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], counters->lc_ops_rejected );
+
+ a = attr_find( e->e_attrs, ad_olmCompletedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], counters->lc_ops_completed );
+
+ a = attr_find( e->e_attrs, ad_olmFailedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], counters->lc_ops_failed );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static void
+lload_monitor_ops_dispose( void **priv )
+{
+ return;
+}
+
+static int
+lload_monitor_ops_free( Entry *e, void **priv )
+{
+ return LDAP_SUCCESS;
+}
+
+static int
+lload_monitor_balancer_init( BackendDB *be, monitor_subsys_t *ms )
+{
+ monitor_extra_t *mbe;
+ Entry *e;
+ int rc;
+ monitor_callback_t *cb;
+ struct berval value = BER_BVC("0");
+
+ assert( be != NULL );
+
+ mbe = (monitor_extra_t *)be->bd_info->bi_extra;
+
+ dnNormalize( 0, NULL, NULL, &ms->mss_dn, &ms->mss_ndn, NULL );
+
+ e = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn, &ms->mss_rdn,
+ oc_olmBalancer, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_balancer_init: "
+ "unable to create entry \"%s,%s\"\n",
+ ms->mss_rdn.bv_val, ms->mss_ndn.bv_val );
+ return -1;
+ }
+
+ ch_free( ms->mss_ndn.bv_val );
+ ber_dupbv( &ms->mss_dn, &e->e_name );
+ ber_dupbv( &ms->mss_ndn, &e->e_nname );
+
+ cb = ch_calloc( sizeof(monitor_callback_t), 1 );
+ cb->mc_update = lload_monitor_balancer_update;
+ cb->mc_free = lload_monitor_balancer_free;
+ cb->mc_dispose = lload_monitor_balancer_dispose;
+ cb->mc_private = NULL;
+
+ attr_merge_normalize_one( e, ad_olmIncomingConnections, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmOutgoingConnections, &value, NULL );
+
+ rc = mbe->register_entry( e, cb, ms, 0 );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_balancer_init: "
+ "unable to register entry \"%s\" for monitoring\n",
+ e->e_name.bv_val );
+ goto done;
+ }
+
+done:
+ entry_free( e );
+
+ return rc;
+}
+
+static int
+lload_monitor_ops_init( BackendDB *be, monitor_subsys_t *ms )
+{
+ monitor_extra_t *mbe;
+ Entry *e, *parent;
+ int rc;
+ int i;
+ struct berval value = BER_BVC("0");
+
+ assert( be != NULL );
+
+ mbe = (monitor_extra_t *)be->bd_info->bi_extra;
+
+ dnNormalize( 0, NULL, NULL, &ms->mss_dn, &ms->mss_ndn, NULL );
+ ms->mss_destroy = lload_monitor_subsystem_destroy;
+
+ parent = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn, &ms->mss_rdn,
+ oc_monitorContainer, NULL, NULL );
+ if ( parent == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_ops_init: "
+ "unable to create entry \"%s,%s\"\n",
+ ms->mss_rdn.bv_val, ms->mss_ndn.bv_val );
+ return -1;
+ }
+ ch_free( ms->mss_ndn.bv_val );
+ ber_dupbv( &ms->mss_dn, &parent->e_name );
+ ber_dupbv( &ms->mss_ndn, &parent->e_nname );
+
+ rc = mbe->register_entry( parent, NULL, ms, MONITOR_F_PERSISTENT_CH );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_ops_init: "
+ "unable to register entry \"%s\" for monitoring\n",
+ parent->e_name.bv_val );
+ goto done;
+ }
+
+ for ( i = 0; lload_monitor_op[i].rdn.bv_val != NULL; i++ ) {
+ monitor_callback_t *cb;
+ e = mbe->entry_stub( &parent->e_name, &parent->e_nname,
+ &lload_monitor_op[i].rdn, oc_olmBalancerOperation, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_ops_init: "
+ "unable to create entry \"%s,%s\"\n",
+ lload_monitor_op[i].rdn.bv_val, parent->e_nname.bv_val );
+ return -1;
+ }
+
+ /* attr_merge_normalize_one( e, ad_olmDbOperations, &value, NULL ); */
+
+ /*
+ * We cannot share a single callback between entries.
+ *
+ * monitor_cache_destroy() tries to free all callbacks and it's called
+ * before mss_destroy() so we have no chance of handling it ourselves
+ */
+ cb = ch_calloc( sizeof(monitor_callback_t), 1 );
+ cb->mc_update = lload_monitor_ops_update;
+ cb->mc_free = lload_monitor_ops_free;
+ cb->mc_dispose = lload_monitor_ops_dispose;
+ cb->mc_private = &lload_stats.counters[i];
+
+ attr_merge_normalize_one( e, ad_olmReceivedOps, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmForwardedOps, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmRejectedOps, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmCompletedOps, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmFailedOps, &value, NULL );
+
+ rc = mbe->register_entry( e, cb, ms, 0 );
+
+ entry_free( e );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_ops_init: "
+ "unable to register entry \"%s\" for monitoring\n",
+ e->e_name.bv_val );
+ ch_free( cb );
+ break;
+ }
+ }
+
+done:
+ entry_free( parent );
+ return rc;
+}
+
+static void *
+lload_monitor_release_conn( void *ctx, void *arg )
+{
+ LloadConnection *c = arg;
+ epoch_t epoch = epoch_join();
+
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ epoch_leave( epoch );
+ return NULL;
+}
+
+static int
+lload_monitor_conn_modify( Operation *op, SlapReply *rs, Entry *e, void *priv )
+{
+ Modifications *m;
+ LloadConnection *c = priv;
+ int rc = SLAP_CB_CONTINUE;
+ epoch_t epoch;
+
+ if ( !acquire_ref( &c->c_refcnt ) ) {
+ /* Shutting down, pretend it's already happened */
+ return LDAP_NO_SUCH_OBJECT;
+ }
+ epoch = epoch_join();
+
+ for ( m = op->orm_modlist; m; m = m->sml_next ) {
+ struct berval closing = BER_BVC("closing");
+ int gentle = 1;
+
+ if ( m->sml_flags & SLAP_MOD_INTERNAL ) continue;
+
+ if ( m->sml_desc != ad_olmConnectionState ||
+ m->sml_op != LDAP_MOD_REPLACE || m->sml_numvals != 1 ||
+ ber_bvcmp( &m->sml_nvalues[0], &closing ) ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+ if ( lload_connection_close( c, &gentle ) ) {
+ rc = LDAP_OTHER;
+ goto done;
+ }
+ }
+
+done:
+ epoch_leave( epoch );
+ /*
+ * The connection might have been ready to disappear in epoch_leave(), that
+ * involves deleting this monitor entry. Make sure that doesn't happen
+ * punting the decref into a separate task that's not holding any locks and
+ * finishes after we did.
+ *
+ * FIXME: It would probably be cleaner to defer the entry deletion into a
+ * separate task instead but the entry holds a pointer to this connection
+ * that might not be safe to manipulate.
+ */
+ ldap_pvt_thread_pool_submit(
+ &connection_pool, lload_monitor_release_conn, c );
+ return rc;
+}
+
+/*
+ * Monitor cache is locked, the connection cannot be unlinked and freed under us.
+ * That also means we need to unlock and finish as soon as possible.
+ */
+static int
+lload_monitor_conn_update( Operation *op, SlapReply *rs, Entry *e, void *priv )
+{
+ Attribute *a;
+ LloadConnection *c = priv;
+ struct berval bv_type, bv_state;
+ ldap_pvt_mp_t active, pending, received, completed, failed;
+
+ CONNECTION_LOCK(c);
+
+ pending = (ldap_pvt_mp_t)c->c_n_ops_executing;
+ received = c->c_counters.lc_ops_received;
+ completed = c->c_counters.lc_ops_completed;
+ failed = c->c_counters.lc_ops_failed;
+
+ switch ( c->c_type ) {
+ case LLOAD_C_OPEN: {
+ struct berval bv = BER_BVC("regular");
+ bv_type = bv;
+ } break;
+ case LLOAD_C_PREPARING: {
+ struct berval bv = BER_BVC("preparing");
+ bv_type = bv;
+ } break;
+ case LLOAD_C_BIND: {
+ struct berval bv = BER_BVC("bind");
+ bv_type = bv;
+ } break;
+ case LLOAD_C_PRIVILEGED: {
+ struct berval bv = BER_BVC("privileged");
+ bv_type = bv;
+ } break;
+ default: {
+ struct berval bv = BER_BVC("unknown");
+ bv_type = bv;
+ } break;
+ }
+
+ switch ( c->c_state ) {
+ case LLOAD_C_INVALID: {
+ /* *_destroy removes the entry from list before setting c_state to
+ * INVALID */
+ assert(0);
+ } break;
+ case LLOAD_C_READY: {
+ struct berval bv = BER_BVC("ready");
+ bv_state = bv;
+ } break;
+ case LLOAD_C_CLOSING: {
+ struct berval bv = BER_BVC("closing");
+ bv_state = bv;
+ } break;
+ case LLOAD_C_ACTIVE: {
+ struct berval bv = BER_BVC("active");
+ bv_state = bv;
+ } break;
+ case LLOAD_C_BINDING: {
+ struct berval bv = BER_BVC("binding");
+ bv_state = bv;
+ } break;
+ case LLOAD_C_DYING: {
+ /* I guess we got it before it was unlinked? */
+ struct berval bv = BER_BVC("dying");
+ bv_state = bv;
+ } break;
+ default: {
+ struct berval bv = BER_BVC("unknown");
+ bv_state = bv;
+ } break;
+ }
+
+ CONNECTION_UNLOCK(c);
+
+ a = attr_find( e->e_attrs, ad_olmConnectionType );
+ assert( a != NULL );
+ if ( !(a->a_flags & SLAP_ATTR_DONT_FREE_DATA) ) {
+ ber_memfree( a->a_vals[0].bv_val );
+ a->a_flags |= SLAP_ATTR_DONT_FREE_DATA;
+ }
+ a->a_vals[0] = bv_type;
+
+ a = attr_find( e->e_attrs, ad_olmConnectionState );
+ assert( a != NULL );
+ if ( !(a->a_flags & SLAP_ATTR_DONT_FREE_DATA) ) {
+ ber_memfree( a->a_vals[0].bv_val );
+ a->a_flags |= SLAP_ATTR_DONT_FREE_DATA;
+ }
+ a->a_vals[0] = bv_state;
+
+ a = attr_find( e->e_attrs, ad_olmPendingOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], pending );
+
+ a = attr_find( e->e_attrs, ad_olmReceivedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], received );
+
+ a = attr_find( e->e_attrs, ad_olmCompletedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], completed );
+
+ a = attr_find( e->e_attrs, ad_olmFailedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], failed );
+
+ return SLAP_CB_CONTINUE;
+}
+
+int
+lload_monitor_conn_unlink( LloadConnection *c )
+{
+ BackendInfo *mi = backend_info( "monitor" );
+ monitor_extra_t *mbe = mi->bi_extra;
+
+ assert( mbe && mbe->is_configured() );
+
+ CONNECTION_ASSERT_LOCKED(c);
+ assert( !BER_BVISNULL( &c->c_monitor_dn ) );
+
+ /*
+ * Avoid a lock inversion with threads holding monitor cache locks in turn
+ * waiting on CONNECTION_LOCK(c)
+ */
+ CONNECTION_UNLOCK(c);
+ mbe->unregister_entry( &c->c_monitor_dn );
+ CONNECTION_LOCK(c);
+
+ ber_memfree( c->c_monitor_dn.bv_val );
+ BER_BVZERO( &c->c_monitor_dn );
+
+ return 0;
+}
+
+int
+lload_monitor_conn_entry_create( LloadConnection *c, monitor_subsys_t *ms )
+{
+ char buf[SLAP_TEXT_BUFLEN];
+ char timebuf[LDAP_LUTIL_GENTIME_BUFSIZE];
+ struct tm tm;
+ struct berval bv_rdn, bv_timestamp, zero = BER_BVC("0"),
+ value = BER_BVC("unknown");
+ monitor_entry_t *mp;
+ monitor_callback_t *cb;
+ Entry *e;
+ Attribute *a;
+ BackendInfo *mi = backend_info( "monitor" );
+ monitor_extra_t *mbe = mi->bi_extra;
+
+ assert( mbe && mbe->is_configured() );
+
+ CONNECTION_ASSERT_LOCKED(c);
+ assert( BER_BVISNULL( &c->c_monitor_dn ) );
+
+ bv_rdn.bv_val = buf;
+ bv_rdn.bv_len = snprintf(
+ bv_rdn.bv_val, SLAP_TEXT_BUFLEN, "cn=Connection %lu", c->c_connid );
+
+ ldap_pvt_gmtime( &c->c_activitytime, &tm );
+ bv_timestamp.bv_len = lutil_gentime( timebuf, sizeof(timebuf), &tm );
+ bv_timestamp.bv_val = timebuf;
+
+ e = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn, &bv_rdn,
+ oc_olmBalancerConnection, &bv_timestamp, &bv_timestamp );
+
+ cb = ch_calloc( sizeof(monitor_callback_t), 1 );
+ cb->mc_update = lload_monitor_conn_update;
+ cb->mc_modify = lload_monitor_conn_modify;
+ cb->mc_private = c;
+
+ attr_merge_one( e, ad_olmConnectionType, &value, NULL );
+ attr_merge_one( e, ad_olmConnectionState, &value, NULL );
+ attr_merge_one( e, ad_olmPendingOps, &zero, NULL );
+ attr_merge_one( e, ad_olmReceivedOps, &zero, NULL );
+ attr_merge_one( e, ad_olmCompletedOps, &zero, NULL );
+ attr_merge_one( e, ad_olmFailedOps, &zero, NULL );
+
+ if ( mbe->register_entry( e, cb, NULL, 0 ) ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_conn_entry_create: "
+ "failed to register monitor entry for connid=%lu\n",
+ c->c_connid );
+
+ ch_free( cb );
+ entry_free( e );
+ return -1;
+ }
+
+ ber_dupbv( &c->c_monitor_dn, &e->e_nname );
+ entry_free( e );
+
+ return 0;
+}
+
+static int
+lload_monitor_incoming_conn_init( BackendDB *be, monitor_subsys_t *ms )
+{
+ monitor_extra_t *mbe;
+ Entry *e;
+ int rc;
+
+ assert( be != NULL );
+ mbe = (monitor_extra_t *)be->bd_info->bi_extra;
+
+ ms->mss_destroy = lload_monitor_subsystem_destroy;
+
+ dnNormalize( 0, NULL, NULL, &ms->mss_dn, &ms->mss_ndn, NULL );
+
+ e = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn, &ms->mss_rdn,
+ oc_monitorContainer, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_incoming_conn_init: "
+ "unable to create entry \"%s,%s\"\n",
+ ms->mss_rdn.bv_val, ms->mss_ndn.bv_val );
+ return -1;
+ }
+ ch_free( ms->mss_ndn.bv_val );
+ ber_dupbv( &ms->mss_dn, &e->e_name );
+ ber_dupbv( &ms->mss_ndn, &e->e_nname );
+
+ rc = mbe->register_entry( e, NULL, ms, 0 );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_incoming_conn_init: "
+ "unable to register entry \"%s\" for monitoring\n",
+ e->e_name.bv_val );
+ goto done;
+ }
+
+ lload_monitor_client_subsys = ms;
+
+done:
+ entry_free( e );
+
+ return rc;
+}
+
+static int
+lload_monitor_server_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ void *priv )
+{
+ Attribute *a;
+ LloadBackend *b = priv;
+ LloadConnection *c;
+ LloadPendingConnection *pc;
+ ldap_pvt_mp_t active = 0, pending = 0, received = 0, completed = 0,
+ failed = 0;
+ int i;
+
+ checked_lock( &b->b_mutex );
+ active = b->b_active + b->b_bindavail;
+
+ LDAP_CIRCLEQ_FOREACH ( c, &b->b_preparing, c_next ) {
+ pending++;
+ }
+
+ LDAP_LIST_FOREACH( pc, &b->b_connecting, next ) {
+ pending++;
+ }
+
+ for ( i = 0; i < LLOAD_STATS_OPS_LAST; i++ ) {
+ received += b->b_counters[i].lc_ops_received;
+ completed += b->b_counters[i].lc_ops_completed;
+ failed += b->b_counters[i].lc_ops_failed;
+ }
+
+ a = attr_find( e->e_attrs, ad_olmPendingOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], (long long unsigned int)b->b_n_ops_executing );
+
+ checked_unlock( &b->b_mutex );
+
+ /* Right now, there is no way to retrieve the entry from monitor's
+ * cache to replace URI at the moment it is modified */
+ a = attr_find( e->e_attrs, ad_olmServerURI );
+ assert( a != NULL );
+ ber_bvreplace( &a->a_vals[0], &b->b_uri );
+
+ a = attr_find( e->e_attrs, ad_olmActiveConnections );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], active );
+
+ a = attr_find( e->e_attrs, ad_olmPendingConnections );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], pending );
+
+ a = attr_find( e->e_attrs, ad_olmReceivedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], received );
+
+ a = attr_find( e->e_attrs, ad_olmCompletedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], completed );
+
+ a = attr_find( e->e_attrs, ad_olmFailedOps );
+ assert( a != NULL );
+ UI2BV( &a->a_vals[0], failed );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+lload_monitor_backend_open( BackendDB *be, monitor_subsys_t *ms )
+{
+ Entry *e;
+ struct berval value = BER_BVC("0");
+ monitor_extra_t *mbe;
+ monitor_callback_t *cb;
+ LloadBackend *b = ms->mss_private;
+ LloadTier *tier = b->b_tier;
+ int rc;
+
+ assert( be != NULL );
+ mbe = (monitor_extra_t *)be->bd_info->bi_extra;
+
+ e = mbe->entry_stub( &tier->t_monitor->mss_dn, &tier->t_monitor->mss_ndn,
+ &ms->mss_rdn, oc_olmBalancerServer, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_backend_open: "
+ "unable to create entry \"%s,%s\"\n",
+ ms->mss_rdn.bv_val, tier->t_monitor->mss_dn.bv_val );
+ return -1;
+ }
+
+ ber_dupbv( &ms->mss_dn, &e->e_name );
+ ber_dupbv( &ms->mss_ndn, &e->e_nname );
+
+ cb = ch_calloc( sizeof(monitor_callback_t), 1 );
+ cb->mc_update = lload_monitor_server_update;
+ cb->mc_free = NULL;
+ cb->mc_dispose = NULL;
+ cb->mc_private = b;
+
+ attr_merge_normalize_one( e, ad_olmServerURI, &b->b_uri, NULL );
+ attr_merge_normalize_one( e, ad_olmActiveConnections, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmPendingConnections, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmPendingOps, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmReceivedOps, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmCompletedOps, &value, NULL );
+ attr_merge_normalize_one( e, ad_olmFailedOps, &value, NULL );
+
+ rc = mbe->register_entry( e, cb, ms, 0 );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_backend_open: "
+ "unable to register entry \"%s\" for monitoring\n",
+ e->e_name.bv_val );
+ goto done;
+ }
+
+ ms->mss_destroy = lload_monitor_backend_destroy;
+
+done:
+ entry_free( e );
+ return rc;
+}
+
+int
+lload_monitor_backend_init(
+ BackendInfo *bi,
+ monitor_subsys_t *ms,
+ LloadBackend *b )
+{
+ monitor_extra_t *mbe = bi->bi_extra;
+ monitor_subsys_t *bk_mss;
+
+ /* FIXME: With back-monitor as it works now, there is no way to know when
+ * this can be safely freed so we leak it on shutdown */
+ bk_mss = ch_calloc( 1, sizeof(monitor_subsys_t) );
+ bk_mss->mss_rdn.bv_len = sizeof("cn=") + b->b_name.bv_len;
+ bk_mss->mss_rdn.bv_val = ch_malloc( bk_mss->mss_rdn.bv_len );
+ bk_mss->mss_rdn.bv_len = snprintf( bk_mss->mss_rdn.bv_val,
+ bk_mss->mss_rdn.bv_len, "cn=%s", b->b_name.bv_val );
+
+ bk_mss->mss_name = b->b_name.bv_val;
+ bk_mss->mss_flags = MONITOR_F_NONE;
+ bk_mss->mss_open = lload_monitor_backend_open;
+ bk_mss->mss_destroy = lload_monitor_subsystem_destroy;
+ bk_mss->mss_update = NULL;
+ bk_mss->mss_private = b;
+
+ if ( mbe->register_subsys_late( bk_mss ) ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_backend_init: "
+ "failed to register backend %s\n",
+ bk_mss->mss_name );
+ ch_free( bk_mss );
+ return -1;
+ }
+
+ b->b_monitor = bk_mss;
+ return LDAP_SUCCESS;
+}
+
+static int
+lload_monitor_tier_open( BackendDB *be, monitor_subsys_t *ms )
+{
+ Entry *e;
+ monitor_extra_t *mbe;
+ LloadTier *tier = ms->mss_private;
+ int rc;
+
+ assert( be != NULL );
+ mbe = (monitor_extra_t *)be->bd_info->bi_extra;
+
+ dnNormalize( 0, NULL, NULL, &ms->mss_dn, &ms->mss_ndn, NULL );
+ e = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn, &ms->mss_rdn,
+ oc_monitorContainer, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_tier_open: "
+ "unable to create entry \"%s,%s\"\n",
+ ms->mss_rdn.bv_val, ms->mss_ndn.bv_val );
+ return -1;
+ }
+
+ ch_free( ms->mss_ndn.bv_val );
+ ber_dupbv( &ms->mss_dn, &e->e_name );
+ ber_dupbv( &ms->mss_ndn, &e->e_nname );
+
+ rc = mbe->register_entry( e, NULL, ms, MONITOR_F_PERSISTENT_CH );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_tier_open: "
+ "unable to register entry \"%s\" for monitoring\n",
+ e->e_name.bv_val );
+ goto done;
+ }
+
+ tier->t_monitor = ms;
+ ms->mss_destroy = lload_monitor_tier_destroy;
+
+done:
+ entry_free( e );
+ return rc;
+}
+
+int
+lload_monitor_tier_init( BackendInfo *bi, LloadTier *tier )
+{
+ monitor_extra_t *mbe;
+ monitor_subsys_t *mss;
+ LloadBackend *b;
+
+ mbe = (monitor_extra_t *)bi->bi_extra;
+
+ mss = ch_calloc( 1, sizeof(monitor_subsys_t) );
+ mss->mss_rdn.bv_len = sizeof("cn=") + tier->t_name.bv_len;
+ mss->mss_rdn.bv_val = ch_malloc( mss->mss_rdn.bv_len );
+ mss->mss_rdn.bv_len = snprintf( mss->mss_rdn.bv_val, mss->mss_rdn.bv_len,
+ "cn=%s", tier->t_name.bv_val );
+
+ ber_str2bv( LLOAD_MONITOR_TIERS_DN, 0, 0, &mss->mss_dn );
+ mss->mss_name = tier->t_name.bv_val;
+ mss->mss_open = lload_monitor_tier_open;
+ mss->mss_destroy = lload_monitor_subsystem_destroy;
+ mss->mss_update = NULL;
+ mss->mss_private = tier;
+
+ if ( mbe->register_subsys_late( mss ) ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_tier_init: "
+ "failed to register backend %s\n",
+ mss->mss_name );
+ return -1;
+ }
+
+ LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
+ if ( lload_monitor_backend_init( bi, mss, b ) ) {
+ return -1;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+lload_monitor_tiers_init( BackendDB *be, monitor_subsys_t *ms )
+{
+ monitor_extra_t *mbe;
+ LloadTier *tier;
+ Entry *e;
+ int rc;
+
+ assert( be != NULL );
+ mbe = (monitor_extra_t *)be->bd_info->bi_extra;
+
+ dnNormalize( 0, NULL, NULL, &ms->mss_dn, &ms->mss_ndn, NULL );
+
+ e = mbe->entry_stub( &ms->mss_dn, &ms->mss_ndn, &ms->mss_rdn,
+ oc_monitorContainer, NULL, NULL );
+ if ( e == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_tiers_init: "
+ "unable to create entry \"%s,%s\"\n",
+ ms->mss_rdn.bv_val, ms->mss_ndn.bv_val );
+ return -1;
+ }
+ ch_free( ms->mss_ndn.bv_val );
+ ber_dupbv( &ms->mss_dn, &e->e_name );
+ ber_dupbv( &ms->mss_ndn, &e->e_nname );
+
+ rc = mbe->register_entry( e, NULL, ms, MONITOR_F_PERSISTENT_CH );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_tiers_init: "
+ "unable to register entry \"%s\" for monitoring\n",
+ e->e_name.bv_val );
+ goto done;
+ }
+
+ LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) {
+ if ( (rc = lload_monitor_tier_init( be->bd_info, tier )) ) {
+ break;
+ }
+ }
+done:
+ entry_free( e );
+
+ return rc;
+}
+
+static int
+lload_monitor_incoming_count( LloadConnection *conn, void *argv )
+{
+ lload_global_stats_t *tmp_stats = argv;
+ tmp_stats->global_incoming++;
+ return 0;
+}
+
+/*
+ * Update all global statistics other than rejected and received,
+ * which are updated in real time
+ */
+void *
+lload_monitor_update_global_stats( void *ctx, void *arg )
+{
+ struct re_s *rtask = arg;
+ lload_global_stats_t tmp_stats = {};
+ LloadTier *tier;
+ int i;
+
+ Debug( LDAP_DEBUG_TRACE, "lload_monitor_update_global_stats: "
+ "updating stats\n" );
+
+ /* count incoming connections */
+ checked_lock( &clients_mutex );
+ connections_walk( &clients_mutex, &clients, lload_monitor_incoming_count,
+ &tmp_stats );
+ checked_unlock( &clients_mutex );
+
+ LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) {
+ LloadBackend *b;
+
+ LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
+ checked_lock( &b->b_mutex );
+ tmp_stats.global_outgoing += b->b_active + b->b_bindavail;
+
+ /* merge completed and failed stats */
+ for ( i = 0; i < LLOAD_STATS_OPS_LAST; i++ ) {
+ tmp_stats.counters[i].lc_ops_completed +=
+ b->b_counters[i].lc_ops_completed;
+ tmp_stats.counters[i].lc_ops_failed +=
+ b->b_counters[i].lc_ops_failed;
+ }
+ checked_unlock( &b->b_mutex );
+ }
+ }
+
+ /* update lload_stats */
+ lload_stats.global_outgoing = tmp_stats.global_outgoing;
+ lload_stats.global_incoming = tmp_stats.global_incoming;
+ for ( i = 0; i < LLOAD_STATS_OPS_LAST; i++ ) {
+ lload_stats.counters[i].lc_ops_completed =
+ tmp_stats.counters[i].lc_ops_completed;
+ lload_stats.counters[i].lc_ops_failed =
+ tmp_stats.counters[i].lc_ops_failed;
+ }
+
+ /* reschedule */
+ checked_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+ checked_unlock( &slapd_rq.rq_mutex );
+ return NULL;
+}
+
+static char *lload_subsys_rdn[] = {
+ LLOAD_MONITOR_BALANCER_RDN,
+ LLOAD_MONITOR_INCOMING_RDN,
+ LLOAD_MONITOR_OPERATIONS_RDN,
+ LLOAD_MONITOR_TIERS_RDN,
+ NULL
+};
+
+static struct monitor_subsys_t balancer_subsys[] = {
+ {
+ LLOAD_MONITOR_BALANCER_NAME,
+ BER_BVNULL,
+ BER_BVC(SLAPD_MONITOR_BACKEND_DN),
+ BER_BVNULL,
+ { BER_BVC("Load Balancer information"),
+ BER_BVNULL },
+ MONITOR_F_PERSISTENT_CH,
+ lload_monitor_balancer_init,
+ lload_monitor_subsystem_destroy, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL /* modify */
+ },
+ {
+ LLOAD_MONITOR_INCOMING_NAME,
+ BER_BVNULL,
+ BER_BVC(LLOAD_MONITOR_BALANCER_DN),
+ BER_BVNULL,
+ { BER_BVC("Load Balancer incoming connections"),
+ BER_BVNULL },
+ MONITOR_F_NONE,
+ lload_monitor_incoming_conn_init,
+ lload_monitor_subsystem_destroy, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL /* modify */
+ },
+ {
+ LLOAD_MONITOR_OPERATIONS_NAME,
+ BER_BVNULL,
+ BER_BVC(LLOAD_MONITOR_BALANCER_DN),
+ BER_BVNULL,
+ { BER_BVC("Load Balancer global operation statistics"),
+ BER_BVNULL },
+ MONITOR_F_PERSISTENT_CH,
+ lload_monitor_ops_init,
+ lload_monitor_subsystem_destroy, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL /* modify */
+ },
+ {
+ LLOAD_MONITOR_TIERS_NAME,
+ BER_BVNULL,
+ BER_BVC(LLOAD_MONITOR_BALANCER_DN),
+ BER_BVNULL,
+ { BER_BVC("Load Balancer Backends information"),
+ BER_BVNULL },
+ MONITOR_F_PERSISTENT_CH,
+ lload_monitor_tiers_init,
+ lload_monitor_subsystem_destroy, /* destroy */
+ NULL, /* update */
+ NULL, /* create */
+ NULL /* modify */
+ },
+ { NULL }
+};
+
+int
+lload_monitor_open( void )
+{
+ static int lload_monitor_initialized_failure = 1;
+ static int lload_monitor_initialized = 0;
+ BackendInfo *mi;
+ monitor_extra_t *mbe;
+ monitor_subsys_t *mss;
+ ConfigArgs c;
+ char *argv[3], **rdn;
+ int i, rc;
+
+ /* check if monitor is configured and usable */
+ mi = backend_info( "monitor" );
+ if ( !mi || !mi->bi_extra ) {
+ Debug( LDAP_DEBUG_CONFIG, "lload_monitor_open: "
+ "monitor backend not available, monitoring disabled\n" );
+ return 0;
+ }
+ mbe = mi->bi_extra;
+
+ /* don't bother if monitor is not configured */
+ if ( !mbe->is_configured() ) {
+ static int warning = 0;
+
+ if ( warning++ == 0 ) {
+ Debug( LDAP_DEBUG_CONFIG, "lload_monitor_open: "
+ "monitoring disabled; "
+ "configure monitor database to enable\n" );
+ }
+
+ return 0;
+ }
+
+ if ( lload_monitor_initialized++ ) {
+ return lload_monitor_initialized_failure;
+ }
+
+ argv[0] = "lload monitor";
+ c.argv = argv;
+ c.argc = 3;
+ c.fname = argv[0];
+ for ( i = 0; s_oid[i].name; i++ ) {
+ argv[1] = s_oid[i].name;
+ argv[2] = s_oid[i].oid;
+
+ if ( parse_oidm( &c, 0, NULL ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_open: "
+ "unable to add objectIdentifier \"%s=%s\"\n",
+ s_oid[i].name, s_oid[i].oid );
+ return 2;
+ }
+ }
+
+ for ( i = 0; s_at[i].desc != NULL; i++ ) {
+ rc = register_at( s_at[i].desc, s_at[i].ad, 1 );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_open: "
+ "register_at failed for attributeType (%s)\n",
+ s_at[i].desc );
+ return 3;
+
+ } else {
+ (*s_at[i].ad)->ad_type->sat_flags |= SLAP_AT_HIDE;
+ }
+ }
+
+ for ( i = 0; s_oc[i].desc != NULL; i++ ) {
+ rc = register_oc( s_oc[i].desc, s_oc[i].oc, 1 );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_open: "
+ "register_oc failed for objectClass (%s)\n",
+ s_oc[i].desc );
+ return 4;
+
+ } else {
+ (*s_oc[i].oc)->soc_flags |= SLAP_OC_HIDE;
+ }
+ }
+
+ for ( i = 0; s_moc[i].name != NULL; i++ ) {
+ *s_moc[i].oc = oc_find( s_moc[i].name );
+ if ( !*s_moc[i].oc ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_open: "
+ "failed to find objectClass (%s)\n",
+ s_moc[i].name );
+ return 5;
+ }
+ }
+
+ /* register the subsystems - Servers are registered in backends_init */
+ for ( mss = balancer_subsys, rdn = lload_subsys_rdn; mss->mss_name;
+ mss++, rdn++ ) {
+ ber_str2bv( *rdn, 0, 1, &mss->mss_rdn );
+ if ( mbe->register_subsys_late( mss ) ) {
+ Debug( LDAP_DEBUG_ANY, "lload_monitor_open: "
+ "failed to register %s subsystem\n",
+ mss->mss_name );
+ return -1;
+ }
+ }
+
+ checked_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_insert( &slapd_rq, 1, lload_monitor_update_global_stats,
+ NULL, "lload_monitor_update_global_stats", "lloadd" );
+ checked_unlock( &slapd_rq.rq_mutex );
+
+ return (lload_monitor_initialized_failure = LDAP_SUCCESS);
+}
diff --git a/servers/lloadd/operation.c b/servers/lloadd/operation.c
new file mode 100644
index 0000000..73f91a1
--- /dev/null
+++ b/servers/lloadd/operation.c
@@ -0,0 +1,725 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include "lutil.h"
+#include "lload.h"
+
+ldap_pvt_thread_mutex_t lload_pin_mutex;
+unsigned long lload_next_pin = 1;
+
+TAvlnode *lload_control_actions = NULL;
+TAvlnode *lload_exop_actions = NULL;
+enum op_restriction lload_default_exop_action = LLOAD_OP_NOT_RESTRICTED;
+
+ber_tag_t
+slap_req2res( ber_tag_t tag )
+{
+ switch ( tag ) {
+ case LDAP_REQ_ADD:
+ case LDAP_REQ_BIND:
+ case LDAP_REQ_COMPARE:
+ case LDAP_REQ_EXTENDED:
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_MODRDN:
+ tag++;
+ break;
+
+ case LDAP_REQ_DELETE:
+ tag = LDAP_RES_DELETE;
+ break;
+
+ case LDAP_REQ_ABANDON:
+ case LDAP_REQ_UNBIND:
+ tag = LBER_SEQUENCE;
+ break;
+
+ case LDAP_REQ_SEARCH:
+ tag = LDAP_RES_SEARCH_RESULT;
+ break;
+
+ default:
+ tag = LBER_SEQUENCE;
+ }
+
+ return tag;
+}
+
+const char *
+lload_msgtype2str( ber_tag_t tag )
+{
+ switch ( tag ) {
+ case LDAP_REQ_ABANDON: return "abandon request";
+ case LDAP_REQ_ADD: return "add request";
+ case LDAP_REQ_BIND: return "bind request";
+ case LDAP_REQ_COMPARE: return "compare request";
+ case LDAP_REQ_DELETE: return "delete request";
+ case LDAP_REQ_EXTENDED: return "extended request";
+ case LDAP_REQ_MODIFY: return "modify request";
+ case LDAP_REQ_RENAME: return "rename request";
+ case LDAP_REQ_SEARCH: return "search request";
+ case LDAP_REQ_UNBIND: return "unbind request";
+
+ case LDAP_RES_ADD: return "add result";
+ case LDAP_RES_BIND: return "bind result";
+ case LDAP_RES_COMPARE: return "compare result";
+ case LDAP_RES_DELETE: return "delete result";
+ case LDAP_RES_EXTENDED: return "extended result";
+ case LDAP_RES_INTERMEDIATE: return "intermediate response";
+ case LDAP_RES_MODIFY: return "modify result";
+ case LDAP_RES_RENAME: return "rename result";
+ case LDAP_RES_SEARCH_ENTRY: return "search-entry response";
+ case LDAP_RES_SEARCH_REFERENCE: return "search-reference response";
+ case LDAP_RES_SEARCH_RESULT: return "search result";
+ }
+ return "unknown message";
+}
+
+int
+lload_restriction_cmp( const void *left, const void *right )
+{
+ const struct restriction_entry *l = left, *r = right;
+ return ber_bvcmp( &l->oid, &r->oid );
+}
+
+int
+operation_client_cmp( const void *left, const void *right )
+{
+ const LloadOperation *l = left, *r = right;
+
+ assert( l->o_client_connid == r->o_client_connid );
+ if ( l->o_client_msgid || r->o_client_msgid ) {
+ return ( l->o_client_msgid < r->o_client_msgid ) ?
+ -1 :
+ ( l->o_client_msgid > r->o_client_msgid );
+ } else {
+ return ( l->o_pin_id < r->o_pin_id ) ? -1 :
+ ( l->o_pin_id > r->o_pin_id );
+ }
+}
+
+int
+operation_upstream_cmp( const void *left, const void *right )
+{
+ const LloadOperation *l = left, *r = right;
+
+ assert( l->o_upstream_connid == r->o_upstream_connid );
+ if ( l->o_upstream_msgid || r->o_upstream_msgid ) {
+ return ( l->o_upstream_msgid < r->o_upstream_msgid ) ?
+ -1 :
+ ( l->o_upstream_msgid > r->o_upstream_msgid );
+ } else {
+ return ( l->o_pin_id < r->o_pin_id ) ? -1 :
+ ( l->o_pin_id > r->o_pin_id );
+ }
+}
+
+/*
+ * Entered holding c_mutex for now.
+ */
+LloadOperation *
+operation_init( LloadConnection *c, BerElement *ber )
+{
+ LloadOperation *op;
+ ber_tag_t tag;
+ ber_len_t len;
+ int rc;
+
+ if ( !IS_ALIVE( c, c_live ) ) {
+ return NULL;
+ }
+
+ op = ch_calloc( 1, sizeof(LloadOperation) );
+ op->o_client = c;
+ op->o_client_connid = c->c_connid;
+ op->o_ber = ber;
+ gettimeofday( &op->o_start, NULL );
+
+ ldap_pvt_thread_mutex_init( &op->o_link_mutex );
+
+ op->o_refcnt = 1;
+
+ tag = ber_get_int( ber, &op->o_client_msgid );
+ if ( tag != LDAP_TAG_MSGID ) {
+ goto fail;
+ }
+
+ if ( !op->o_client_msgid ) {
+ goto fail;
+ }
+
+ CONNECTION_ASSERT_LOCKED(c);
+ rc = ldap_tavl_insert( &c->c_ops, op, operation_client_cmp, ldap_avl_dup_error );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_PACKETS, "operation_init: "
+ "several operations with same msgid=%d in-flight "
+ "from client connid=%lu\n",
+ op->o_client_msgid, op->o_client_connid );
+ goto fail;
+ }
+
+ tag = op->o_tag = ber_skip_element( ber, &op->o_request );
+ switch ( tag ) {
+ case LBER_ERROR:
+ rc = -1;
+ break;
+ }
+ if ( rc ) {
+ ldap_tavl_delete( &c->c_ops, op, operation_client_cmp );
+ goto fail;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LDAP_TAG_CONTROLS ) {
+ ber_skip_element( ber, &op->o_ctrls );
+ }
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_BIND:
+ lload_stats.counters[LLOAD_STATS_OPS_BIND].lc_ops_received++;
+ break;
+ default:
+ lload_stats.counters[LLOAD_STATS_OPS_OTHER].lc_ops_received++;
+ break;
+ }
+
+ Debug( LDAP_DEBUG_STATS, "operation_init: "
+ "received a new operation, %s with msgid=%d for client "
+ "connid=%lu\n",
+ lload_msgtype2str( op->o_tag ), op->o_client_msgid,
+ op->o_client_connid );
+
+ c->c_n_ops_executing++;
+ return op;
+
+fail:
+ ch_free( op );
+ return NULL;
+}
+
+void
+operation_destroy( LloadOperation *op )
+{
+ Debug( LDAP_DEBUG_TRACE, "operation_destroy: "
+ "op=%p destroyed operation from client connid=%lu, "
+ "client msgid=%d\n",
+ op, op->o_client_connid, op->o_client_msgid );
+
+ assert( op->o_refcnt == 0 );
+ assert( op->o_client == NULL );
+ assert( op->o_upstream == NULL );
+
+ ber_free( op->o_ber, 1 );
+ ldap_pvt_thread_mutex_destroy( &op->o_link_mutex );
+ ch_free( op );
+}
+
+int
+operation_unlink( LloadOperation *op )
+{
+ LloadConnection *client, *upstream;
+ uintptr_t prev_refcnt;
+ int result = 0;
+
+ assert( op->o_refcnt == 0 );
+
+ Debug( LDAP_DEBUG_TRACE, "operation_unlink: "
+ "unlinking operation between client connid=%lu and upstream "
+ "connid=%lu "
+ "client msgid=%d\n",
+ op->o_client_connid, op->o_upstream_connid, op->o_client_msgid );
+
+ checked_lock( &op->o_link_mutex );
+ client = op->o_client;
+ upstream = op->o_upstream;
+
+ op->o_client = NULL;
+ op->o_upstream = NULL;
+ checked_unlock( &op->o_link_mutex );
+
+ assert( client || upstream );
+
+ if ( client ) {
+ result |= operation_unlink_client( op, client );
+ operation_update_global_rejected( op );
+ }
+
+ if ( upstream ) {
+ result |= operation_unlink_upstream( op, upstream );
+ }
+
+ return result;
+}
+
+int
+operation_unlink_client( LloadOperation *op, LloadConnection *client )
+{
+ LloadOperation *removed;
+ int result = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "operation_unlink_client: "
+ "unlinking operation op=%p msgid=%d client connid=%lu\n",
+ op, op->o_client_msgid, op->o_client_connid );
+
+ CONNECTION_LOCK(client);
+ if ( (removed = ldap_tavl_delete(
+ &client->c_ops, op, operation_client_cmp )) ) {
+ result = LLOAD_OP_DETACHING_CLIENT;
+
+ assert( op == removed );
+ client->c_n_ops_executing--;
+
+ if ( op->o_restricted == LLOAD_OP_RESTRICTED_WRITE ) {
+ if ( !--client->c_restricted_inflight &&
+ client->c_restricted_at >= 0 ) {
+ if ( lload_write_coherence < 0 ) {
+ client->c_restricted_at = -1;
+ } else if ( timerisset( &op->o_last_response ) ) {
+ client->c_restricted_at = op->o_last_response.tv_sec;
+ } else {
+ /* We have to default to o_start just in case we abandoned an
+ * operation that the backend actually processed */
+ client->c_restricted_at = op->o_start.tv_sec;
+ }
+ }
+ }
+
+ if ( op->o_tag == LDAP_REQ_BIND &&
+ client->c_state == LLOAD_C_BINDING ) {
+ client->c_state = LLOAD_C_READY;
+ if ( !BER_BVISNULL( &client->c_auth ) ) {
+ ber_memfree( client->c_auth.bv_val );
+ BER_BVZERO( &client->c_auth );
+ }
+ if ( !BER_BVISNULL( &client->c_sasl_bind_mech ) ) {
+ ber_memfree( client->c_sasl_bind_mech.bv_val );
+ BER_BVZERO( &client->c_sasl_bind_mech );
+ }
+ if ( op->o_pin_id ) {
+ client->c_pin_id = 0;
+ }
+ }
+ }
+ if ( client->c_state == LLOAD_C_CLOSING && !client->c_ops ) {
+ CONNECTION_DESTROY(client);
+ } else {
+ CONNECTION_UNLOCK(client);
+ }
+
+ return result;
+}
+
+int
+operation_unlink_upstream( LloadOperation *op, LloadConnection *upstream )
+{
+ LloadOperation *removed;
+ LloadBackend *b = NULL;
+ int result = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "operation_unlink_upstream: "
+ "unlinking operation op=%p msgid=%d upstream connid=%lu\n",
+ op, op->o_upstream_msgid, op->o_upstream_connid );
+
+ CONNECTION_LOCK(upstream);
+ if ( (removed = ldap_tavl_delete(
+ &upstream->c_ops, op, operation_upstream_cmp )) ) {
+ result |= LLOAD_OP_DETACHING_UPSTREAM;
+
+ assert( op == removed );
+ upstream->c_n_ops_executing--;
+
+ if ( upstream->c_state == LLOAD_C_BINDING ) {
+ assert( op->o_tag == LDAP_REQ_BIND && upstream->c_ops == NULL );
+ upstream->c_state = LLOAD_C_READY;
+ if ( !BER_BVISNULL( &upstream->c_sasl_bind_mech ) ) {
+ ber_memfree( upstream->c_sasl_bind_mech.bv_val );
+ BER_BVZERO( &upstream->c_sasl_bind_mech );
+ }
+ }
+ operation_update_conn_counters( op, upstream );
+ b = upstream->c_backend;
+ }
+ if ( upstream->c_state == LLOAD_C_CLOSING && !upstream->c_ops ) {
+ CONNECTION_DESTROY(upstream);
+ } else {
+ CONNECTION_UNLOCK(upstream);
+ }
+
+ if ( b ) {
+ checked_lock( &b->b_mutex );
+ b->b_n_ops_executing--;
+ operation_update_backend_counters( op, b );
+ checked_unlock( &b->b_mutex );
+ }
+
+ return result;
+}
+
+int
+operation_send_abandon( LloadOperation *op, LloadConnection *upstream )
+{
+ BerElement *ber;
+ int rc = -1;
+
+ if ( !IS_ALIVE( upstream, c_live ) ) {
+ return rc;
+ }
+
+ checked_lock( &upstream->c_io_mutex );
+ ber = upstream->c_pendingber;
+ if ( ber == NULL && (ber = ber_alloc()) == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "operation_send_abandon: "
+ "ber_alloc failed\n" );
+ goto done;
+ }
+ upstream->c_pendingber = ber;
+
+ Debug( LDAP_DEBUG_TRACE, "operation_send_abandon: "
+ "abandoning %s msgid=%d on connid=%lu\n",
+ lload_msgtype2str( op->o_tag ), op->o_upstream_msgid,
+ op->o_upstream_connid );
+
+ if ( op->o_tag == LDAP_REQ_BIND ) {
+ rc = ber_printf( ber, "t{tit{ist{s}}}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, upstream->c_next_msgid++,
+ LDAP_REQ_BIND, LDAP_VERSION3, "", LDAP_AUTH_SASL, "" );
+ } else {
+ rc = ber_printf( ber, "t{titi}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, upstream->c_next_msgid++,
+ LDAP_REQ_ABANDON, op->o_upstream_msgid );
+ }
+
+ if ( rc < 0 ) {
+ ber_free( ber, 1 );
+ upstream->c_pendingber = NULL;
+ goto done;
+ }
+ rc = LDAP_SUCCESS;
+
+done:
+ checked_unlock( &upstream->c_io_mutex );
+ return rc;
+}
+
+/*
+ * Will remove the operation from its upstream and if it was still there,
+ * sends an abandon request.
+ *
+ * Being called from client_reset or request_abandon, the following hold:
+ * - noone else is processing the read part of the client connection (no new
+ * operations come in there - relevant for the c_state checks)
+ * - op->o_client_refcnt > op->o_client_live (and it follows that op->o_client != NULL)
+ */
+void
+operation_abandon( LloadOperation *op )
+{
+ LloadConnection *c;
+
+ checked_lock( &op->o_link_mutex );
+ c = op->o_upstream;
+ checked_unlock( &op->o_link_mutex );
+ if ( !c || !IS_ALIVE( c, c_live ) ) {
+ goto done;
+ }
+
+ /* for now consider all abandoned operations completed,
+ * perhaps add a separate counter later */
+ op->o_res = LLOAD_OP_COMPLETED;
+ if ( !operation_unlink_upstream( op, c ) ) {
+ /* The operation has already been abandoned or finished */
+ Debug( LDAP_DEBUG_TRACE, "operation_abandon: "
+ "%s from connid=%lu msgid=%d not present in connid=%lu any "
+ "more\n",
+ lload_msgtype2str( op->o_tag ), op->o_client_connid,
+ op->o_client_msgid, op->o_upstream_connid );
+ goto done;
+ }
+
+ if ( operation_send_abandon( op, c ) == LDAP_SUCCESS ) {
+ connection_write_cb( -1, 0, c );
+ }
+
+done:
+ OPERATION_UNLINK(op);
+}
+
+void
+operation_send_reject(
+ LloadOperation *op,
+ int result,
+ const char *msg,
+ int send_anyway )
+{
+ LloadConnection *c;
+ BerElement *ber;
+ int found;
+
+ Debug( LDAP_DEBUG_TRACE, "operation_send_reject: "
+ "rejecting %s from client connid=%lu with message: \"%s\"\n",
+ lload_msgtype2str( op->o_tag ), op->o_client_connid, msg );
+
+ checked_lock( &op->o_link_mutex );
+ c = op->o_client;
+ checked_unlock( &op->o_link_mutex );
+ if ( !c || !IS_ALIVE( c, c_live ) ) {
+ Debug( LDAP_DEBUG_TRACE, "operation_send_reject: "
+ "not sending msgid=%d, client connid=%lu is dead\n",
+ op->o_client_msgid, op->o_client_connid );
+
+ goto done;
+ }
+
+ found = operation_unlink_client( op, c );
+ if ( !found && !send_anyway ) {
+ Debug( LDAP_DEBUG_TRACE, "operation_send_reject: "
+ "msgid=%d not scheduled for client connid=%lu anymore, "
+ "not sending\n",
+ op->o_client_msgid, c->c_connid );
+ goto done;
+ }
+
+ if ( op->o_client_msgid == 0 ) {
+ assert( op->o_saved_msgid == 0 && op->o_pin_id );
+ Debug( LDAP_DEBUG_TRACE, "operation_send_reject: "
+ "operation pin=%lu is just a pin, not sending\n",
+ op->o_pin_id );
+ goto done;
+ }
+
+ checked_lock( &c->c_io_mutex );
+ ber = c->c_pendingber;
+ if ( ber == NULL && (ber = ber_alloc()) == NULL ) {
+ checked_unlock( &c->c_io_mutex );
+ Debug( LDAP_DEBUG_ANY, "operation_send_reject: "
+ "ber_alloc failed, closing connid=%lu\n",
+ c->c_connid );
+ CONNECTION_LOCK_DESTROY(c);
+ goto done;
+ }
+ c->c_pendingber = ber;
+
+ ber_printf( ber, "t{tit{ess}}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, op->o_client_msgid,
+ slap_req2res( op->o_tag ), result, "", msg );
+
+ checked_unlock( &c->c_io_mutex );
+
+ connection_write_cb( -1, 0, c );
+
+done:
+ OPERATION_UNLINK(op);
+}
+
+/*
+ * Upstream is shutting down, signal the client if necessary, but we have to
+ * call operation_destroy_from_upstream ourselves to detach upstream from the
+ * op.
+ *
+ * Only called from upstream_destroy.
+ */
+void
+operation_lost_upstream( LloadOperation *op )
+{
+ operation_send_reject( op, LDAP_OTHER,
+ "connection to the remote server has been severed", 0 );
+}
+
+int
+connection_timeout( LloadConnection *upstream, void *arg )
+{
+ LloadOperation *op;
+ TAvlnode *ops = NULL, *node, *next;
+ LloadBackend *b = upstream->c_backend;
+ struct timeval *threshold = arg;
+ int rc, nops = 0;
+
+ CONNECTION_LOCK(upstream);
+ for ( node = ldap_tavl_end( upstream->c_ops, TAVL_DIR_LEFT );
+ node && timercmp( &((LloadOperation *)node->avl_data)->o_start,
+ threshold, < ); /* shortcut */
+ node = next ) {
+ LloadOperation *found_op;
+
+ next = ldap_tavl_next( node, TAVL_DIR_RIGHT );
+ op = node->avl_data;
+
+ /* Have we received another response since? */
+ if ( timerisset( &op->o_last_response ) &&
+ !timercmp( &op->o_last_response, threshold, < ) ) {
+ continue;
+ }
+
+ op->o_res = LLOAD_OP_FAILED;
+ found_op = ldap_tavl_delete( &upstream->c_ops, op, operation_upstream_cmp );
+ assert( op == found_op );
+
+ if ( upstream->c_state == LLOAD_C_BINDING ) {
+ assert( op->o_tag == LDAP_REQ_BIND && upstream->c_ops == NULL );
+ upstream->c_state = LLOAD_C_READY;
+ if ( !BER_BVISNULL( &upstream->c_sasl_bind_mech ) ) {
+ ber_memfree( upstream->c_sasl_bind_mech.bv_val );
+ BER_BVZERO( &upstream->c_sasl_bind_mech );
+ }
+ }
+
+ rc = ldap_tavl_insert( &ops, op, operation_upstream_cmp, ldap_avl_dup_error );
+ assert( rc == LDAP_SUCCESS );
+
+ Debug( LDAP_DEBUG_STATS2, "connection_timeout: "
+ "timing out %s from connid=%lu msgid=%d sent to connid=%lu as "
+ "msgid=%d\n",
+ lload_msgtype2str( op->o_tag ), op->o_client_connid,
+ op->o_client_msgid, op->o_upstream_connid,
+ op->o_upstream_msgid );
+ nops++;
+ }
+
+ if ( nops == 0 ) {
+ CONNECTION_UNLOCK(upstream);
+ return LDAP_SUCCESS;
+ }
+ upstream->c_n_ops_executing -= nops;
+ upstream->c_counters.lc_ops_failed += nops;
+ Debug( LDAP_DEBUG_STATS, "connection_timeout: "
+ "timing out %d operations for connid=%lu\n",
+ nops, upstream->c_connid );
+ CONNECTION_UNLOCK(upstream);
+
+ checked_lock( &b->b_mutex );
+ b->b_n_ops_executing -= nops;
+ checked_unlock( &b->b_mutex );
+
+ for ( node = ldap_tavl_end( ops, TAVL_DIR_LEFT ); node;
+ node = ldap_tavl_next( node, TAVL_DIR_RIGHT ) ) {
+ op = node->avl_data;
+
+ operation_send_reject( op,
+ op->o_tag == LDAP_REQ_SEARCH ? LDAP_TIMELIMIT_EXCEEDED :
+ LDAP_ADMINLIMIT_EXCEEDED,
+ "upstream did not respond in time", 0 );
+
+ if ( upstream->c_type != LLOAD_C_BIND && rc == LDAP_SUCCESS ) {
+ rc = operation_send_abandon( op, upstream );
+ }
+ OPERATION_UNLINK(op);
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ connection_write_cb( -1, 0, upstream );
+ }
+
+ CONNECTION_LOCK(upstream);
+ /* ITS#9799: If a Bind timed out, connection is in an unknown state */
+ if ( upstream->c_type == LLOAD_C_BIND || rc != LDAP_SUCCESS ||
+ ( upstream->c_state == LLOAD_C_CLOSING && !upstream->c_ops ) ) {
+ CONNECTION_DESTROY(upstream);
+ } else {
+ CONNECTION_UNLOCK(upstream);
+ }
+
+ /* just dispose of the AVL, most operations should already be gone */
+ ldap_tavl_free( ops, NULL );
+ return LDAP_SUCCESS;
+}
+
+void
+operations_timeout( evutil_socket_t s, short what, void *arg )
+{
+ struct event *self = arg;
+ LloadTier *tier;
+ time_t threshold;
+
+ Debug( LDAP_DEBUG_TRACE, "operations_timeout: "
+ "running timeout task\n" );
+ if ( !lload_timeout_api ) goto done;
+
+ threshold = slap_get_time() - lload_timeout_api->tv_sec;
+
+ LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) {
+ LloadBackend *b;
+
+ LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
+ epoch_t epoch;
+
+ checked_lock( &b->b_mutex );
+ if ( b->b_n_ops_executing == 0 ) {
+ checked_unlock( &b->b_mutex );
+ continue;
+ }
+
+ epoch = epoch_join();
+
+ Debug( LDAP_DEBUG_TRACE, "operations_timeout: "
+ "timing out binds for backend uri=%s\n",
+ b->b_uri.bv_val );
+ connections_walk_last( &b->b_mutex, &b->b_bindconns,
+ b->b_last_bindconn, connection_timeout, &threshold );
+
+ Debug( LDAP_DEBUG_TRACE, "operations_timeout: "
+ "timing out other operations for backend uri=%s\n",
+ b->b_uri.bv_val );
+ connections_walk_last( &b->b_mutex, &b->b_conns, b->b_last_conn,
+ connection_timeout, &threshold );
+
+ epoch_leave( epoch );
+ checked_unlock( &b->b_mutex );
+ }
+ }
+done:
+ Debug( LDAP_DEBUG_TRACE, "operations_timeout: "
+ "timeout task finished\n" );
+ evtimer_add( self, lload_timeout_api );
+}
+
+void
+operation_update_global_rejected( LloadOperation *op )
+{
+ if ( op->o_res == LLOAD_OP_REJECTED ) {
+ assert( op->o_upstream_connid == 0 );
+ switch ( op->o_tag ) {
+ case LDAP_REQ_BIND:
+ lload_stats.counters[LLOAD_STATS_OPS_BIND].lc_ops_rejected++;
+ break;
+ default:
+ lload_stats.counters[LLOAD_STATS_OPS_OTHER].lc_ops_rejected++;
+ break;
+ }
+ }
+}
+
+void
+operation_update_conn_counters( LloadOperation *op, LloadConnection *upstream )
+{
+ if ( op->o_res == LLOAD_OP_COMPLETED ) {
+ upstream->c_counters.lc_ops_completed++;
+ } else {
+ upstream->c_counters.lc_ops_failed++;
+ }
+}
+
+void
+operation_update_backend_counters( LloadOperation *op, LloadBackend *b )
+{
+ int stat_type = op->o_tag == LDAP_REQ_BIND ? LLOAD_STATS_OPS_BIND :
+ LLOAD_STATS_OPS_OTHER;
+
+ assert( b != NULL );
+ if ( op->o_res == LLOAD_OP_COMPLETED ) {
+ b->b_counters[stat_type].lc_ops_completed++;
+ } else {
+ b->b_counters[stat_type].lc_ops_failed++;
+ }
+}
diff --git a/servers/lloadd/proto-lload.h b/servers/lloadd/proto-lload.h
new file mode 100644
index 0000000..cfbbd95
--- /dev/null
+++ b/servers/lloadd/proto-lload.h
@@ -0,0 +1,254 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* 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 PROTO_LLOAD_H
+#define PROTO_LLOAD_H
+
+#include <ldap_cdefs.h>
+#include "ldap_pvt.h"
+
+#include <event2/event.h>
+
+LDAP_BEGIN_DECL
+
+/*
+ * backend.c
+ */
+
+LDAP_SLAPD_F (void) backend_connect( evutil_socket_t s, short what, void *arg );
+LDAP_SLAPD_F (void *) backend_connect_task( void *ctx, void *arg );
+LDAP_SLAPD_F (void) backend_retry( LloadBackend *b );
+LDAP_SLAPD_F (int) upstream_select( LloadOperation *op, LloadConnection **c, int *res, char **message );
+LDAP_SLAPD_F (int) backend_select( LloadBackend *b, LloadOperation *op, LloadConnection **c, int *res, char **message );
+LDAP_SLAPD_F (int) try_upstream( LloadBackend *b, lload_c_head *head, LloadOperation *op, LloadConnection *c, int *res, char **message );
+LDAP_SLAPD_F (void) backend_reset( LloadBackend *b, int gentle );
+LDAP_SLAPD_F (LloadBackend *) lload_backend_new( void );
+LDAP_SLAPD_F (void) lload_backend_destroy( LloadBackend *b );
+
+/*
+ * bind.c
+ */
+LDAP_SLAPD_F (int) request_bind( LloadConnection *c, LloadOperation *op );
+LDAP_SLAPD_F (int) handle_bind_response( LloadConnection *client, LloadOperation *op, BerElement *ber );
+LDAP_SLAPD_F (int) handle_whoami_response( LloadConnection *client, LloadOperation *op, BerElement *ber );
+LDAP_SLAPD_F (int) handle_vc_bind_response( LloadConnection *client, LloadOperation *op, BerElement *ber );
+
+/*
+ * client.c
+ */
+LDAP_SLAPD_F (int) request_abandon( LloadConnection *c, LloadOperation *op );
+LDAP_SLAPD_F (int) request_process( LloadConnection *c, LloadOperation *op );
+LDAP_SLAPD_F (int) handle_one_request( LloadConnection *c );
+LDAP_SLAPD_F (void) client_tls_handshake_cb( evutil_socket_t s, short what, void *arg );
+LDAP_SLAPD_F (LloadConnection *) client_init( ber_socket_t s, const char *peername, struct event_base *base, int use_tls );
+LDAP_SLAPD_F (void) client_reset( LloadConnection *c );
+LDAP_SLAPD_F (void) client_destroy( LloadConnection *c );
+LDAP_SLAPD_F (void) clients_destroy( int gentle );
+LDAP_SLAPD_V (long) lload_client_max_pending;
+
+/*
+ * config.c
+ */
+LDAP_SLAPD_F (int) lload_read_config( const char *fname, const char *dir );
+LDAP_SLAPD_F (void) lload_config_destroy( void );
+LDAP_SLAPD_F (int) verb_to_mask( const char *word, slap_verbmasks *v );
+LDAP_SLAPD_F (int) lload_tls_get_config( LDAP *ld, int opt, char **val );
+LDAP_SLAPD_F (void) lload_bindconf_tls_defaults( slap_bindconf *bc );
+LDAP_SLAPD_F (int) lload_backend_parse( const char *word, LloadBackend *b );
+LDAP_SLAPD_F (int) lload_bindconf_parse( const char *word, slap_bindconf *bc );
+LDAP_SLAPD_F (int) lload_bindconf_unparse( slap_bindconf *bc, struct berval *bv );
+LDAP_SLAPD_F (int) lload_bindconf_tls_set( slap_bindconf *bc, LDAP *ld );
+LDAP_SLAPD_F (void) lload_bindconf_free( slap_bindconf *bc );
+LDAP_SLAPD_F (void) lload_restriction_free( struct restriction_entry *entry );
+#ifdef BALANCER_MODULE
+LDAP_SLAPD_F (int) lload_back_init_cf( BackendInfo *bi );
+#endif
+
+/*
+ * connection.c
+ */
+LDAP_SLAPD_V (ldap_pvt_thread_mutex_t) clients_mutex;
+LDAP_SLAPD_F (void *) handle_pdus( void *ctx, void *arg );
+LDAP_SLAPD_F (void) connection_write_cb( evutil_socket_t s, short what, void *arg );
+LDAP_SLAPD_F (void) connection_read_cb( evutil_socket_t s, short what, void *arg );
+LDAP_SLAPD_F (int) lload_connection_close( LloadConnection *c, void *arg );
+LDAP_SLAPD_F (LloadConnection *) lload_connection_init( ber_socket_t s, const char *peername, int use_tls );
+LDAP_SLAPD_F (void) connection_destroy( LloadConnection *c );
+LDAP_SLAPD_F (void) connections_walk_last( ldap_pvt_thread_mutex_t *cq_mutex,
+ lload_c_head *cq,
+ LloadConnection *cq_last,
+ CONNCB cb,
+ void *arg );
+LDAP_SLAPD_F (void) connections_walk( ldap_pvt_thread_mutex_t *cq_mutex, lload_c_head *cq, CONNCB cb, void *arg );
+
+/*
+ * daemon.c
+ */
+LDAP_SLAPD_F (int) lload_open_new_listener( const char *urls, LDAPURLDesc *lud );
+LDAP_SLAPD_F (int) lloadd_listeners_init( const char *urls );
+LDAP_SLAPD_F (int) lloadd_daemon_destroy( void );
+LDAP_SLAPD_F (int) lloadd_daemon( struct event_base *daemon_base );
+LDAP_SLAPD_F (LloadListener **) lloadd_get_listeners( void );
+LDAP_SLAPD_F (void) listeners_reactivate( void );
+LDAP_SLAPD_F (struct event_base *) lload_get_base( ber_socket_t s );
+LDAP_SLAPD_V (int) lload_daemon_threads;
+LDAP_SLAPD_V (int) lload_daemon_mask;
+
+LDAP_SLAPD_F (void) lload_sig_shutdown( evutil_socket_t sig, short what, void *arg );
+
+LDAP_SLAPD_F (void) lload_pause_server( void );
+LDAP_SLAPD_F (void) lload_unpause_server( void );
+
+LDAP_SLAPD_V (struct event_base *) daemon_base;
+LDAP_SLAPD_V (struct evdns_base *) dnsbase;
+LDAP_SLAPD_V (volatile sig_atomic_t) slapd_shutdown;
+LDAP_SLAPD_V (volatile sig_atomic_t) slapd_gentle_shutdown;
+LDAP_SLAPD_V (int) lloadd_inited;
+LDAP_SLAPD_V (struct LloadChange) lload_change;
+
+LDAP_SLAPD_V (struct event *) lload_timeout_event;
+
+LDAP_SLAPD_V (LDAP *) lload_tls_backend_ld;
+LDAP_SLAPD_V (LDAP *) lload_tls_ld;
+LDAP_SLAPD_V (void *) lload_tls_ctx;
+#ifdef BALANCER_MODULE
+LDAP_SLAPD_V (int) lload_use_slap_tls_ctx;
+#endif /* BALANCER_MODULE */
+
+/*
+ * extended.c
+ */
+LDAP_SLAPD_V (Avlnode *) lload_exop_handlers;
+LDAP_SLAPD_F (int) exop_handler_cmp( const void *l, const void *r );
+LDAP_SLAPD_F (int) request_extended( LloadConnection *c, LloadOperation *op );
+LDAP_SLAPD_F (int) lload_exop_init( void );
+LDAP_SLAPD_F (void) lload_exop_destroy( void );
+
+/*
+ * init.c
+ */
+LDAP_SLAPD_F (int) lload_global_init( void );
+LDAP_SLAPD_F (int) lload_global_destroy( void );
+LDAP_SLAPD_F (int) lload_tls_init( void );
+LDAP_SLAPD_F (int) lload_init( int mode, const char *name );
+LDAP_SLAPD_F (int) lload_destroy( void );
+LDAP_SLAPD_F (void) lload_counters_init( void );
+
+/*
+ * libevent_support.c
+ */
+LDAP_SLAPD_F (int) lload_libevent_init( void );
+LDAP_SLAPD_F (void) lload_libevent_destroy( void );
+
+#ifdef BALANCER_MODULE
+/*
+ * monitor.c
+ */
+LDAP_SLAPD_V (monitor_subsys_t *) lload_monitor_client_subsys;
+LDAP_SLAPD_F (int) lload_monitor_open( void );
+LDAP_SLAPD_F (int) lload_monitor_conn_entry_create( LloadConnection *c, monitor_subsys_t *ms );
+LDAP_SLAPD_F (int) lload_monitor_conn_unlink( LloadConnection *c );
+LDAP_SLAPD_F (int) lload_monitor_backend_init( BackendInfo *bi, monitor_subsys_t *ms, LloadBackend *b );
+LDAP_SLAPD_F (int) lload_monitor_tier_init( BackendInfo *bi, LloadTier *tier );
+#endif /* BALANCER_MODULE */
+
+/*
+ * operation.c
+ */
+LDAP_SLAPD_V (ldap_pvt_thread_mutex_t) lload_pin_mutex;
+LDAP_SLAPD_V (unsigned long) lload_next_pin;
+LDAP_SLAPD_V (TAvlnode *) lload_control_actions;
+LDAP_SLAPD_V (TAvlnode *) lload_exop_actions;
+LDAP_SLAPD_V (enum op_restriction) lload_default_exop_action;
+LDAP_SLAPD_F (int) lload_restriction_cmp( const void *left, const void *right );
+LDAP_SLAPD_F (const char *) lload_msgtype2str( ber_tag_t tag );
+LDAP_SLAPD_F (int) operation_upstream_cmp( const void *l, const void *r );
+LDAP_SLAPD_F (int) operation_client_cmp( const void *l, const void *r );
+LDAP_SLAPD_F (LloadOperation *) operation_init( LloadConnection *c, BerElement *ber );
+LDAP_SLAPD_F (int) operation_send_abandon( LloadOperation *op, LloadConnection *c );
+LDAP_SLAPD_F (void) operation_abandon( LloadOperation *op );
+LDAP_SLAPD_F (void) operation_send_reject( LloadOperation *op, int result, const char *msg, int send_anyway );
+LDAP_SLAPD_F (int) operation_send_reject_locked( LloadOperation *op, int result, const char *msg, int send_anyway );
+LDAP_SLAPD_F (void) operation_lost_upstream( LloadOperation *op );
+LDAP_SLAPD_F (void) operation_destroy( LloadOperation *op );
+LDAP_SLAPD_F (int) operation_unlink( LloadOperation *op );
+LDAP_SLAPD_F (int) operation_unlink_client( LloadOperation *op, LloadConnection *client );
+LDAP_SLAPD_F (int) operation_unlink_upstream( LloadOperation *op, LloadConnection *upstream );
+LDAP_SLAPD_F (void) operations_timeout( evutil_socket_t s, short what, void *arg );
+LDAP_SLAPD_F (void) operation_update_conn_counters( LloadOperation *op, LloadConnection *upstream );
+LDAP_SLAPD_F (void) operation_update_backend_counters( LloadOperation *op, LloadBackend *b );
+LDAP_SLAPD_F (void) operation_update_global_rejected( LloadOperation *op );
+
+/*
+ * tier.c
+ */
+LDAP_SLAPD_F (int) tier_startup( LloadTier *tier );
+LDAP_SLAPD_F (int) tier_reset( LloadTier *tier, int shutdown );
+LDAP_SLAPD_F (int) tier_destroy( LloadTier *tier );
+LDAP_SLAPD_F (void) lload_tiers_shutdown( void );
+LDAP_SLAPD_F (void) lload_tiers_reset( int shutdown );
+LDAP_SLAPD_F (void) lload_tiers_update( evutil_socket_t s, short what, void *arg );
+LDAP_SLAPD_F (void) lload_tiers_destroy( void );
+LDAP_SLAPD_F (struct lload_tier_type *) lload_tier_find( char *type );
+
+/*
+ * upstream.c
+ */
+LDAP_SLAPD_F (int) lload_upstream_entry_cmp( const void *l, const void *r );
+LDAP_SLAPD_F (int) forward_final_response( LloadConnection *client, LloadOperation *op, BerElement *ber );
+LDAP_SLAPD_F (int) forward_response( LloadConnection *client, LloadOperation *op, BerElement *ber );
+LDAP_SLAPD_F (void *) upstream_bind( void *ctx, void *arg );
+LDAP_SLAPD_F (LloadConnection *) upstream_init( ber_socket_t s, LloadBackend *b );
+LDAP_SLAPD_F (void) upstream_destroy( LloadConnection *c );
+
+LDAP_SLAPD_V (ber_len_t) sockbuf_max_incoming_client;
+LDAP_SLAPD_V (ber_len_t) sockbuf_max_incoming_upstream;
+LDAP_SLAPD_V (int) lload_conn_max_pdus_per_cycle;
+
+LDAP_SLAPD_V (int) lload_write_coherence;
+
+LDAP_SLAPD_V (lload_features_t) lload_features;
+
+LDAP_SLAPD_V (slap_mask_t) global_allows;
+LDAP_SLAPD_V (slap_mask_t) global_disallows;
+
+LDAP_SLAPD_V (const char) Versionstr[];
+
+LDAP_SLAPD_V (int) global_gentlehup;
+LDAP_SLAPD_V (int) global_idletimeout;
+
+LDAP_SLAPD_V (struct timeval *) lload_timeout_api;
+LDAP_SLAPD_V (struct timeval *) lload_timeout_net;
+LDAP_SLAPD_V (struct timeval *) lload_write_timeout;
+
+LDAP_SLAPD_V (char *) global_host;
+LDAP_SLAPD_V (int) lber_debug;
+LDAP_SLAPD_V (int) ldap_syslog;
+
+LDAP_SLAPD_V (lload_global_stats_t) lload_stats;
+LDAP_SLAPD_V (char *) listeners_list;
+LDAP_END_DECL
+
+#endif /* PROTO_LLOAD_H */
diff --git a/servers/lloadd/tier.c b/servers/lloadd/tier.c
new file mode 100644
index 0000000..84ead03
--- /dev/null
+++ b/servers/lloadd/tier.c
@@ -0,0 +1,168 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include "lload.h"
+
+lload_t_head tiers;
+
+int
+tier_startup( LloadTier *tier )
+{
+ LloadBackend *b;
+
+ LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
+ checked_lock( &b->b_mutex );
+ if ( !b->b_retry_event ) {
+ b->b_retry_event = evtimer_new( daemon_base, backend_connect, b );
+ if ( !b->b_retry_event ) {
+ Debug( LDAP_DEBUG_ANY, "tier_startup: "
+ "%s failed to allocate retry event\n",
+ tier->t_type.tier_name );
+ return -1;
+ }
+ }
+ backend_retry( b );
+ checked_unlock( &b->b_mutex );
+ }
+ return LDAP_SUCCESS;
+}
+
+int
+tier_reset( LloadTier *tier, int shutdown )
+{
+ LloadBackend *b;
+
+ LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
+ epoch_t epoch = epoch_join();
+
+ checked_lock( &b->b_mutex );
+ if ( shutdown ) {
+ b->b_numconns = b->b_numbindconns = 0;
+ }
+ backend_reset( b, 1 );
+ backend_retry( b );
+ checked_unlock( &b->b_mutex );
+
+ epoch_leave( epoch );
+ }
+ return LDAP_SUCCESS;
+}
+
+int
+tier_destroy( LloadTier *tier )
+{
+ while ( !LDAP_CIRCLEQ_EMPTY( &tier->t_backends ) ) {
+ LloadBackend *b = LDAP_CIRCLEQ_FIRST( &tier->t_backends );
+ epoch_t epoch = epoch_join();
+
+ lload_backend_destroy( b );
+
+ epoch_leave( epoch );
+ }
+
+#ifdef BALANCER_MODULE
+ if ( tier->t_monitor ) {
+ /* FIXME: implement proper subsys shutdown in back-monitor or make
+ * backend just an entry, not a subsys */
+ if ( slapd_shutdown ) {
+ /* Just drop backlink, back-monitor will call mss_destroy later */
+ assert( tier->t_monitor->mss_private == tier );
+ tier->t_monitor->mss_private = NULL;
+ } else {
+ BackendDB *be;
+ struct berval monitordn = BER_BVC("cn=monitor");
+ int rc;
+
+ be = select_backend( &monitordn, 0 );
+
+ rc = tier->t_monitor->mss_destroy( be, tier->t_monitor );
+ assert( rc == LDAP_SUCCESS );
+ }
+ }
+#endif /* BALANCER_MODULE */
+
+ ch_free( tier->t_name.bv_val );
+ ch_free( tier );
+ return LDAP_SUCCESS;
+}
+
+void
+lload_tiers_destroy( void )
+{
+ while ( !LDAP_STAILQ_EMPTY( &tiers ) ) {
+ LloadTier *tier = LDAP_STAILQ_FIRST( &tiers );
+
+ LDAP_STAILQ_REMOVE_HEAD( &tiers, t_next );
+ tier->t_type.tier_destroy( tier );
+ }
+}
+
+void
+lload_tiers_shutdown( void )
+{
+ lload_tiers_reset( 1 );
+}
+
+void
+lload_tiers_reset( int shutdown )
+{
+ LloadTier *tier;
+
+ LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) {
+ tier->t_type.tier_reset( tier, shutdown );
+ }
+}
+
+void
+lload_tiers_update( evutil_socket_t s, short what, void *arg )
+{
+ LloadTier *tier;
+
+ LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) {
+ if ( tier->t_type.tier_update ) {
+ tier->t_type.tier_update( tier );
+ }
+ }
+}
+
+extern struct lload_tier_type roundrobin_tier;
+extern struct lload_tier_type weighted_tier;
+extern struct lload_tier_type bestof_tier;
+
+struct {
+ char *name;
+ struct lload_tier_type *type;
+} tier_types[] = {
+ { "roundrobin", &roundrobin_tier },
+ { "weighted", &weighted_tier },
+ { "bestof", &bestof_tier },
+
+ { NULL }
+};
+
+struct lload_tier_type *
+lload_tier_find( char *name )
+{
+ int i;
+
+ for ( i = 0; tier_types[i].name; i++ ) {
+ if ( !strcasecmp( name, tier_types[i].name ) ) {
+ return tier_types[i].type;
+ }
+ }
+ return NULL;
+}
diff --git a/servers/lloadd/tier_bestof.c b/servers/lloadd/tier_bestof.c
new file mode 100644
index 0000000..0c44d4e
--- /dev/null
+++ b/servers/lloadd/tier_bestof.c
@@ -0,0 +1,328 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/string.h>
+#include <math.h>
+
+#include "lload.h"
+#include "lutil.h"
+
+static LloadTierInit bestof_init;
+static LloadTierBackendConfigCb bestof_backend_options;
+static LloadTierBackendCb bestof_add_backend;
+static LloadTierBackendCb bestof_remove_backend;
+static LloadTierSelect bestof_select;
+
+struct lload_tier_type bestof_tier;
+
+/*
+ * xorshift - we don't need high quality randomness, and we don't want to
+ * interfere with anyone else's use of srand() but we still want something with
+ * little bias.
+ *
+ * The PRNG here cycles thru 2^64−1 numbers.
+ */
+static uint64_t bestof_seed;
+
+static void
+bestof_srand( int seed )
+{
+ bestof_seed = seed;
+}
+
+static uint64_t
+bestof_rand()
+{
+ uint64_t val = bestof_seed;
+ val ^= val << 13;
+ val ^= val >> 7;
+ val ^= val << 17;
+ bestof_seed = val;
+ return val;
+}
+
+static int
+bestof_cmp( const void *left, const void *right )
+{
+ const LloadBackend *l = left;
+ const LloadBackend *r = right;
+ struct timeval now;
+ uintptr_t count, diff;
+ float a = l->b_fitness, b = r->b_fitness, factor = 1;
+
+ gettimeofday( &now, NULL );
+ /* We assume this is less than a second after the last update */
+ factor = 1 / ( pow( ( 1 / factor ) + 1, now.tv_usec / 1000000.0 ) - 1 );
+
+ count = __atomic_load_n( &l->b_operation_count, __ATOMIC_RELAXED );
+ diff = __atomic_load_n( &l->b_operation_time, __ATOMIC_RELAXED );
+ if ( count ) {
+ a = ( a * factor + (float)diff * l->b_weight / count ) / ( factor + 1 );
+ }
+
+ count = __atomic_load_n( &r->b_operation_count, __ATOMIC_RELAXED );
+ diff = __atomic_load_n( &r->b_operation_time, __ATOMIC_RELAXED );
+ if ( count ) {
+ b = ( b * factor + (float)diff * r->b_weight / count ) / ( factor + 1 );
+ }
+
+ return (a - b < 0) ? -1 : (a - b == 0) ? 0 : 1;
+}
+
+LloadTier *
+bestof_init( void )
+{
+ LloadTier *tier;
+ int seed;
+
+ tier = ch_calloc( 1, sizeof(LloadTier) );
+
+ tier->t_type = bestof_tier;
+ ldap_pvt_thread_mutex_init( &tier->t_mutex );
+ LDAP_CIRCLEQ_INIT( &tier->t_backends );
+
+ /* Make sure we don't pass 0 as a seed */
+ do {
+ seed = rand();
+ } while ( !seed );
+ bestof_srand( seed );
+
+ return tier;
+}
+
+int
+bestof_add_backend( LloadTier *tier, LloadBackend *b )
+{
+ assert( b->b_tier == tier );
+
+ LDAP_CIRCLEQ_INSERT_TAIL( &tier->t_backends, b, b_next );
+ if ( !tier->t_private ) {
+ tier->t_private = b;
+ }
+ tier->t_nbackends++;
+ return LDAP_SUCCESS;
+}
+
+static int
+bestof_remove_backend( LloadTier *tier, LloadBackend *b )
+{
+ LloadBackend *next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next );
+
+ assert_locked( &tier->t_mutex );
+ assert_locked( &b->b_mutex );
+
+ assert( b->b_tier == tier );
+ assert( tier->t_private );
+
+ LDAP_CIRCLEQ_REMOVE( &tier->t_backends, b, b_next );
+ LDAP_CIRCLEQ_ENTRY_INIT( b, b_next );
+
+ if ( b == next ) {
+ tier->t_private = NULL;
+ } else {
+ tier->t_private = next;
+ }
+ tier->t_nbackends--;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+bestof_backend_options( LloadTier *tier, LloadBackend *b, char *arg )
+{
+ struct berval weight = BER_BVC("weight=");
+ unsigned long l;
+
+ if ( !strncasecmp( arg, weight.bv_val, weight.bv_len ) ) {
+ if ( lutil_atoulx( &l, &arg[weight.bv_len], 0 ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "bestof_backend_options: "
+ "cannot parse %s as weight\n",
+ arg );
+ return 1;
+ }
+ b->b_weight = l;
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+bestof_update( LloadTier *tier )
+{
+ LloadBackend *b, *first, *next;
+ time_t now = slap_get_time();
+
+ checked_lock( &tier->t_mutex );
+ first = b = tier->t_private;
+ checked_unlock( &tier->t_mutex );
+
+ if ( !first ) return LDAP_SUCCESS;
+
+ do {
+ int steps;
+ checked_lock( &b->b_mutex );
+
+ steps = now - b->b_last_update;
+ if ( b->b_weight && steps > 0 ) {
+ uintptr_t count, diff;
+ float factor = 1;
+
+ count = __atomic_exchange_n(
+ &b->b_operation_count, 0, __ATOMIC_RELAXED );
+ diff = __atomic_exchange_n(
+ &b->b_operation_time, 0, __ATOMIC_RELAXED );
+
+ /* Smear values over time - rolling average */
+ if ( count ) {
+ float fitness = b->b_weight * diff;
+
+ /* Stretch factor accordingly favouring the latest value */
+ if ( steps > 10 ) {
+ factor = 0; /* No recent data */
+ } else if ( steps > 1 ) {
+ factor = 1 / ( pow( ( 1 / factor ) + 1, steps ) - 1 );
+ }
+
+ b->b_fitness = ( factor * b->b_fitness + fitness / count ) /
+ ( factor + 1 );
+ b->b_last_update = now;
+ }
+ }
+
+ next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next );
+ checked_unlock( &b->b_mutex );
+ b = next;
+ } while ( b != first );
+
+ return LDAP_SUCCESS;
+}
+
+int
+bestof_select(
+ LloadTier *tier,
+ LloadOperation *op,
+ LloadConnection **cp,
+ int *res,
+ char **message )
+{
+ LloadBackend *first, *next, *b, *b0, *b1;
+ int result = 0, rc = 0, n = tier->t_nbackends;
+ int i0, i1, i = 0;
+
+ checked_lock( &tier->t_mutex );
+ first = b0 = b = tier->t_private;
+ checked_unlock( &tier->t_mutex );
+
+ if ( !first ) return rc;
+
+ if ( tier->t_nbackends == 1 ) {
+ goto fallback;
+ }
+
+ /* Pick two backend indices at random */
+ i0 = bestof_rand() % n;
+ i1 = bestof_rand() % ( n - 1 );
+ if ( i1 >= i0 ) {
+ i1 += 1;
+ } else {
+ int tmp = i0;
+ i0 = i1;
+ i1 = tmp;
+ }
+ assert( i0 < i1 );
+
+ /*
+ * FIXME: use a static array in t_private so we don't have to do any of
+ * this
+ */
+ for ( i = 0; i < i1; i++ ) {
+ if ( i == i0 ) {
+ b0 = b;
+ }
+ checked_lock( &b->b_mutex );
+ next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next );
+ checked_unlock( &b->b_mutex );
+ b = next;
+ }
+ b1 = b;
+ assert( b0 != b1 );
+
+ if ( bestof_cmp( b0, b1 ) < 0 ) {
+ checked_lock( &b0->b_mutex );
+ result = backend_select( b0, op, cp, res, message );
+ checked_unlock( &b0->b_mutex );
+ } else {
+ checked_lock( &b1->b_mutex );
+ result = backend_select( b1, op, cp, res, message );
+ checked_unlock( &b1->b_mutex );
+ }
+
+ rc |= result;
+ if ( result && *cp ) {
+ checked_lock( &tier->t_mutex );
+ tier->t_private = LDAP_CIRCLEQ_LOOP_NEXT(
+ &tier->t_backends, (*cp)->c_backend, b_next );
+ checked_unlock( &tier->t_mutex );
+ return rc;
+ }
+
+ /* Preferred backends deemed unusable, do a round robin from scratch */
+ b = first;
+fallback:
+ do {
+ checked_lock( &b->b_mutex );
+ next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next );
+
+ rc = backend_select( b, op, cp, res, message );
+ checked_unlock( &b->b_mutex );
+
+ if ( rc && *cp ) {
+ /*
+ * Round-robin step:
+ * Rotate the queue to put this backend at the end. The race here
+ * is acceptable.
+ */
+ checked_lock( &tier->t_mutex );
+ tier->t_private = next;
+ checked_unlock( &tier->t_mutex );
+ return rc;
+ }
+
+ b = next;
+ } while ( b != first );
+
+ return rc;
+}
+
+struct lload_tier_type bestof_tier = {
+ .tier_name = "bestof",
+
+ .tier_init = bestof_init,
+ .tier_startup = tier_startup,
+ .tier_update = bestof_update,
+ .tier_reset = tier_reset,
+ .tier_destroy = tier_destroy,
+
+ .tier_oc = BER_BVC("olcBkLloadTierConfig"),
+ .tier_backend_oc = BER_BVC("olcBkLloadBackendConfig"),
+
+ .tier_add_backend = bestof_add_backend,
+ .tier_remove_backend = bestof_remove_backend,
+
+ .tier_select = bestof_select,
+};
diff --git a/servers/lloadd/tier_roundrobin.c b/servers/lloadd/tier_roundrobin.c
new file mode 100644
index 0000000..d299fb9
--- /dev/null
+++ b/servers/lloadd/tier_roundrobin.c
@@ -0,0 +1,136 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include "lload.h"
+
+static LloadTierInit roundrobin_init;
+static LloadTierBackendCb roundrobin_add_backend;
+static LloadTierBackendCb roundrobin_remove_backend;
+static LloadTierSelect roundrobin_select;
+
+struct lload_tier_type roundrobin_tier;
+
+static LloadTier *
+roundrobin_init( void )
+{
+ LloadTier *tier;
+
+ tier = ch_calloc( 1, sizeof(LloadTier) );
+
+ tier->t_type = roundrobin_tier;
+ ldap_pvt_thread_mutex_init( &tier->t_mutex );
+ LDAP_CIRCLEQ_INIT( &tier->t_backends );
+
+ return tier;
+}
+
+static int
+roundrobin_add_backend( LloadTier *tier, LloadBackend *b )
+{
+ assert( b->b_tier == tier );
+ LDAP_CIRCLEQ_INSERT_TAIL( &tier->t_backends, b, b_next );
+ if ( !tier->t_private ) {
+ tier->t_private = b;
+ }
+ tier->t_nbackends++;
+ return LDAP_SUCCESS;
+}
+
+static int
+roundrobin_remove_backend( LloadTier *tier, LloadBackend *b )
+{
+ LloadBackend *next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next );
+
+ assert_locked( &tier->t_mutex );
+ assert_locked( &b->b_mutex );
+
+ assert( b->b_tier == tier );
+
+ LDAP_CIRCLEQ_REMOVE( &tier->t_backends, b, b_next );
+ if ( b == tier->t_private ) {
+ if ( tier->t_nbackends ) {
+ tier->t_private = next;
+ } else {
+ assert( b == next );
+ tier->t_private = NULL;
+ }
+ }
+ tier->t_nbackends--;
+ return LDAP_SUCCESS;
+}
+
+static int
+roundrobin_select(
+ LloadTier *tier,
+ LloadOperation *op,
+ LloadConnection **cp,
+ int *res,
+ char **message )
+{
+ LloadBackend *b, *first, *next;
+ int rc = 0;
+
+ checked_lock( &tier->t_mutex );
+ first = b = tier->t_private;
+ checked_unlock( &tier->t_mutex );
+
+ if ( !first ) return rc;
+
+ do {
+ int result;
+
+ checked_lock( &b->b_mutex );
+ next = LDAP_CIRCLEQ_LOOP_NEXT( &tier->t_backends, b, b_next );
+
+ result = backend_select( b, op, cp, res, message );
+ checked_unlock( &b->b_mutex );
+
+ rc |= result;
+ if ( result && *cp ) {
+ /*
+ * Round-robin step:
+ * Rotate the queue to put this backend at the end. The race here
+ * is acceptable.
+ */
+ checked_lock( &tier->t_mutex );
+ tier->t_private = next;
+ checked_unlock( &tier->t_mutex );
+ return rc;
+ }
+
+ b = next;
+ } while ( b != first );
+
+ return rc;
+}
+
+struct lload_tier_type roundrobin_tier = {
+ .tier_name = "roundrobin",
+
+ .tier_init = roundrobin_init,
+ .tier_startup = tier_startup,
+ .tier_reset = tier_reset,
+ .tier_destroy = tier_destroy,
+
+ .tier_oc = BER_BVC("olcBkLloadTierConfig"),
+ .tier_backend_oc = BER_BVC("olcBkLloadBackendConfig"),
+
+ .tier_add_backend = roundrobin_add_backend,
+ .tier_remove_backend = roundrobin_remove_backend,
+
+ .tier_select = roundrobin_select,
+};
diff --git a/servers/lloadd/tier_weighted.c b/servers/lloadd/tier_weighted.c
new file mode 100644
index 0000000..1255104
--- /dev/null
+++ b/servers/lloadd/tier_weighted.c
@@ -0,0 +1,224 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/string.h>
+
+#include "lload.h"
+#include "lutil.h"
+
+static LloadTierInit weighted_init;
+static LloadTierBackendCb weighted_add_backend;
+static LloadTierBackendCb weighted_remove_backend;
+static LloadTierSelect weighted_select;
+
+struct lload_tier_type weighted_tier;
+
+/*
+ * Linear Congruential Generator - we don't need
+ * high quality randomness, and we don't want to
+ * interfere with anyone else's use of srand().
+ *
+ * The PRNG here cycles thru 941,955 numbers.
+ */
+static float weighted_seed;
+
+static void
+weighted_srand( int seed )
+{
+ weighted_seed = (float)seed / (float)RAND_MAX;
+}
+
+static float
+weighted_rand()
+{
+ float val = 9821.0 * weighted_seed + .211327;
+ weighted_seed = val - (int)val;
+ return weighted_seed;
+}
+
+static void
+weighted_shuffle( LloadBackend **b, int n )
+{
+ int i, j, p;
+ uintptr_t total = 0, r;
+
+ for ( i = 0; i < n; i++ )
+ total += b[i]->b_weight;
+
+ /* all weights are zero, do a straight Fisher-Yates shuffle */
+ if ( !total ) {
+ while ( n ) {
+ LloadBackend *t;
+ i = weighted_rand() * n--;
+ t = b[n];
+ b[n] = b[i];
+ b[i] = t;
+ }
+ return;
+ }
+
+ /* Do a shuffle per RFC2782 Page 4 */
+ p = n;
+ for ( i = 0; i < n - 1; i++ ) {
+ r = weighted_rand() * total;
+ for ( j = 0; j < p; j++ ) {
+ r -= b[j]->b_weight;
+ if ( r <= 0 ) {
+ if ( j ) {
+ LloadBackend *t = b[0];
+ b[0] = b[j];
+ b[j] = t;
+ }
+ total -= b[0]->b_weight;
+ b++;
+ p--;
+ break;
+ }
+ }
+ /* TODO: once we have total == 0, should we jump over to the previous
+ * case? */
+ }
+}
+
+LloadTier *
+weighted_init( void )
+{
+ LloadTier *tier;
+
+ tier = ch_calloc( 1, sizeof(LloadTier) );
+
+ tier->t_type = weighted_tier;
+ ldap_pvt_thread_mutex_init( &tier->t_mutex );
+ LDAP_CIRCLEQ_INIT( &tier->t_backends );
+
+ weighted_srand( rand() );
+
+ return tier;
+}
+
+int
+weighted_add_backend( LloadTier *tier, LloadBackend *to_add )
+{
+ LloadBackend *b;
+ uintptr_t added = 1;
+
+ assert( to_add->b_tier == tier );
+
+ /* This requires us to use LDAP_CIRCLEQ_ENTRY_INIT() every time we have
+ * removed the backend from the list */
+ if ( LDAP_CIRCLEQ_NEXT( to_add, b_next ) ) {
+ added = 0;
+ LDAP_CIRCLEQ_REMOVE( &tier->t_backends, to_add, b_next );
+ }
+
+ /*
+ * Keep it sorted. The only thing RFC 2782 specifies is that weight 0
+ * entries are at the front of the list so they have a chance to be
+ * selected.
+ *
+ * Even with that in mind, there is a problem outlined in the RFC 2782
+ * errata[0] where the ordering affects the likelihood of an entry being
+ * selected with weight 0 entries in the mix - they are an afterthought
+ * into the design after all.
+ *
+ * [0]. https://www.rfc-editor.org/errata/eid2984
+ */
+ LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
+ if ( to_add->b_weight < b->b_weight ) {
+ LDAP_CIRCLEQ_INSERT_BEFORE( &tier->t_backends, b, to_add, b_next );
+ goto done;
+ }
+ }
+ LDAP_CIRCLEQ_INSERT_TAIL( &tier->t_backends, to_add, b_next );
+
+done:
+ tier->t_nbackends += added;
+ return LDAP_SUCCESS;
+}
+
+static int
+weighted_remove_backend( LloadTier *tier, LloadBackend *b )
+{
+ assert_locked( &tier->t_mutex );
+ assert_locked( &b->b_mutex );
+
+ assert( b->b_tier == tier );
+ assert( tier->t_nbackends );
+
+ LDAP_CIRCLEQ_REMOVE( &tier->t_backends, b, b_next );
+ LDAP_CIRCLEQ_ENTRY_INIT( b, b_next );
+ tier->t_nbackends--;
+
+ return LDAP_SUCCESS;
+}
+
+int
+weighted_select(
+ LloadTier *tier,
+ LloadOperation *op,
+ LloadConnection **cp,
+ int *res,
+ char **message )
+{
+ LloadBackend *b, **sorted;
+ int rc = 0, i = 0;
+
+ if ( !tier->t_nbackends ) return rc;
+
+ sorted = ch_malloc( tier->t_nbackends * sizeof(LloadBackend *) );
+
+ LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) {
+ sorted[i++] = b;
+ }
+
+ assert( i == tier->t_nbackends );
+
+ weighted_shuffle( sorted, tier->t_nbackends );
+
+ for ( i = 0; i < tier->t_nbackends; i++ ) {
+ int result;
+
+ checked_lock( &sorted[i]->b_mutex );
+ result = backend_select( sorted[i], op, cp, res, message );
+ checked_unlock( &sorted[i]->b_mutex );
+
+ rc |= result;
+ if ( result && *cp ) {
+ break;
+ }
+ }
+
+ ch_free( sorted );
+ return rc;
+}
+
+struct lload_tier_type weighted_tier = {
+ .tier_name = "weighted",
+
+ .tier_init = weighted_init,
+ .tier_startup = tier_startup,
+ .tier_reset = tier_reset,
+ .tier_destroy = tier_destroy,
+
+ .tier_oc = BER_BVC("olcBkLloadTierConfig"),
+ .tier_backend_oc = BER_BVC("olcBkLloadBackendConfig"),
+
+ .tier_add_backend = weighted_add_backend,
+ .tier_remove_backend = weighted_remove_backend,
+
+ .tier_select = weighted_select,
+};
diff --git a/servers/lloadd/upstream.c b/servers/lloadd/upstream.c
new file mode 100644
index 0000000..2784532
--- /dev/null
+++ b/servers/lloadd/upstream.c
@@ -0,0 +1,1184 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <ac/socket.h>
+#include <ac/errno.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#include "lload.h"
+
+#include "lutil.h"
+#include "lutil_ldap.h"
+
+#ifdef HAVE_CYRUS_SASL
+static const sasl_callback_t client_callbacks[] = {
+#ifdef SASL_CB_GETREALM
+ { SASL_CB_GETREALM, NULL, NULL },
+#endif
+ { SASL_CB_USER, NULL, NULL },
+ { SASL_CB_AUTHNAME, NULL, NULL },
+ { SASL_CB_PASS, NULL, NULL },
+ { SASL_CB_LIST_END, NULL, NULL }
+};
+#endif /* HAVE_CYRUS_SASL */
+
+static void upstream_unlink( LloadConnection *upstream );
+
+int
+lload_upstream_entry_cmp( const void *l, const void *r )
+{
+ return SLAP_PTRCMP( l, r );
+}
+
+static void
+linked_upstream_lost( LloadConnection *client )
+{
+ int gentle = 1;
+
+ CONNECTION_LOCK(client);
+ assert( client->c_restricted >= LLOAD_OP_RESTRICTED_UPSTREAM );
+ assert( client->c_linked_upstream );
+
+ client->c_restricted = LLOAD_OP_NOT_RESTRICTED;
+ client->c_linked_upstream = NULL;
+ CONNECTION_UNLOCK(client);
+ lload_connection_close( client, &gentle );
+}
+
+int
+forward_response( LloadConnection *client, LloadOperation *op, BerElement *ber )
+{
+ BerElement *output;
+ BerValue response, controls = BER_BVNULL;
+ ber_int_t msgid;
+ ber_tag_t tag, response_tag;
+ ber_len_t len;
+
+ CONNECTION_LOCK(client);
+ if ( op->o_client_msgid ) {
+ msgid = op->o_client_msgid;
+ } else {
+ assert( op->o_pin_id );
+ msgid = op->o_saved_msgid;
+ op->o_saved_msgid = 0;
+ }
+ CONNECTION_UNLOCK(client);
+
+ response_tag = ber_skip_element( ber, &response );
+
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LDAP_TAG_CONTROLS ) {
+ ber_skip_element( ber, &controls );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "forward_response: "
+ "%s to client connid=%lu request msgid=%d\n",
+ lload_msgtype2str( response_tag ), op->o_client_connid, msgid );
+
+ checked_lock( &client->c_io_mutex );
+ output = client->c_pendingber;
+ if ( output == NULL && (output = ber_alloc()) == NULL ) {
+ ber_free( ber, 1 );
+ checked_unlock( &client->c_io_mutex );
+ return -1;
+ }
+ client->c_pendingber = output;
+
+ ber_printf( output, "t{titOtO}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, msgid,
+ response_tag, &response,
+ LDAP_TAG_CONTROLS, BER_BV_OPTIONAL( &controls ) );
+
+ checked_unlock( &client->c_io_mutex );
+
+ ber_free( ber, 1 );
+ connection_write_cb( -1, 0, client );
+ return 0;
+}
+
+int
+forward_final_response(
+ LloadConnection *client,
+ LloadOperation *op,
+ BerElement *ber )
+{
+ int rc;
+
+ Debug( LDAP_DEBUG_STATS, "forward_final_response: "
+ "connid=%lu msgid=%d finishing up with a request for "
+ "client connid=%lu\n",
+ op->o_upstream_connid, op->o_upstream_msgid, op->o_client_connid );
+
+ rc = forward_response( client, op, ber );
+
+ op->o_res = LLOAD_OP_COMPLETED;
+ if ( !op->o_pin_id ) {
+ OPERATION_UNLINK(op);
+ }
+
+ return rc;
+}
+
+static int
+handle_unsolicited( LloadConnection *c, BerElement *ber )
+{
+ CONNECTION_ASSERT_LOCKED(c);
+
+ assert( c->c_state != LLOAD_C_INVALID );
+ if ( c->c_state == LLOAD_C_DYING ) {
+ CONNECTION_UNLOCK(c);
+ goto out;
+ }
+ c->c_state = LLOAD_C_CLOSING;
+
+ Debug( LDAP_DEBUG_STATS, "handle_unsolicited: "
+ "teardown for upstream connection connid=%lu\n",
+ c->c_connid );
+
+ CONNECTION_DESTROY(c);
+
+out:
+ ber_free( ber, 1 );
+ return -1;
+}
+
+/*
+ * Pull c->c_currentber from the connection and try to look up the operation on
+ * the upstream.
+ *
+ * If it's a notice of disconnection, we won't find it and need to tear down
+ * the connection and tell the clients, if we can't find the operation, ignore
+ * the message (either client already disconnected/abandoned it or the upstream
+ * is pulling our leg).
+ *
+ * Some responses need special handling:
+ * - Bind response
+ * - VC response where the client requested a Bind (both need to update the
+ * client's bind status)
+ * - search entries/referrals and intermediate responses (will not trigger
+ * operation to be removed)
+ *
+ * If the worker pool is overloaded, we might be called directly from
+ * the read callback, at that point, the connection hasn't been muted.
+ *
+ * TODO: when the client already has data pending on write, we should mute the
+ * upstream.
+ * - should record the BerElement on the Op and the Op on the client
+ *
+ * The following hold on entering any of the handlers:
+ * - op->o_upstream_refcnt > 0
+ * - op->o_upstream->c_refcnt > 0
+ * - op->o_client->c_refcnt > 0
+ */
+static int
+handle_one_response( LloadConnection *c )
+{
+ BerElement *ber;
+ LloadOperation *op = NULL, needle = { .o_upstream_connid = c->c_connid };
+ LloadOperationHandler handler = NULL;
+ ber_tag_t tag;
+ ber_len_t len;
+ int rc = LDAP_SUCCESS;
+
+ ber = c->c_currentber;
+ c->c_currentber = NULL;
+
+ tag = ber_get_int( ber, &needle.o_upstream_msgid );
+ if ( tag != LDAP_TAG_MSGID ) {
+ rc = -1;
+ ber_free( ber, 1 );
+ goto fail;
+ }
+
+ CONNECTION_LOCK(c);
+ if ( needle.o_upstream_msgid == 0 ) {
+ return handle_unsolicited( c, ber );
+ } else if ( !( op = ldap_tavl_find(
+ c->c_ops, &needle, operation_upstream_cmp ) ) ) {
+ /* Already abandoned, do nothing */
+ CONNECTION_UNLOCK(c);
+ ber_free( ber, 1 );
+ return rc;
+ /*
+ } else if ( op->o_response_pending ) {
+ c->c_pendingop = op;
+ event_del( c->c_read_event );
+ */
+ } else {
+ CONNECTION_UNLOCK(c);
+ /*
+ op->o_response_pending = ber;
+ */
+
+ tag = ber_peek_tag( ber, &len );
+ switch ( tag ) {
+ case LDAP_RES_SEARCH_ENTRY:
+ case LDAP_RES_SEARCH_REFERENCE:
+ case LDAP_RES_INTERMEDIATE:
+ handler = forward_response;
+ break;
+ case LDAP_RES_BIND:
+ handler = handle_bind_response;
+ break;
+ case LDAP_RES_EXTENDED:
+ if ( op->o_tag == LDAP_REQ_BIND ) {
+#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
+ if ( lload_features & LLOAD_FEATURE_VC ) {
+ handler = handle_vc_bind_response;
+ } else
+#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
+ {
+ handler = handle_whoami_response;
+ }
+ }
+ break;
+ }
+ if ( !handler ) {
+ handler = forward_final_response;
+ }
+ }
+ if ( op ) {
+ struct timeval tv, tvdiff;
+ uintptr_t diff;
+
+ gettimeofday( &tv, NULL );
+ if ( !timerisset( &op->o_last_response ) ) {
+ LloadBackend *b = c->c_backend;
+
+ timersub( &tv, &op->o_start, &tvdiff );
+ diff = 1000000 * tvdiff.tv_sec + tvdiff.tv_usec;
+
+ __atomic_add_fetch( &b->b_operation_count, 1, __ATOMIC_RELAXED );
+ __atomic_add_fetch( &b->b_operation_time, diff, __ATOMIC_RELAXED );
+ }
+ op->o_last_response = tv;
+
+ Debug( LDAP_DEBUG_STATS2, "handle_one_response: "
+ "upstream connid=%lu, processing response for "
+ "client connid=%lu, msgid=%d\n",
+ c->c_connid, op->o_client_connid, op->o_client_msgid );
+ } else {
+ tag = ber_peek_tag( ber, &len );
+ Debug( LDAP_DEBUG_STATS2, "handle_one_response: "
+ "upstream connid=%lu, %s, msgid=%d not for a pending "
+ "operation\n",
+ c->c_connid, lload_msgtype2str( tag ),
+ needle.o_upstream_msgid );
+ }
+
+ if ( handler ) {
+ LloadConnection *client;
+
+ checked_lock( &op->o_link_mutex );
+ client = op->o_client;
+ checked_unlock( &op->o_link_mutex );
+ if ( client && IS_ALIVE( client, c_live ) ) {
+ rc = handler( client, op, ber );
+ } else {
+ ber_free( ber, 1 );
+ }
+ } else {
+ assert(0);
+ ber_free( ber, 1 );
+ }
+
+fail:
+ if ( rc ) {
+ Debug( LDAP_DEBUG_STATS, "handle_one_response: "
+ "error on processing a response (%s) on upstream connection "
+ "connid=%ld, tag=%lx\n",
+ lload_msgtype2str( tag ), c->c_connid, tag );
+ CONNECTION_LOCK_DESTROY(c);
+ }
+ return rc;
+}
+
+#ifdef HAVE_CYRUS_SASL
+static int
+sasl_bind_step( LloadConnection *c, BerValue *scred, BerValue *ccred )
+{
+ LloadBackend *b = c->c_backend;
+ sasl_conn_t *ctx = c->c_sasl_authctx;
+ sasl_interact_t *prompts = NULL;
+ unsigned credlen;
+ int rc = -1;
+
+ if ( !ctx ) {
+ const char *mech = NULL;
+#ifdef HAVE_TLS
+ void *ssl;
+#endif /* HAVE_TLS */
+
+ if ( sasl_client_new( "ldap", b->b_host, NULL, NULL, client_callbacks,
+ 0, &ctx ) != SASL_OK ) {
+ goto done;
+ }
+ c->c_sasl_authctx = ctx;
+
+ assert( c->c_sasl_defaults == NULL );
+ c->c_sasl_defaults =
+ lutil_sasl_defaults( NULL, bindconf.sb_saslmech.bv_val,
+ bindconf.sb_realm.bv_val, bindconf.sb_authcId.bv_val,
+ bindconf.sb_cred.bv_val, bindconf.sb_authzId.bv_val );
+
+#ifdef HAVE_TLS
+ /* Check for TLS */
+ ssl = ldap_pvt_tls_sb_ctx( c->c_sb );
+ if ( ssl ) {
+ struct berval authid = BER_BVNULL;
+ ber_len_t ssf;
+
+ ssf = ldap_pvt_tls_get_strength( ssl );
+ (void)ldap_pvt_tls_get_my_dn( ssl, &authid, NULL, 0 );
+
+ sasl_setprop( ctx, SASL_SSF_EXTERNAL, &ssf );
+ sasl_setprop( ctx, SASL_AUTH_EXTERNAL, authid.bv_val );
+ ch_free( authid.bv_val );
+#ifdef SASL_CHANNEL_BINDING /* 2.1.25+ */
+ {
+ char cbinding[64];
+ struct berval cbv = { sizeof(cbinding), cbinding };
+ if ( ldap_pvt_tls_get_unique( ssl, &cbv, 0 ) ) {
+ sasl_channel_binding_t *cb =
+ ch_malloc( sizeof(*cb) + cbv.bv_len );
+ void *cb_data;
+ cb->name = "ldap";
+ cb->critical = 0;
+ cb->len = cbv.bv_len;
+ cb->data = cb_data = cb + 1;
+ memcpy( cb_data, cbv.bv_val, cbv.bv_len );
+ sasl_setprop( ctx, SASL_CHANNEL_BINDING, cb );
+ c->c_sasl_cbinding = cb;
+ }
+ }
+#endif
+ }
+#endif
+
+#if !defined(_WIN32)
+ /* Check for local */
+ if ( b->b_proto == LDAP_PROTO_IPC ) {
+ char authid[sizeof( "gidNumber=4294967295+uidNumber=4294967295,"
+ "cn=peercred,cn=external,cn=auth" )];
+ int ssf = LDAP_PVT_SASL_LOCAL_SSF;
+
+ sprintf( authid,
+ "gidNumber=%u+uidNumber=%u,"
+ "cn=peercred,cn=external,cn=auth",
+ getegid(), geteuid() );
+ sasl_setprop( ctx, SASL_SSF_EXTERNAL, &ssf );
+ sasl_setprop( ctx, SASL_AUTH_EXTERNAL, authid );
+ }
+#endif
+
+ do {
+ rc = sasl_client_start( ctx, bindconf.sb_saslmech.bv_val,
+ &prompts,
+ (const char **)&ccred->bv_val, &credlen,
+ &mech );
+
+ if ( rc == SASL_INTERACT ) {
+ if ( lutil_sasl_interact( NULL, LDAP_SASL_QUIET,
+ c->c_sasl_defaults, prompts ) ) {
+ break;
+ }
+ }
+ } while ( rc == SASL_INTERACT );
+
+ ber_str2bv( mech, 0, 0, &c->c_sasl_bind_mech );
+ } else {
+ assert( c->c_sasl_defaults );
+
+ do {
+ rc = sasl_client_step( ctx,
+ (scred == NULL) ? NULL : scred->bv_val,
+ (scred == NULL) ? 0 : scred->bv_len,
+ &prompts,
+ (const char **)&ccred->bv_val, &credlen);
+
+ if ( rc == SASL_INTERACT ) {
+ if ( lutil_sasl_interact( NULL, LDAP_SASL_QUIET,
+ c->c_sasl_defaults, prompts ) ) {
+ break;
+ }
+ }
+ } while ( rc == SASL_INTERACT );
+ }
+
+ if ( rc == SASL_OK ) {
+ sasl_ssf_t *ssf;
+ rc = sasl_getprop( ctx, SASL_SSF, (const void **)(char *)&ssf );
+ if ( rc == SASL_OK && ssf && *ssf ) {
+ Debug( LDAP_DEBUG_CONNS, "sasl_bind_step: "
+ "connid=%lu mech=%s setting up a new SASL security layer\n",
+ c->c_connid, c->c_sasl_bind_mech.bv_val );
+ ldap_pvt_sasl_install( c->c_sb, ctx );
+ }
+ }
+ ccred->bv_len = credlen;
+
+done:
+ Debug( LDAP_DEBUG_TRACE, "sasl_bind_step: "
+ "connid=%lu next step for SASL bind mech=%s rc=%d\n",
+ c->c_connid, c->c_sasl_bind_mech.bv_val, rc );
+ return rc;
+}
+#endif /* HAVE_CYRUS_SASL */
+
+int
+upstream_bind_cb( LloadConnection *c )
+{
+ BerElement *ber = c->c_currentber;
+ LloadBackend *b = c->c_backend;
+ BerValue matcheddn, message;
+ ber_tag_t tag;
+ ber_int_t msgid, result;
+
+ c->c_currentber = NULL;
+
+ if ( ber_scanf( ber, "it", &msgid, &tag ) == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_bind_cb: "
+ "protocol violation from server\n" );
+ goto fail;
+ }
+
+ if ( msgid != ( c->c_next_msgid - 1 ) || tag != LDAP_RES_BIND ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_bind_cb: "
+ "unexpected %s from server, msgid=%d\n",
+ lload_msgtype2str( tag ), msgid );
+ goto fail;
+ }
+
+ if ( ber_scanf( ber, "{emm" /* "}" */, &result, &matcheddn, &message ) ==
+ LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_bind_cb: "
+ "response does not conform with a bind response\n" );
+ goto fail;
+ }
+
+ switch ( result ) {
+ case LDAP_SUCCESS:
+#ifdef HAVE_CYRUS_SASL
+ case LDAP_SASL_BIND_IN_PROGRESS:
+ if ( !BER_BVISNULL( &c->c_sasl_bind_mech ) ) {
+ BerValue scred = BER_BVNULL, ccred;
+ ber_len_t len;
+ int rc;
+
+ if ( ber_peek_tag( ber, &len ) == LDAP_TAG_SASL_RES_CREDS &&
+ ber_scanf( ber, "m", &scred ) == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_bind_cb: "
+ "sasl bind response malformed\n" );
+ goto fail;
+ }
+
+ rc = sasl_bind_step( c, &scred, &ccred );
+ if ( rc != SASL_OK &&
+ ( rc != SASL_CONTINUE || result == LDAP_SUCCESS ) ) {
+ goto fail;
+ }
+
+ if ( result == LDAP_SASL_BIND_IN_PROGRESS ) {
+ BerElement *outber;
+
+ checked_lock( &c->c_io_mutex );
+ outber = c->c_pendingber;
+ if ( outber == NULL && (outber = ber_alloc()) == NULL ) {
+ checked_unlock( &c->c_io_mutex );
+ goto fail;
+ }
+ c->c_pendingber = outber;
+
+ msgid = c->c_next_msgid++;
+ ber_printf( outber, "{it{iOt{OON}N}}",
+ msgid, LDAP_REQ_BIND, LDAP_VERSION3,
+ &bindconf.sb_binddn, LDAP_AUTH_SASL,
+ &c->c_sasl_bind_mech, BER_BV_OPTIONAL( &ccred ) );
+ checked_unlock( &c->c_io_mutex );
+
+ connection_write_cb( -1, 0, c );
+
+ if ( rc == SASL_OK ) {
+ BER_BVZERO( &c->c_sasl_bind_mech );
+ }
+ break;
+ }
+ }
+ if ( result == LDAP_SASL_BIND_IN_PROGRESS ) {
+ goto fail;
+ }
+#endif /* HAVE_CYRUS_SASL */
+ CONNECTION_LOCK(c);
+ c->c_pdu_cb = handle_one_response;
+ c->c_state = LLOAD_C_READY;
+ c->c_type = LLOAD_C_OPEN;
+ c->c_read_timeout = NULL;
+ Debug( LDAP_DEBUG_CONNS, "upstream_bind_cb: "
+ "connection connid=%lu for backend server '%s' is ready "
+ "for use\n",
+ c->c_connid, b->b_name.bv_val );
+ CONNECTION_UNLOCK(c);
+ checked_lock( &b->b_mutex );
+ LDAP_CIRCLEQ_REMOVE( &b->b_preparing, c, c_next );
+ b->b_active++;
+ b->b_opening--;
+ b->b_failed = 0;
+ if ( b->b_last_conn ) {
+ LDAP_CIRCLEQ_INSERT_AFTER(
+ &b->b_conns, b->b_last_conn, c, c_next );
+ } else {
+ LDAP_CIRCLEQ_INSERT_HEAD( &b->b_conns, c, c_next );
+ }
+ b->b_last_conn = c;
+ backend_retry( b );
+ checked_unlock( &b->b_mutex );
+ break;
+ default:
+ Debug( LDAP_DEBUG_ANY, "upstream_bind_cb: "
+ "upstream bind failed, rc=%d, message='%s'\n",
+ result, message.bv_val );
+ goto fail;
+ }
+
+ checked_lock( &c->c_io_mutex );
+ c->c_io_state &= ~LLOAD_C_READ_HANDOVER;
+ checked_unlock( &c->c_io_mutex );
+ event_add( c->c_read_event, c->c_read_timeout );
+ ber_free( ber, 1 );
+ return -1;
+
+fail:
+ CONNECTION_LOCK_DESTROY(c);
+ ber_free( ber, 1 );
+ return -1;
+}
+
+void *
+upstream_bind( void *ctx, void *arg )
+{
+ LloadConnection *c = arg;
+ BerElement *ber;
+ ber_int_t msgid;
+
+ /* A reference was passed on to us */
+ assert( IS_ALIVE( c, c_refcnt ) );
+
+ if ( !IS_ALIVE( c, c_live ) ) {
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ return NULL;
+ }
+
+ CONNECTION_LOCK(c);
+ assert( !event_pending( c->c_read_event, EV_READ, NULL ) );
+ c->c_pdu_cb = upstream_bind_cb;
+ CONNECTION_UNLOCK(c);
+
+ checked_lock( &c->c_io_mutex );
+ ber = c->c_pendingber;
+ if ( ber == NULL && (ber = ber_alloc()) == NULL ) {
+ goto fail;
+ }
+ c->c_pendingber = ber;
+ msgid = c->c_next_msgid++;
+
+ if ( bindconf.sb_method == LDAP_AUTH_SIMPLE ) {
+ /* simple bind */
+ ber_printf( ber, "{it{iOtON}}",
+ msgid, LDAP_REQ_BIND, LDAP_VERSION3,
+ &bindconf.sb_binddn, LDAP_AUTH_SIMPLE,
+ &bindconf.sb_cred );
+
+#ifdef HAVE_CYRUS_SASL
+ } else {
+ BerValue cred;
+ int rc;
+
+ rc = sasl_bind_step( c, NULL, &cred );
+ if ( rc != SASL_OK && rc != SASL_CONTINUE ) {
+ goto fail;
+ }
+
+ ber_printf( ber, "{it{iOt{OON}N}}",
+ msgid, LDAP_REQ_BIND, LDAP_VERSION3,
+ &bindconf.sb_binddn, LDAP_AUTH_SASL,
+ &c->c_sasl_bind_mech, BER_BV_OPTIONAL( &cred ) );
+
+ if ( rc == SASL_OK ) {
+ BER_BVZERO( &c->c_sasl_bind_mech );
+ }
+#endif /* HAVE_CYRUS_SASL */
+ }
+ /* TODO: can we be paused at this point? Then we'd have to move this line
+ * after connection_write_cb */
+ c->c_io_state &= ~LLOAD_C_READ_HANDOVER;
+ checked_unlock( &c->c_io_mutex );
+
+ connection_write_cb( -1, 0, c );
+
+ CONNECTION_LOCK(c);
+ c->c_read_timeout = lload_timeout_net;
+ event_add( c->c_read_event, c->c_read_timeout );
+ CONNECTION_UNLOCK(c);
+
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ return NULL;
+
+fail:
+ checked_unlock( &c->c_io_mutex );
+ CONNECTION_LOCK_DESTROY(c);
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ return NULL;
+}
+
+/*
+ * The backend is already locked when entering the function.
+ */
+static int
+upstream_finish( LloadConnection *c )
+{
+ LloadBackend *b = c->c_backend;
+ int is_bindconn = 0;
+
+ assert_locked( &b->b_mutex );
+ CONNECTION_ASSERT_LOCKED(c);
+ assert( c->c_live );
+ c->c_pdu_cb = handle_one_response;
+
+ /* Unless we are configured to use the VC exop, consider allocating the
+ * connection into the bind conn pool. Start off by allocating one for
+ * general use, then one for binds, then we start filling up the general
+ * connection pool, finally the bind pool */
+ if (
+#ifdef LDAP_API_FEATURE_VERIFY_CREDENTIALS
+ !(lload_features & LLOAD_FEATURE_VC) &&
+#endif /* LDAP_API_FEATURE_VERIFY_CREDENTIALS */
+ b->b_active && b->b_numbindconns ) {
+ if ( !b->b_bindavail ) {
+ is_bindconn = 1;
+ } else if ( b->b_active >= b->b_numconns &&
+ b->b_bindavail < b->b_numbindconns ) {
+ is_bindconn = 1;
+ }
+ }
+
+ if ( is_bindconn ) {
+ LDAP_CIRCLEQ_REMOVE( &b->b_preparing, c, c_next );
+ c->c_state = LLOAD_C_READY;
+ c->c_type = LLOAD_C_BIND;
+ b->b_bindavail++;
+ b->b_opening--;
+ b->b_failed = 0;
+ if ( b->b_last_bindconn ) {
+ LDAP_CIRCLEQ_INSERT_AFTER(
+ &b->b_bindconns, b->b_last_bindconn, c, c_next );
+ } else {
+ LDAP_CIRCLEQ_INSERT_HEAD( &b->b_bindconns, c, c_next );
+ }
+ b->b_last_bindconn = c;
+ } else if ( bindconf.sb_method == LDAP_AUTH_NONE ) {
+ LDAP_CIRCLEQ_REMOVE( &b->b_preparing, c, c_next );
+ c->c_state = LLOAD_C_READY;
+ c->c_type = LLOAD_C_OPEN;
+ b->b_active++;
+ b->b_opening--;
+ b->b_failed = 0;
+ if ( b->b_last_conn ) {
+ LDAP_CIRCLEQ_INSERT_AFTER( &b->b_conns, b->b_last_conn, c, c_next );
+ } else {
+ LDAP_CIRCLEQ_INSERT_HEAD( &b->b_conns, c, c_next );
+ }
+ b->b_last_conn = c;
+ } else {
+ if ( ldap_pvt_thread_pool_submit(
+ &connection_pool, upstream_bind, c ) ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_finish: "
+ "failed to set up a bind callback for connid=%lu\n",
+ c->c_connid );
+ return -1;
+ }
+ /* keep a reference for upstream_bind */
+ acquire_ref( &c->c_refcnt );
+
+ Debug( LDAP_DEBUG_CONNS, "upstream_finish: "
+ "scheduled a bind callback for connid=%lu\n",
+ c->c_connid );
+ return LDAP_SUCCESS;
+ }
+ event_add( c->c_read_event, c->c_read_timeout );
+
+ Debug( LDAP_DEBUG_CONNS, "upstream_finish: "
+ "%sconnection connid=%lu for backend server '%s' is ready for "
+ "use\n",
+ is_bindconn ? "bind " : "", c->c_connid, b->b_name.bv_val );
+
+ backend_retry( b );
+ return LDAP_SUCCESS;
+}
+
+#ifdef HAVE_TLS
+static void
+upstream_tls_handshake_cb( evutil_socket_t s, short what, void *arg )
+{
+ LloadConnection *c = arg;
+ LloadBackend *b;
+ epoch_t epoch;
+ int rc = LDAP_SUCCESS;
+
+ CONNECTION_LOCK(c);
+ if ( what & EV_TIMEOUT ) {
+ Debug( LDAP_DEBUG_CONNS, "upstream_tls_handshake_cb: "
+ "connid=%lu, timeout reached, destroying\n",
+ c->c_connid );
+ goto fail;
+ }
+ b = c->c_backend;
+
+ rc = ldap_pvt_tls_connect( lload_tls_backend_ld, c->c_sb, b->b_host );
+ 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.
+ */
+ 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 );
+ event_assign( c->c_write_event, base, c->c_fd, EV_WRITE,
+ connection_write_cb, c );
+ Debug( LDAP_DEBUG_CONNS, "upstream_tls_handshake_cb: "
+ "connid=%lu finished\n",
+ c->c_connid );
+ c->c_is_tls = LLOAD_TLS_ESTABLISHED;
+
+ CONNECTION_UNLOCK(c);
+ checked_lock( &b->b_mutex );
+ CONNECTION_LOCK(c);
+
+ rc = upstream_finish( c );
+ checked_unlock( &b->b_mutex );
+
+ if ( rc ) {
+ goto fail;
+ }
+ } else if ( ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_NEEDS_WRITE, NULL ) ) {
+ event_add( c->c_write_event, lload_write_timeout );
+ Debug( LDAP_DEBUG_CONNS, "upstream_tls_handshake_cb: "
+ "connid=%lu need write rc=%d\n",
+ c->c_connid, rc );
+ }
+ CONNECTION_UNLOCK(c);
+ return;
+
+fail:
+ Debug( LDAP_DEBUG_CONNS, "upstream_tls_handshake_cb: "
+ "connid=%lu failed rc=%d\n",
+ c->c_connid, rc );
+
+ assert( c->c_ops == NULL );
+ epoch = epoch_join();
+ CONNECTION_DESTROY(c);
+ epoch_leave( epoch );
+}
+
+static int
+upstream_starttls( LloadConnection *c )
+{
+ BerValue matcheddn, message, responseOid,
+ startTLSOid = BER_BVC(LDAP_EXOP_START_TLS);
+ BerElement *ber = c->c_currentber;
+ struct event_base *base;
+ ber_int_t msgid, result;
+ ber_tag_t tag;
+
+ c->c_currentber = NULL;
+ CONNECTION_LOCK(c);
+
+ if ( ber_scanf( ber, "it", &msgid, &tag ) == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_starttls: "
+ "protocol violation from server\n" );
+ goto fail;
+ }
+
+ if ( msgid != ( c->c_next_msgid - 1 ) || tag != LDAP_RES_EXTENDED ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_starttls: "
+ "unexpected %s from server, msgid=%d\n",
+ lload_msgtype2str( tag ), msgid );
+ goto fail;
+ }
+
+ if ( ber_scanf( ber, "{emm}", &result, &matcheddn, &message ) ==
+ LBER_ERROR ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_starttls: "
+ "protocol violation on StartTLS response\n" );
+ goto fail;
+ }
+
+ if ( (tag = ber_get_tag( ber )) != LBER_DEFAULT ) {
+ if ( tag != LDAP_TAG_EXOP_RES_OID ||
+ ber_scanf( ber, "{m}", &responseOid ) == LBER_DEFAULT ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_starttls: "
+ "protocol violation on StartTLS response\n" );
+ goto fail;
+ }
+
+ if ( ber_bvcmp( &responseOid, &startTLSOid ) ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_starttls: "
+ "oid=%s not a StartTLS response\n",
+ responseOid.bv_val );
+ goto fail;
+ }
+ }
+
+ if ( result != LDAP_SUCCESS ) {
+ LloadBackend *b = c->c_backend;
+ int rc;
+
+ Debug( LDAP_DEBUG_STATS, "upstream_starttls: "
+ "server doesn't support StartTLS rc=%d message='%s'%s\n",
+ result, message.bv_val,
+ (c->c_is_tls == LLOAD_STARTTLS_OPTIONAL) ? ", ignored" : "" );
+ if ( c->c_is_tls != LLOAD_STARTTLS_OPTIONAL ) {
+ goto fail;
+ }
+ c->c_is_tls = LLOAD_CLEARTEXT;
+
+ CONNECTION_UNLOCK(c);
+ checked_lock( &b->b_mutex );
+ CONNECTION_LOCK(c);
+
+ rc = upstream_finish( c );
+ checked_unlock( &b->b_mutex );
+
+ if ( rc ) {
+ goto fail;
+ }
+
+ ber_free( ber, 1 );
+ CONNECTION_UNLOCK(c);
+
+ checked_lock( &c->c_io_mutex );
+ c->c_io_state &= ~LLOAD_C_READ_HANDOVER;
+ checked_unlock( &c->c_io_mutex );
+
+ /* Do not keep handle_pdus running, we have adjusted c_read_event as we
+ * need it. */
+ return -1;
+ }
+
+ base = event_get_base( c->c_read_event );
+
+ c->c_io_state &= ~LLOAD_C_READ_HANDOVER;
+ event_del( c->c_read_event );
+ event_del( c->c_write_event );
+
+ c->c_read_timeout = lload_timeout_net;
+ event_assign( c->c_read_event, base, c->c_fd, EV_READ|EV_PERSIST,
+ upstream_tls_handshake_cb, c );
+ event_assign( c->c_write_event, base, c->c_fd, EV_WRITE,
+ upstream_tls_handshake_cb, c );
+
+ event_add( c->c_read_event, c->c_read_timeout );
+ event_add( c->c_write_event, lload_write_timeout );
+
+ CONNECTION_UNLOCK(c);
+
+ ber_free( ber, 1 );
+ return -1;
+
+fail:
+ ber_free( ber, 1 );
+ CONNECTION_DESTROY(c);
+ return -1;
+}
+#endif /* HAVE_TLS */
+
+/*
+ * We must already hold b->b_mutex when called.
+ */
+LloadConnection *
+upstream_init( ber_socket_t s, LloadBackend *b )
+{
+ LloadConnection *c;
+ struct event_base *base = lload_get_base( s );
+ struct event *event;
+ int flags;
+
+ assert( b != NULL );
+
+ flags = (b->b_proto == LDAP_PROTO_IPC) ? CONN_IS_IPC : 0;
+ if ( (c = lload_connection_init( s, b->b_host, flags )) == NULL ) {
+ return NULL;
+ }
+
+ CONNECTION_LOCK(c);
+ c->c_backend = b;
+#ifdef HAVE_TLS
+ c->c_is_tls = b->b_tls;
+#endif
+ c->c_pdu_cb = handle_one_response;
+
+ LDAP_CIRCLEQ_INSERT_HEAD( &b->b_preparing, c, c_next );
+ c->c_type = LLOAD_C_PREPARING;
+
+ {
+ ber_len_t max = sockbuf_max_incoming_upstream;
+ ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &max );
+ }
+
+ event = event_new( base, s, EV_READ|EV_PERSIST, connection_read_cb, c );
+ if ( !event ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_init: "
+ "Read event could not be allocated\n" );
+ goto fail;
+ }
+ c->c_read_event = event;
+
+ event = event_new( base, s, EV_WRITE, connection_write_cb, c );
+ if ( !event ) {
+ Debug( LDAP_DEBUG_ANY, "upstream_init: "
+ "Write event could not be allocated\n" );
+ goto fail;
+ }
+ /* We only add the write event when we have data pending */
+ c->c_write_event = event;
+
+#ifdef BALANCER_MODULE
+ if ( b->b_monitor ) {
+ acquire_ref( &c->c_refcnt );
+ CONNECTION_UNLOCK(c);
+ checked_unlock( &b->b_mutex );
+ if ( lload_monitor_conn_entry_create( c, b->b_monitor ) ) {
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ checked_lock( &b->b_mutex );
+ CONNECTION_LOCK(c);
+ goto fail;
+ }
+ checked_lock( &b->b_mutex );
+ CONNECTION_LOCK(c);
+ RELEASE_REF( c, c_refcnt, c->c_destroy );
+ }
+#endif /* BALANCER_MODULE */
+
+ c->c_destroy = upstream_destroy;
+ c->c_unlink = upstream_unlink;
+
+#ifdef HAVE_TLS
+ if ( c->c_is_tls == LLOAD_CLEARTEXT ) {
+#endif /* HAVE_TLS */
+ if ( upstream_finish( c ) ) {
+ goto fail;
+ }
+#ifdef HAVE_TLS
+ } else if ( c->c_is_tls == LLOAD_LDAPS ) {
+ event_assign( c->c_read_event, base, s, EV_READ|EV_PERSIST,
+ upstream_tls_handshake_cb, c );
+ event_add( c->c_read_event, c->c_read_timeout );
+ event_assign( c->c_write_event, base, s, EV_WRITE,
+ upstream_tls_handshake_cb, c );
+ event_add( c->c_write_event, lload_write_timeout );
+ } else if ( c->c_is_tls == LLOAD_STARTTLS ||
+ c->c_is_tls == LLOAD_STARTTLS_OPTIONAL ) {
+ BerElement *output;
+
+ checked_lock( &c->c_io_mutex );
+ if ( (output = c->c_pendingber = ber_alloc()) == NULL ) {
+ checked_unlock( &c->c_io_mutex );
+ goto fail;
+ }
+ ber_printf( output, "t{tit{ts}}", LDAP_TAG_MESSAGE,
+ LDAP_TAG_MSGID, c->c_next_msgid++,
+ LDAP_REQ_EXTENDED,
+ LDAP_TAG_EXOP_REQ_OID, LDAP_EXOP_START_TLS );
+ checked_unlock( &c->c_io_mutex );
+
+ c->c_pdu_cb = upstream_starttls;
+ CONNECTION_UNLOCK(c);
+ connection_write_cb( s, 0, c );
+ CONNECTION_LOCK(c);
+ if ( IS_ALIVE( c, c_live ) ) {
+ event_add( c->c_read_event, c->c_read_timeout );
+ }
+ }
+#endif /* HAVE_TLS */
+ 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_del( c->c_write_event );
+ event_free( c->c_write_event );
+ }
+ if ( c->c_read_event ) {
+ event_del( c->c_read_event );
+ event_free( c->c_read_event );
+ }
+
+ c->c_state = LLOAD_C_INVALID;
+ c->c_live--;
+ c->c_refcnt--;
+ connection_destroy( c );
+
+ return NULL;
+}
+
+static void
+upstream_unlink( LloadConnection *c )
+{
+ LloadBackend *b = c->c_backend;
+ struct event *read_event, *write_event;
+ TAvlnode *root, *linked_root;
+ long freed, executing;
+
+ Debug( LDAP_DEBUG_CONNS, "upstream_unlink: "
+ "removing upstream connid=%lu\n",
+ c->c_connid );
+ CONNECTION_ASSERT_LOCKED(c);
+
+ assert( c->c_state != LLOAD_C_INVALID );
+ assert( c->c_state != LLOAD_C_DYING );
+
+ c->c_state = LLOAD_C_DYING;
+
+ read_event = c->c_read_event;
+ write_event = c->c_write_event;
+
+ root = c->c_ops;
+ c->c_ops = NULL;
+ executing = c->c_n_ops_executing;
+ c->c_n_ops_executing = 0;
+
+ linked_root = c->c_linked;
+ c->c_linked = NULL;
+
+ CONNECTION_UNLOCK(c);
+
+ freed = ldap_tavl_free( root, (AVL_FREE)operation_lost_upstream );
+ assert( freed == executing );
+
+ ldap_tavl_free( linked_root, (AVL_FREE)linked_upstream_lost );
+
+ /*
+ * Avoid a deadlock:
+ * event_del will block if the event is currently executing its callback,
+ * that callback might be waiting to lock c->c_mutex
+ */
+ if ( read_event ) {
+ event_del( read_event );
+ }
+
+ if ( write_event ) {
+ event_del( write_event );
+ }
+
+ checked_lock( &b->b_mutex );
+ if ( c->c_type == LLOAD_C_PREPARING ) {
+ LDAP_CIRCLEQ_REMOVE( &b->b_preparing, c, c_next );
+ b->b_opening--;
+ b->b_failed++;
+ } else if ( c->c_type == LLOAD_C_BIND ) {
+ if ( c == b->b_last_bindconn ) {
+ LloadConnection *prev =
+ LDAP_CIRCLEQ_LOOP_PREV( &b->b_bindconns, c, c_next );
+ if ( prev == c ) {
+ b->b_last_bindconn = NULL;
+ } else {
+ b->b_last_bindconn = prev;
+ }
+ }
+ LDAP_CIRCLEQ_REMOVE( &b->b_bindconns, c, c_next );
+ b->b_bindavail--;
+ } else {
+ if ( c == b->b_last_conn ) {
+ LloadConnection *prev =
+ LDAP_CIRCLEQ_LOOP_PREV( &b->b_conns, c, c_next );
+ if ( prev == c ) {
+ b->b_last_conn = NULL;
+ } else {
+ b->b_last_conn = prev;
+ }
+ }
+ LDAP_CIRCLEQ_REMOVE( &b->b_conns, c, c_next );
+ b->b_active--;
+ }
+ b->b_n_ops_executing -= executing;
+ backend_retry( b );
+ checked_unlock( &b->b_mutex );
+
+ CONNECTION_LOCK(c);
+ CONNECTION_ASSERT_LOCKED(c);
+}
+
+void
+upstream_destroy( LloadConnection *c )
+{
+ Debug( LDAP_DEBUG_CONNS, "upstream_destroy: "
+ "freeing connection connid=%lu\n",
+ c->c_connid );
+
+ CONNECTION_LOCK(c);
+ assert( c->c_state == LLOAD_C_DYING );
+
+#ifdef BALANCER_MODULE
+ /*
+ * Can't do this in upstream_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;
+ }
+
+ if ( c->c_type != LLOAD_C_BIND ) {
+ BER_BVZERO( &c->c_sasl_bind_mech );
+ }
+ connection_destroy( c );
+}
diff --git a/servers/lloadd/value.c b/servers/lloadd/value.c
new file mode 100644
index 0000000..ec71444
--- /dev/null
+++ b/servers/lloadd/value.c
@@ -0,0 +1,67 @@
+/* value.c - routines for dealing with values */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * 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
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/*
+ * 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 "lload.h"
+
+int
+value_add_one( BerVarray *vals, struct berval *addval )
+{
+ int n;
+ BerVarray v2;
+
+ if ( *vals == NULL ) {
+ *vals = (BerVarray)SLAP_MALLOC( 2 * sizeof(struct berval) );
+ if ( *vals == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "value_add_one: "
+ "SLAP_MALLOC failed.\n" );
+ return LBER_ERROR_MEMORY;
+ }
+ n = 0;
+
+ } else {
+ for ( n = 0; !BER_BVISNULL( &(*vals)[n] ); n++ ) {
+ ; /* Empty */
+ }
+ *vals = (BerVarray)SLAP_REALLOC(
+ (char *)*vals, ( n + 2 ) * sizeof(struct berval) );
+ if ( *vals == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "value_add_one: "
+ "SLAP_MALLOC failed.\n" );
+ return LBER_ERROR_MEMORY;
+ }
+ }
+
+ v2 = &(*vals)[n];
+ ber_dupbv( v2, addval );
+
+ v2++;
+ BER_BVZERO( v2 );
+
+ return LDAP_SUCCESS;
+}