diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:54:12 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:54:12 +0000 |
commit | b527294153be3b79563c82c66102adc0004736c0 (patch) | |
tree | 9b423a224848441885190b5ea7cf0feb23510c9d /servers/lloadd | |
parent | Initial commit. (diff) | |
download | openldap-c8fbeeca8dbf1e2566e57ef7d6140c44e5f1c9de.tar.xz openldap-c8fbeeca8dbf1e2566e57ef7d6140c44e5f1c9de.zip |
Adding upstream version 2.6.7+dfsg.upstream/2.6.7+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
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 *)©_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", "e_ptr, &inquote ); + + if ( token ) + for ( i = 0; hide[i]; i++ ) + if ( !strcasecmp( token, hide[i] ) ) break; + if ( quote_ptr ) *quote_ptr = ' '; + Debug( LDAP_DEBUG_CONFIG, "%s (%s%s)\n", + c->log, hide[i] ? hide[i] : c->line, hide[i] ? " ***" : "" ); + if ( quote_ptr ) *quote_ptr = '\0'; + + for ( ;; token = strtok_quote( NULL, " \t", "e_ptr, &inquote ) ) { + if ( c->argc >= c->argv_size ) { + char **tmp; + tmp = ch_realloc( c->argv, + ( c->argv_size + ARGS_STEP ) * sizeof(*c->argv) ); + if ( !tmp ) { + Debug( LDAP_DEBUG_ANY, "%s: out of memory\n", c->log ); + return -1; + } + c->argv = tmp; + c->argv_size += ARGS_STEP; + } + if ( token == NULL ) break; + c->argv[c->argc++] = token; + } + c->argv[c->argc] = NULL; + if ( inquote ) { + /* these directives parse c->line independently of argv tokenizing */ + for ( i = 0; raw[i]; i++ ) + if ( !strcasecmp( c->argv[0], raw[i] ) ) return 0; + + Debug( LDAP_DEBUG_ANY, "%s: unterminated quoted string \"%s\"\n", + c->log, c->argv[c->argc - 1] ); + return -1; + } + return 0; +} + +void +lload_config_destroy( void ) +{ + free( line ); + if ( slapd_args_file ) free( slapd_args_file ); + if ( slapd_pid_file ) free( slapd_pid_file ); + slap_loglevel_destroy(); +} + +/* See if the given URL (in plain and parsed form) matches + * any of the server's listener addresses. Return matching + * LloadListener or NULL for no match. + */ +LloadListener * +lload_config_check_my_url( const char *url, LDAPURLDesc *lud ) +{ + LloadListener **l = lloadd_get_listeners(); + int i, isMe; + + /* Try a straight compare with LloadListener strings */ + for ( i = 0; l && l[i]; i++ ) { + if ( !strcasecmp( url, l[i]->sl_url.bv_val ) ) { + return l[i]; + } + } + + isMe = 0; + /* If hostname is empty, or is localhost, or matches + * our hostname, this url refers to this host. + * Compare it against listeners and ports. + */ + if ( !lud->lud_host || !lud->lud_host[0] || + !strncasecmp( + "localhost", lud->lud_host, STRLENOF("localhost") ) || + !strcasecmp( global_host, lud->lud_host ) ) { + for ( i = 0; l && l[i]; i++ ) { + LDAPURLDesc *lu2; + ldap_url_parse_ext( + l[i]->sl_url.bv_val, &lu2, LDAP_PVT_URL_PARSE_DEF_PORT ); + do { + if ( strcasecmp( lud->lud_scheme, lu2->lud_scheme ) ) break; + if ( lud->lud_port != lu2->lud_port ) break; + /* Listener on ANY address */ + if ( !lu2->lud_host || !lu2->lud_host[0] ) { + isMe = 1; + break; + } + /* URL on ANY address */ + if ( !lud->lud_host || !lud->lud_host[0] ) { + isMe = 1; + break; + } + /* Listener has specific host, must + * match it + */ + if ( !strcasecmp( lud->lud_host, lu2->lud_host ) ) { + isMe = 1; + break; + } + } while (0); + ldap_free_urldesc( lu2 ); + if ( isMe ) { + return l[i]; + } + } + } + return NULL; +} + +#ifdef BALANCER_MODULE +static int +backend_cf_gen( ConfigArgs *c ) +{ + LloadBackend *b = c->ca_private; + enum lcf_backend flag = 0; + int rc = LDAP_SUCCESS; + + assert( b != NULL ); + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch ( c->type ) { + case CFG_URI: + c->value_bv = b->b_uri; + break; + case CFG_NUMCONNS: + c->value_uint = b->b_numconns; + break; + case CFG_BINDCONNS: + c->value_uint = b->b_numbindconns; + break; + case CFG_RETRY: + c->value_uint = b->b_retry_timeout; + break; + case CFG_MAX_PENDING_CONNS: + c->value_uint = b->b_max_conn_pending; + break; + case CFG_MAX_PENDING_OPS: + c->value_uint = b->b_max_pending; + break; + case CFG_STARTTLS: + enum_to_verb( tlskey, b->b_tls_conf, &c->value_bv ); + break; + case CFG_WEIGHT: + c->value_uint = b->b_weight; + break; + default: + rc = 1; + break; + } + + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { + /* We only need to worry about deletions to multi-value or MAY + * attributes */ + switch ( c->type ) { + case CFG_STARTTLS: + b->b_tls_conf = LLOAD_CLEARTEXT; + break; + default: + break; + } + return rc; + } + + switch ( c->type ) { + case CFG_URI: + rc = backend_config_url( b, &c->value_bv ); + if ( rc ) { + backend_config_url( b, &b->b_uri ); + goto fail; + } + if ( !BER_BVISNULL( &b->b_uri ) ) { + ch_free( b->b_uri.bv_val ); + } + b->b_uri = c->value_bv; + flag = LLOAD_BACKEND_MOD_OTHER; + break; + case CFG_NUMCONNS: + if ( !c->value_uint ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "invalid connection pool configuration" ); + goto fail; + } + b->b_numconns = c->value_uint; + flag = LLOAD_BACKEND_MOD_CONNS; + break; + case CFG_BINDCONNS: + if ( !c->value_uint ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "invalid connection pool configuration" ); + goto fail; + } + b->b_numbindconns = c->value_uint; + flag = LLOAD_BACKEND_MOD_CONNS; + break; + case CFG_RETRY: + b->b_retry_timeout = c->value_uint; + break; + case CFG_MAX_PENDING_CONNS: + b->b_max_conn_pending = c->value_uint; + break; + case CFG_MAX_PENDING_OPS: + b->b_max_pending = c->value_uint; + break; + case CFG_STARTTLS: { + int i = bverb_to_mask( &c->value_bv, tlskey ); + if ( BER_BVISNULL( &tlskey[i].word ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "invalid starttls configuration" ); + goto fail; + } +#ifndef HAVE_TLS + if ( tlskey[i].mask == LLOAD_STARTTLS_OPTIONAL ) { + Debug( LDAP_DEBUG_ANY, "%s: " + "lloadd compiled without TLS but starttls specified, " + "it will be ignored\n", + c->log ); + } else if ( tlskey[i].mask != LLOAD_CLEARTEXT ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "invalid starttls configuration when compiled without " + "TLS support" ); + goto fail; + } +#endif /* ! HAVE_TLS */ + b->b_tls_conf = tlskey[i].mask; + } break; + case CFG_WEIGHT: + b->b_weight = c->value_uint; + break; + default: + rc = 1; + break; + } + + /* do not set this if it has already been set by another callback, e.g. + * lload_backend_ldadd */ + if ( lload_change.type == LLOAD_CHANGE_UNDEFINED ) { + lload_change.type = LLOAD_CHANGE_MODIFY; + } + lload_change.object = LLOAD_BACKEND; + lload_change.target = b; + lload_change.flags.backend |= flag; + + config_push_cleanup( c, lload_backend_finish ); + return rc; + +fail: + if ( lload_change.type == LLOAD_CHANGE_ADD ) { + /* Abort the ADD */ + lload_change.type = LLOAD_CHANGE_DEL; + } + + Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg ); + return 1; +} + +int +lload_back_init_cf( BackendInfo *bi ) +{ + /* Make sure we don't exceed the bits reserved for userland */ + config_check_userland( CFG_LAST ); + + bi->bi_cf_ocs = lloadocs; + + return config_register_schema( config_back_cf_table, lloadocs ); +} + +static int +lload_tier_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca ) +{ + LloadTier *tier; + Attribute *a; + AttributeDescription *ad = NULL; + struct lload_tier_type *tier_impl; + struct berval bv, type, rdn; + const char *text; + char *name; + + Debug( LDAP_DEBUG_TRACE, "lload_tier_ldadd: " + "a new tier is being added\n" ); + + if ( p->ce_type != Cft_Backend || !p->ce_bi || + p->ce_bi->bi_cf_ocs != lloadocs ) + return LDAP_CONSTRAINT_VIOLATION; + + dnRdn( &e->e_name, &rdn ); + type.bv_len = strchr( rdn.bv_val, '=' ) - rdn.bv_val; + type.bv_val = rdn.bv_val; + + /* Find attr */ + slap_bv2ad( &type, &ad, &text ); + if ( ad != slap_schema.si_ad_cn ) return LDAP_NAMING_VIOLATION; + + a = attr_find( e->e_attrs, ad ); + if ( !a || a->a_numvals != 1 ) return LDAP_NAMING_VIOLATION; + bv = a->a_vals[0]; + + if ( bv.bv_val[0] == '{' && ( name = strchr( bv.bv_val, '}' ) ) ) { + name++; + bv.bv_len -= name - bv.bv_val; + bv.bv_val = name; + } + + ad = NULL; + slap_str2ad( "olcBkLloadTierType", &ad, &text ); + assert( ad != NULL ); + + a = attr_find( e->e_attrs, ad ); + if ( !a || a->a_numvals != 1 ) return LDAP_OBJECT_CLASS_VIOLATION; + + tier_impl = lload_tier_find( a->a_vals[0].bv_val ); + if ( !tier_impl ) { + Debug( LDAP_DEBUG_ANY, "lload_tier_ldadd: " + "tier type %s not recongnised\n", + bv.bv_val ); + return LDAP_OTHER; + } + + tier = tier_impl->tier_init(); + if ( !tier ) { + return LDAP_OTHER; + } + + ber_dupbv( &tier->t_name, &bv ); + + ca->bi = p->ce_bi; + ca->ca_private = tier; + + if ( !lloadd_inited ) { + if ( LDAP_STAILQ_EMPTY( &tiers ) ) { + LDAP_STAILQ_INSERT_HEAD( &tiers, tier, t_next ); + } else { + LDAP_STAILQ_INSERT_TAIL( &tiers, tier, t_next ); + } + } + + /* ca cleanups are only run in the case of online config but we use it to + * save the new config when done with the entry */ + ca->lineno = 0; + + lload_change.type = LLOAD_CHANGE_ADD; + lload_change.object = LLOAD_TIER; + lload_change.target = tier; + + return LDAP_SUCCESS; +} + +static int +lload_backend_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca ) +{ + LloadTier *tier = p->ce_private; + LloadBackend *b; + Attribute *a; + AttributeDescription *ad = NULL; + struct berval bv, type, rdn; + const char *text; + char *name; + + Debug( LDAP_DEBUG_TRACE, "lload_backend_ldadd: " + "a new backend-server is being added\n" ); + + if ( p->ce_type != Cft_Misc || !p->ce_bi || + p->ce_bi->bi_cf_ocs != lloadocs ) + return LDAP_CONSTRAINT_VIOLATION; + + dnRdn( &e->e_name, &rdn ); + type.bv_len = strchr( rdn.bv_val, '=' ) - rdn.bv_val; + type.bv_val = rdn.bv_val; + + /* Find attr */ + slap_bv2ad( &type, &ad, &text ); + if ( ad != slap_schema.si_ad_cn ) return LDAP_NAMING_VIOLATION; + + a = attr_find( e->e_attrs, ad ); + if ( !a || a->a_numvals != 1 ) return LDAP_NAMING_VIOLATION; + bv = a->a_vals[0]; + + if ( bv.bv_val[0] == '{' && ( name = strchr( bv.bv_val, '}' ) ) ) { + name++; + bv.bv_len -= name - bv.bv_val; + bv.bv_val = name; + } + + b = lload_backend_new(); + ber_dupbv( &b->b_name, &bv ); + b->b_tier = tier; + + ca->bi = p->ce_bi; + ca->ca_private = b; + config_push_cleanup( ca, lload_backend_finish ); + + /* ca cleanups are only run in the case of online config but we use it to + * save the new config when done with the entry */ + ca->lineno = 0; + + lload_change.type = LLOAD_CHANGE_ADD; + lload_change.object = LLOAD_BACKEND; + lload_change.target = b; + + return LDAP_SUCCESS; +} + +#ifdef SLAP_CONFIG_DELETE +static int +lload_backend_lddel( CfEntryInfo *ce, Operation *op ) +{ + LloadBackend *b = ce->ce_private; + + lload_change.type = LLOAD_CHANGE_DEL; + lload_change.object = LLOAD_BACKEND; + lload_change.target = b; + + return LDAP_SUCCESS; +} + +static int +lload_tier_lddel( CfEntryInfo *ce, Operation *op ) +{ + LloadTier *tier = ce->ce_private; + + lload_change.type = LLOAD_CHANGE_DEL; + lload_change.object = LLOAD_TIER; + lload_change.target = tier; + + return LDAP_SUCCESS; +} +#endif /* SLAP_CONFIG_DELETE */ + +static int +lload_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *c ) +{ + struct berval bv; + LloadTier *tier; + int i = 0; + + bv.bv_val = c->cr_msg; + LDAP_STAILQ_FOREACH ( tier, &tiers, t_next ) { + LloadBackend *b; + ConfigOCs *coc; + Entry *e; + int j = 0; + + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), + "cn=" SLAP_X_ORDERED_FMT "%s", i, tier->t_name.bv_val ); + + c->ca_private = tier; + c->valx = i; + + for ( coc = lloadocs; coc->co_type; coc++ ) { + if ( !ber_bvcmp( coc->co_name, &tier->t_type.tier_oc ) ) { + break; + } + } + assert( coc->co_type ); + + e = config_build_entry( op, rs, p->e_private, c, &bv, coc, NULL ); + if ( !e ) { + return 1; + } + + LDAP_CIRCLEQ_FOREACH ( b, &tier->t_backends, b_next ) { + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), + "cn=" SLAP_X_ORDERED_FMT "%s", j, b->b_name.bv_val ); + + for ( coc = lloadocs; coc->co_type; coc++ ) { + if ( !ber_bvcmp( + coc->co_name, &tier->t_type.tier_backend_oc ) ) { + break; + } + } + assert( coc->co_type ); + + c->ca_private = b; + c->valx = j; + + if ( !config_build_entry( + op, rs, e->e_private, c, &bv, coc, NULL ) ) { + return 1; + } + + j++; + } + + i++; + } + return LDAP_SUCCESS; +} +#endif /* BALANCER_MODULE */ diff --git a/servers/lloadd/connection.c b/servers/lloadd/connection.c new file mode 100644 index 0000000..a3f5323 --- /dev/null +++ b/servers/lloadd/connection.c @@ -0,0 +1,644 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <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], ¤t_refs, ¤t_refs, + __ATOMIC_ACQ_REL ); + ldap_pvt_thread_rdwr_runlock( &epoch_mutex ); + + for ( p = old_refs; p; p = next ) { + next = p->next; + + p->dispose( p->object ); + ch_free( p ); + } + + for ( p = current_refs; p; p = next ) { + next = p->next; + + p->dispose( p->object ); + ch_free( p ); + } +} + +/* + * Add the object to the "current global epoch", not the epoch our thread + * entered. + */ +void +epoch_append( void *ptr, dispose_cb *cb ) +{ + struct pending_ref *new; + epoch_t epoch = __atomic_load_n( ¤t_epoch, __ATOMIC_ACQUIRE ); + + /* + * BTW, the following is not appropriate here: + * assert( __atomic_load_n( &epoch_threads[epoch], __ATOMIC_RELAXED ) ); + * + * We might be a thread lagging behind in the "previous epoch" with no + * other threads executing at all. + */ + + new = ch_malloc( sizeof(struct pending_ref) ); + new->object = ptr; + new->dispose = cb; + new->next = __atomic_load_n( &references[epoch], __ATOMIC_ACQUIRE ); + + while ( !__atomic_compare_exchange( &references[epoch], &new->next, &new, 0, + __ATOMIC_RELEASE, __ATOMIC_RELAXED ) ) + /* iterate until we succeed */; +} + +int +acquire_ref( uintptr_t *refp ) +{ + uintptr_t refcnt, new_refcnt; + + refcnt = __atomic_load_n( refp, __ATOMIC_ACQUIRE ); + + /* + * If we just incremented the refcnt and checked for zero after, another + * thread might falsely believe the object was going to stick around. + * + * Checking whether the object is still dead at disposal time might not be + * able to distinguish it from being freed in a later epoch. + */ + do { + if ( !refcnt ) { + return refcnt; + } + + new_refcnt = refcnt + 1; + } while ( !__atomic_compare_exchange( refp, &refcnt, &new_refcnt, 0, + __ATOMIC_RELEASE, __ATOMIC_RELAXED ) ); + assert( new_refcnt == refcnt + 1 ); + + return refcnt; +} + +int +try_release_ref( + uintptr_t *refp, + void *object, + dispose_cb *unlink_cb, + dispose_cb *destroy_cb ) +{ + uintptr_t refcnt, new_refcnt; + + refcnt = __atomic_load_n( refp, __ATOMIC_ACQUIRE ); + + /* We promise the caller that we won't decrease refcnt below 0 */ + do { + if ( !refcnt ) { + return refcnt; + } + + new_refcnt = refcnt - 1; + } while ( !__atomic_compare_exchange( refp, &refcnt, &new_refcnt, 0, + __ATOMIC_RELEASE, __ATOMIC_RELAXED ) ); + assert( new_refcnt == refcnt - 1 ); + + if ( !new_refcnt ) { + if ( unlink_cb ) { + unlink_cb( object ); + } + epoch_append( object, destroy_cb ); + } + + return refcnt; +} diff --git a/servers/lloadd/epoch.h b/servers/lloadd/epoch.h new file mode 100644 index 0000000..06b70be --- /dev/null +++ b/servers/lloadd/epoch.h @@ -0,0 +1,148 @@ +/* epoch.h - epoch based memory reclamation */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <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; +} |