From 46ddb4039cfdf318f2499f9ee08f14ec1b3b4c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 8 Mar 2017 22:59:57 +0000 Subject: [PATCH] lloadd ahoy --- .gitignore | 1 + build/top.mk | 5 + configure.ac | 44 + doc/devel/lloadd/design.md | 282 ++++ include/ldap_defaults.h | 5 + servers/Makefile.in | 2 +- servers/lloadd/Makefile.in | 74 + servers/lloadd/ch_malloc.c | 1 + servers/lloadd/client.c | 103 ++ servers/lloadd/config.c | 2374 +++++++++++++++++++++++++++++ servers/lloadd/config.h | 147 ++ servers/lloadd/connection.c | 148 ++ servers/lloadd/daemon.c | 1400 +++++++++++++++++ servers/lloadd/design.md | 1 + servers/lloadd/globals.c | 1 + servers/lloadd/init.c | 144 ++ servers/lloadd/libevent_support.c | 161 ++ servers/lloadd/main.c | 974 ++++++++++++ servers/lloadd/nt_svc.c | 1 + servers/lloadd/proto-slap.h | 268 ++++ servers/lloadd/sl_malloc.c | 1 + servers/lloadd/slap.h | 332 ++++ servers/lloadd/user.c | 1 + servers/lloadd/value.c | 67 + servers/slapd/bconfig.c | 1 + 25 files changed, 6537 insertions(+), 1 deletion(-) create mode 100644 doc/devel/lloadd/design.md create mode 100644 servers/lloadd/Makefile.in create mode 120000 servers/lloadd/ch_malloc.c create mode 100644 servers/lloadd/client.c create mode 100644 servers/lloadd/config.c create mode 100644 servers/lloadd/config.h create mode 100644 servers/lloadd/connection.c create mode 100644 servers/lloadd/daemon.c create mode 120000 servers/lloadd/design.md create mode 120000 servers/lloadd/globals.c create mode 100644 servers/lloadd/init.c create mode 100644 servers/lloadd/libevent_support.c create mode 100644 servers/lloadd/main.c create mode 120000 servers/lloadd/nt_svc.c create mode 100644 servers/lloadd/proto-slap.h create mode 120000 servers/lloadd/sl_malloc.c create mode 100644 servers/lloadd/slap.h create mode 120000 servers/lloadd/user.c create mode 100644 servers/lloadd/value.c diff --git a/.gitignore b/.gitignore index 0f8012c05a..0f8b363cb4 100644 --- a/.gitignore +++ b/.gitignore @@ -96,6 +96,7 @@ servers/slapd/slapmodify servers/slapd/slappasswd servers/slapd/slapschema servers/slapd/slaptest +servers/lloadd/lloadd tests/progs/ldif-filter tests/progs/slapd-addel tests/progs/slapd-bind diff --git a/build/top.mk b/build/top.mk index 86fafca025..be03fdde72 100644 --- a/build/top.mk +++ b/build/top.mk @@ -164,6 +164,8 @@ LTHREAD_LIBS = @LTHREAD_LIBS@ SLAPD_NDB_LIBS = @SLAPD_NDB_LIBS@ WT_LIBS = @WT_LIBS@ +LEVENT_LIBS = @LEVENT_LIBS@ + LDAP_LIBLBER_LA = $(LDAP_LIBDIR)/liblber/liblber.la LDAP_LIBLDAP_LA = $(LDAP_LIBDIR)/libldap/libldap.la @@ -175,6 +177,8 @@ LDAP_L = $(LDAP_LIBLUTIL_A) \ $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA) SLAPD_L = $(LDAP_LIBLUNICODE_A) $(LDAP_LIBREWRITE_A) \ $(LDAP_LIBLUTIL_A) $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA) +LLOADD_L = $(LDAP_LIBLUTIL_A) $(LDAP_LIBLDAP_LA) \ + $(LDAP_LIBLBER_LA) WRAP_LIBS = @WRAP_LIBS@ # AutoConfig generated @@ -202,6 +206,7 @@ SLAPD_SQL_INCLUDES = @SLAPD_SQL_INCLUDES@ SLAPD_SQL_LIBS = @SLAPD_SQL_LIBS@ SLAPD_LIBS = @SLAPD_LIBS@ @SLAPD_PERL_LDFLAGS@ @SLAPD_SQL_LDFLAGS@ @SLAPD_SQL_LIBS@ @SLAPD_SLP_LIBS@ @SLAPD_GMP_LIBS@ +LLOADD_LIBS = @BALANCER_LIBS@ $(LEVENT_LIBS) # Our Defaults CC = $(AC_CC) diff --git a/configure.ac b/configure.ac index 49fb967efb..65372c7de2 100644 --- a/configure.ac +++ b/configure.ac @@ -410,6 +410,12 @@ OL_ARG_ENABLE(unique, [AS_HELP_STRING([--enable-unique], [Attribute Uniqueness o OL_ARG_ENABLE(valsort, [AS_HELP_STRING([--enable-valsort], [Value Sorting overlay])], no, [no yes mod], ol_enable_overlays) +dnl ---------------------------------------------------------------- +dnl BALANCER OPTIONS +AC_ARG_ENABLE(balanceroptions,[ +LLOADD (Load Balancer Daemon) Options:]) +OL_ARG_ENABLE(balancer,[ --enable-balancer enable building load balancer], auto)dnl + dnl ---------------------------------------------------------------- AC_ARG_ENABLE(xxliboptions,[ Library Generation & Linking Options]) @@ -481,6 +487,13 @@ if test $ol_enable_modules = yes ; then ol_enable_dynamic=yes fi +if test $ol_enable_balancer = yes ; then + dnl Load Balancer was specifically enabled + if test $ol_with_threads = no ; then + AC_MSG_ERROR([Load balancer requires threads]) + fi +fi + if test $ol_enable_spasswd = yes ; then if test $ol_with_cyrus_sasl = no ; then AC_MSG_ERROR([--enable-spasswd requires --with-cyrus-sasl]) @@ -504,13 +517,16 @@ LDAP_LIBS= SLAPD_NDB_LIBS= SLAPD_NDB_INCS= LTHREAD_LIBS= +LEVENT_LIBS= LUTIL_LIBS= CLIENT_LIBS= SLAPD_LIBS= +BALANCER_LIBS= BUILD_SLAPD=no +BUILD_BALANCER=no BUILD_THREAD=no @@ -2128,6 +2144,24 @@ if test $ol_enable_slp != no ; then fi fi +dnl ---------------------------------------------------------------- +dnl Libevent +if test $ol_enable_balancer != no ; then + AC_CHECK_LIB(event_core, evconnlistener_set_error_cb, + [have_libevent=yes + LEVENT_LIBS="$LEVENT_LIBS -levent_core"], + [AC_CHECK_LIB(event, evconnlistener_set_error_cb, + [have_libevent=yes + LEVENT_LIBS="$LEVENT_LIBS -levent"], + [have_libevent=no])]) + + if test $have_libevent = yes ; then + AC_DEFINE(HAVE_LIBEVENT, 1, [define if you have -levent]) + elif test $ol_enable_balancer = yes ; then + AC_MSG_ERROR([You need libevent 2.0 or later to build the load balancer]) + fi +fi + dnl ---------------------------------------------------------------- dnl Checks for typedefs, structures, and compiler characteristics. @@ -2930,6 +2964,12 @@ if test "$ol_enable_valsort" != no ; then AC_DEFINE_UNQUOTED(SLAPD_OVER_VALSORT,$MFLAG,[define for Value Sorting overlay]) fi +if test "$ol_enable_balancer" != no \ + -a "$ol_with_threads" != no \ + -a "$have_libevent" = yes ; then + BUILD_BALANCER=yes +fi + if test "$ol_enable_slapi" != no ; then AC_DEFINE(ENABLE_SLAPI,1,[define to enable slapi library]) BUILD_SLAPI=yes @@ -3002,14 +3042,17 @@ dnl overlays AC_SUBST(BUILD_TRANSLUCENT) AC_SUBST(BUILD_UNIQUE) AC_SUBST(BUILD_VALSORT) + AC_SUBST(BUILD_BALANCER) AC_SUBST(LDAP_LIBS) AC_SUBST(CLIENT_LIBS) AC_SUBST(SLAPD_LIBS) +AC_SUBST(BALANCER_LIBS) AC_SUBST(SLAPD_NDB_LIBS) AC_SUBST(SLAPD_NDB_INCS) AC_SUBST(LTHREAD_LIBS) AC_SUBST(LUTIL_LIBS) +AC_SUBST(LEVENT_LIBS) AC_SUBST(WRAP_LIBS) AC_SUBST(SLAPD_MODULES_CPPFLAGS) @@ -3091,6 +3134,7 @@ AC_CONFIG_FILES([Makefile:build/top.mk:Makefile.in:build/dir.mk] [servers/slapd/shell-backends/Makefile:build/top.mk:servers/slapd/shell-backends/Makefile.in:build/srv.mk] [servers/slapd/slapi/Makefile:build/top.mk:servers/slapd/slapi/Makefile.in:build/lib.mk:build/lib-shared.mk] [servers/slapd/overlays/Makefile:build/top.mk:servers/slapd/overlays/Makefile.in:build/lib.mk] +[servers/lloadd/Makefile:build/top.mk:servers/lloadd/Makefile.in:build/srv.mk] [tests/Makefile:build/top.mk:tests/Makefile.in:build/dir.mk] [tests/run] [tests/progs/Makefile:build/top.mk:tests/progs/Makefile.in:build/rules.mk]) diff --git a/doc/devel/lloadd/design.md b/doc/devel/lloadd/design.md new file mode 100644 index 0000000000..62fcd882af --- /dev/null +++ b/doc/devel/lloadd/design.md @@ -0,0 +1,282 @@ +TODO: +- [ ] keep a global op in-flight counter? (might need locking) +- [-] scheduling (who does what, more than one select thread? How does the proxy + work get distributed between threads?) +- [ ] managing timeouts? +- [X] outline locking policy: seems like there might be a lock inversion in the + design looming: when working with op, might need a lock on both client and + upstream but depending on where we started, we might want to start with + locking one, then other +- [ ] how to deal with the balancer running out of fds? Especially when we hit + the limit, then lose an upstream connection and accept() a client, we + wouldn't be able to initiate a new one. A bit of a DoS... But probably not + a concern for Ericsson +- [ ] non-Linux? No idea how anything other than poll works (moot if building a + libevent/libuv-based load balancer since they take care of that, except + edge-triggered I/O?) +- [-] rootDSE? Controls and exops might have different semantics and need + binding to the same upstream connection. +- [ ] Just piggybacking on OpenLDAP as a module? Would still need some updates + in the core and the module/subsystem would be a very invasive one. On the + other hand, allows to expose live configuration and monitoring over LDAP + over the current slapd listeners without re-inventing the wheel. + + +Expecting to handle only LDAPv3 + +terms: + server - configured target + upstream - a single connection to a server + client - an incoming connection + +To maintain fairness `G( requested => ( F( progressed | failed ) ) )`, use +queues and put timeouts in + +Runtime organisation +------ +- main thread with its own event base handling signals +- one thread (later possibly more) listening on the rendezvous sockets, handing + the new sockets to worker threads +- n worker threads dealing with client and server I/O (dispatching actual work + to the thread pool most likely) +- a thread pool to handle actual work + +Operational behaviour +------ + +- client read -> upstream write: + - client read: + - if TLS_SETUP, keep processing, set state back when finished and note that + we're under TLS + - ber_get_next(), if we don't have a tag, finished (unless we have true + edge-triggered I/O, also put the fd back into the ones we're waiting for) + - peek at op tag: + - unbind: + - with a single lock, mark all pending ops in upstreams abandoned, clear + client link (would it be fast enough if we remove them from upstream + map instead?) + - locked per op: + - remove op from upstream map + - check upstream is not write-suspended, if it is ... + - try to write the abandon op to upstream, suspend upstream if not + fully sent + - remove op from client map (how if we're in avl_apply?, another pass?) + - would be nice if we could wipe the complete client map then, otherwise + we need to queue it to have it freed when all abandons get passed onto + the upstream (just dropping them might put extra strain on upstreams, + will probably have a queue on each client/upstream anyway, not just a + single Ber) + - bind: + - check mechanism is not EXTERNAL (or implement it) + - abandon existing ops (see unbind) + - set state to BINDING, put DN into authzid + - pick upstream, create PDU and sent + - abandon: + - find op, mark for abandon, send to appropriate upstream + - Exop: + - check not BINDING (unless it's a cancel?) + - check OID: + - STARTTLS: + - check we don't have TLS yet + - abandon all + - set state to TLS_SETUP + - send the hello + - VC(?): + - similar to bind except for the abandons/state change + - other: + - check not BINDING + - pick an upstream + - create a PDU, send (marking upstream suspended if not written in full) + - check if should read again (keep a counter of number of times to read + off a connection in a single pass so that we maintain fairness) + - if read enough requests and can still read, re-queue ourselves (if we + don't have true edge-triggered I/O, we can just register the fd again) + - upstream write (only when suspended): + - flush the current BER + - there shouldn't be anything else? +- upstream read -> client write: + - upstream read: + - ber_get_next(), if we don't have a tag, finished (unless we have true + edge-triggered I/O, also put the fd back into the ones we're waiting for) + - when we get it, peek at msgid, resolve client connection, lock, check: + - if unsolicited, handle as close (and mark connection closing) + - if op is abandoned or does not exist, drop PDU and op, update counters + - if client backlogged, suspend upstream, register callback to unsuspend + (on progress when writing to client or abandon from client (connection + death, abandon proper, ...)) + - reconstruct final PDU, write BER to client, if did not write fully, + suspend client + - if a final response, decrement operation counts on upstream and client + - check if should read again (keep a counter of number of responses to read + off a connection in a single pass so that we don't starve any?) + - client write ready (only checked for when suspended): + - write the rest of pending BER if any + - on successful write, pick all pending ops that need failure response, push + to client (are there any controls that need to be present in response even + in the case of failure?, what to do with them?) + - on successfully flushing them, walk through suspended upstreams, picking + the pending PDU (unsuspending the upstream) and writing, if PDU flushed + successfully, pick next upstream + - if we successfully flushed all suspended upstreams, unsuspend client + (and disable the write callback) +- upstream close/error: + - look up pending ops, try to write to clients, mark clients suspended that + have ops that need responses (another queue associated with client to speed + up?) + - schedule a new connection open +- client close/error: + - same as unbind +- client inactive (no pending ops and nothing happened in x seconds) + - might just send notice of disconnection and close +- op timeout handling: + - mark for abandon + - send abandon + - send timeLimitExceeded/adminLimitExceeded to client + +Picking an upstream: +- while there is a level available: + - pick a random ordering of upstreams based on weights + - while there is an upstream in the level: + - check number of ops in-flight (this is where we lock the upstream map) + - find the least busy connection (and check if a new connection should be + opened) + - try to lock for socket write, if available (no BER queued) we have our + upstream + +PDU processing: +- request (have an upstream selected): + - get new msgid from upstream + - create an Op structure (actually, with the need for freelist lock, we can + make it a cache for freed operation structures, avoiding some malloc + traffic, to reset, we need slap_sl_mem_create( ,,, 1 )) + - check proxyauthz is not present? or just let upstream reject it if there are + two? + - add own controls at the end: + - construct proxyauthz from authzid + - construct session tracking from remote IP, own name, authzid + - send over + - insert Op into client and upstream maps +- response/intermediate/entry: + - look up Op in upstream's map + - write old msgid, rest of the response can go unchanged + - if a response, remove Op from all maps (client and upstream) + +Managing upstreams: +- async connect up to min_connections (is there a point in having a connection + count range if we can't use it when needed since all of the below is async?) +- when connected, set up TLS (if requested) +- when done, send a bind +- go for the bind interaction +- when done, add it to the upstream's connection list +- (if a connection is suspended or connections are over 75 % op limit, schedule + creating a new connection setup unless connection limit has been hit) + +Managing timeouts: +- two options: + - maintain a separate locked priority queue to give a perfect ordering to when + each operation is to time out, would need to maintain yet another place + where operations can be found. + - the locking protocol for disposing of the operation would need to be + adjusted and might become even more complicated, might do the alternative + initially and then attempt this if it helps performance + - just do a sweep over all clients (that mutex is less contended) every so + often. With many in-flight operations might be a lot of wasted work. + - we still need to sweep over all clients to check if they should be killed + anyway + +Dispatcher thread (2^n of them, fd x is handled by thread no x % (2^n)): +- poll on all registered fds +- remove each fd that's ready from the registered list and schedule the work +- work threads can put their fd back in if they deem necessary (=not suspended) +- this works as a poor man's edge-triggered polling, with enough workers, should + we do proper edge triggered I/O? What about non-Linux? + +Listener thread: +- slapd has just one, which then reassigns the sockets to separate I/O + threads + +Threading: +- if using slap_sl_malloc, how much perf do we gain? To allocate a context per + op, we should have a dedicated parent context so that when we free it, we can + use that exclusively. The parent context's parent would be the main thread's + context. This implies a lot of slap_sl_mem_setctx/slap_sl_mem_create( ,,, 0 ) + and making sure an op does not allocate/free things from two threads at the + same time (might need an Op mutex after all? Not such a huge cost if we + routinely reuse Op structures) + +Locking policy: +- read mutexes are unnecessary, we only have one thread receiving data from the + connection - the one started from the dispatcher +- two reference counters of operation structures (an op is accessible from + client and upstream map, each counter is consistent when thread has a lock on + corresponding map), when decreasing the counter to zero, start freeing + procedure +- place to mark disposal finished for each side, consistency enforced by holding + the freelist lock when reading/manipulating +- when op is created, we already have a write lock on upstream socket and map, + start writing, insert to upstream map with upstream refcount 1, unlock, lock + client, insert (client refcount 0), unlock, lock upstream, decrement refcount + (triggers a test if we need to drop it now), unlock upstream, done +- when upstream processes a PDU, locks its map, increments counter, (potentially + removes if it's a response), unlocks, locks client's map, write mutex (this + order?) and full client mutex (if a bind response) +- when client side wants to work with a PDU (abandon, (un)bind), locks its map, + increase refcount, unlocks, locks upstream map, write mutex, sends or queues + abandon, unlocks write mutex, initiates freeing procedure from upstream side + (or if having to remember we've already increased client-side refcount, mark + for deletion, lose upstream lock, lock client, decref, either triggering + deletion from client or mark for it) +- if we have operation lock, we can simplify a bit (no need for three-stage + locking above) + +Shutdown: +- stop accept() thread(s) - potentially add a channel to hand these listening + sockets over for zero-downtime restart +- if very gentle, mark connections as closing, start timeout and: + - when a new non-abandon PDU comes in from client - return LDAP_UNAVAILABLE + - when receiving a PDU from upstream, send over to client, if no ops pending, + send unsolicited response and close (RFC4511 suggests unsolicited response + is the last PDU coming from the upstream and libldap agrees, so we can't + send it for a socket we want to shut down more gracefully) +- gentle (or very gentle timed out): + - set timeout + - mark all ops as abandoned + - send unbind to all upstreams + - send unsolicited to all clients +- imminent (or gentle timed out): + - async close all connections? + - exit() + +RootDSE: +- default option is not to care and if a control/exop has special restrictions, + it is the admin's job to flag it as such in the load-balancer's config +- another is not to care about the search request but check each search entry + being passed back, check DN and if it's a rootDSE, filter the list of + controls/exops/sasl mechs (external!) that are supported +- last one is to check all search requests for the DN/scope and synthesise the + response locally - probably not (would need to configure the complete list of + controls, exops, sasl mechs, naming contexts in the balancer) + +Potential red flags: +- we suspend upstreams, if we ever suspend clients we need to be sure we can't + create dependency cycles + - is this an issue when only suspending the read side of each? Because even if + we stop reading from everything, we should eventually flush data to those we + can still talk to, as upstreams are flushed, we can start sending new + requests from live clients (those that are suspended are due to their own + inability to accept data) + - we might need to suspend a client if there is a reason to choose a + particular upstream (multi-request operation - bind, VC, PR, TXN, ...) + - a SASL bind, but that means there are no outstanding ops to receive + it holds that !suspended(client) \or !suspended(upstream), so they + cannot participate in a cycle + - VC - multiple binds at the same time - !!! more analysis needed + - PR - should only be able to have one per connection (that's a problem + for later, maybe even needs a dedicated upstream connection) + - TXN - ??? probably same situation as PR + - or if we have a queue for pending Bers on the server, we not need to suspend + clients, upstream is only chosen if the queue is free or there is a reason + to send it to that particular upstream (multi-stage bind/VC, PR, ...), but + that still makes it possible for a client to exhaust all our memory by + sending requests (VC or other ones bound to a slow upstream or by not + reading the responses at all) diff --git a/include/ldap_defaults.h b/include/ldap_defaults.h index 9468049a1c..6d7f0f16c0 100644 --- a/include/ldap_defaults.h +++ b/include/ldap_defaults.h @@ -63,4 +63,9 @@ /* dn of the default "monitor" subentry */ #define SLAPD_MONITOR_DN "cn=Monitor" +/* + * LLOADD DEFINITIONS + */ +#define LLOADD_DEFAULT_CONFIGFILE LDAP_SYSCONFDIR LDAP_DIRSEP "lloadd.conf" + #endif /* _LDAP_CONFIG_H */ diff --git a/servers/Makefile.in b/servers/Makefile.in index 4fbb1ad1ea..95ef69c4d0 100644 --- a/servers/Makefile.in +++ b/servers/Makefile.in @@ -13,5 +13,5 @@ ## top-level directory of the distribution or, alternatively, at ## . -SUBDIRS= slapd +SUBDIRS= slapd lloadd diff --git a/servers/lloadd/Makefile.in b/servers/lloadd/Makefile.in new file mode 100644 index 0000000000..768d3e71e1 --- /dev/null +++ b/servers/lloadd/Makefile.in @@ -0,0 +1,74 @@ +# Makefile.in for Load Balancer +# $OpenLDAP$ +## This work is part of OpenLDAP Software . +## +## Copyright 1998-2020 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## . + +PROGRAMS = lloadd +XPROGRAMS = slloadd + +XSRCS = version.c + +NT_SRCS = nt_svc.c +NT_OBJS = nt_svc.o ../../libraries/liblutil/slapdmsg.res + +SRCS = main.c globals.c config.c connection.c client.c daemon.c \ + ch_malloc.c init.c user.c sl_malloc.c value.c \ + libevent_support.c \ + $(@PLAT@_SRCS) + +OBJS = $(patsubst %.c,%.o,$(SRCS)) $(@PLAT@_OBJS) + +LDAP_INCDIR= ../../include -I$(srcdir) +LDAP_LIBDIR= ../../libraries + +BUILD_OPT = "--enable-balancer" +BUILD_SRV = @BUILD_BALANCER@ + +all-local-srv: $(PROGRAMS) + +# $(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) + +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) + +install-local-srv: FORCE + -$(MKDIR) $(DESTDIR)$(libexecdir) + @-$(INSTALL) -m 700 -d $(DESTDIR)$(localstatedir)/openldap-lloadd + @( \ + for prg in $(PROGRAMS); do \ + $(LTINSTALL) $(INSTALLFLAGS) $(STRIP) -m 755 $$prg$(EXEEXT) \ + $(DESTDIR)$(libexecdir); \ + done \ + ) + diff --git a/servers/lloadd/ch_malloc.c b/servers/lloadd/ch_malloc.c new file mode 120000 index 0000000000..6b13fb55a1 --- /dev/null +++ b/servers/lloadd/ch_malloc.c @@ -0,0 +1 @@ +../slapd/ch_malloc.c \ No newline at end of file diff --git a/servers/lloadd/client.c b/servers/lloadd/client.c new file mode 100644 index 0000000000..13926891d0 --- /dev/null +++ b/servers/lloadd/client.c @@ -0,0 +1,103 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2020 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "portable.h" + +#include +#include +#include +#include +#include + +#include "lutil.h" +#include "slap.h" + +static void client_destroy( Connection *c ); + +static void +client_read_cb( evutil_socket_t s, short what, void *arg ) +{ + Connection *c = arg; + Debug( LDAP_DEBUG_CONNS, "client_read_cb: " + "connection %lu ready to read\n", + c->c_connid ); + evutil_closesocket( s ); + client_destroy( c ); +} + +static void +client_write_cb( evutil_socket_t s, short what, void *arg ) +{ + Connection *c = arg; +} + +Connection * +client_init( + ber_socket_t s, + Listener *listener, + const char *peername, + struct event_base *base, + int flags ) +{ + Connection *c; + struct event *event; + + assert( listener != NULL ); + + c = connection_init( s, peername, flags ); + + event = event_new( base, s, EV_READ|EV_PERSIST, client_read_cb, c ); + if ( !event ) { + Debug( LDAP_DEBUG_ANY, "Read event could not be allocated\n" ); + goto fail; + } + event_add( event, NULL ); + c->c_read_event = event; + + event = event_new( base, s, EV_WRITE, client_write_cb, c ); + if ( !event ) { + Debug( LDAP_DEBUG_ANY, "Write event could not be allocated\n" ); + goto fail; + } + /* We only register the write event when we have data pending */ + c->c_write_event = event; + + c->c_private = listener; + + return c; +fail: + 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 ); + } + connection_destroy( c ); + return NULL; +} + +static void +client_destroy( Connection *c ) +{ + event_del( c->c_read_event ); + event_free( c->c_read_event ); + + event_del( c->c_write_event ); + event_free( c->c_write_event ); + + connection_destroy( c ); +} diff --git a/servers/lloadd/config.c b/servers/lloadd/config.c new file mode 100644 index 0000000000..0c64b3f31a --- /dev/null +++ b/servers/lloadd/config.c @@ -0,0 +1,2374 @@ +/* config.c - configuration file handling routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2020 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifndef S_ISREG +#define S_ISREG(m) ( ((m) & _S_IFMT ) == _S_IFREG ) +#endif + +#include "slap.h" +#include "lutil.h" +#include "lutil_ldap.h" +#include "config.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 + */ +slap_mask_t global_allows = 0; +slap_mask_t global_disallows = 0; +int global_gentlehup = 0; +int global_idletimeout = 0; +char *global_host = NULL; + +static FILE *logfile; +static char *logfileName; + +ber_len_t sockbuf_max_incoming = SLAP_SB_MAX_INCOMING_DEFAULT; +ber_len_t sockbuf_max_incoming_auth = SLAP_SB_MAX_INCOMING_AUTH; + +char *slapd_pid_file = NULL; +char *slapd_args_file = NULL; + +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; +#ifdef LDAP_TCP_BUFFER +static ConfigDriver config_tcp_buffer; +#endif /* LDAP_TCP_BUFFER */ +static ConfigDriver config_restrict; +static ConfigDriver config_loglevel; +static ConfigDriver config_include; +#ifdef HAVE_TLS +static ConfigDriver config_tls_option; +static ConfigDriver config_tls_config; +#endif + +enum { + CFG_ACL = 1, + CFG_BACKEND, + CFG_DATABASE, + 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_CONCUR, + CFG_THREADS, + CFG_LOGFILE, + CFG_MIRRORMODE, + CFG_LTHREADS, + CFG_THREADQS, + CFG_TLS_ECNAME, + CFG_TLS_CACERT, + CFG_TLS_CERT, + CFG_TLS_KEY, + + CFG_LAST +}; + +/* alphabetical ordering */ + +static ConfigTable config_back_cf_table[] = { + /* This attr is read-only */ + { "", "", 0, 0, 0, + ARG_MAGIC, + &config_fname, + }, + { "argsfile", "file", 2, 2, 0, + ARG_STRING, + &slapd_args_file, + }, + { "concurrency", "level", 2, 2, 0, + ARG_INT|ARG_MAGIC|CFG_CONCUR, + &config_generic, + }, + { "database", "type", 2, 2, 0, + ARG_MAGIC|CFG_DATABASE, + &config_generic, + }, + { "gentlehup", "on|off", 2, 2, 0, +#ifdef SIGHUP + ARG_ON_OFF, + &global_gentlehup, +#else + ARG_IGNORED, + NULL, +#endif + }, + { "idletimeout", "timeout", 2, 2, 0, + ARG_INT, + &global_idletimeout, + }, + { "include", "file", 2, 2, 0, + ARG_MAGIC, + &config_include, + }, + { "listener-threads", "count", 2, 0, 0, + ARG_UINT|ARG_MAGIC|CFG_LTHREADS, + &config_generic, + }, + { "logfile", "file", 2, 2, 0, + ARG_STRING|ARG_MAGIC|CFG_LOGFILE, + &config_generic, + }, + { "loglevel", "level", 2, 0, 0, + ARG_MAGIC, + &config_loglevel, + }, + { "pidfile", "file", 2, 2, 0, + ARG_STRING, + &slapd_pid_file, + }, + { "restrict", "op_list", 2, 0, 0, + ARG_MAGIC, + &config_restrict, + }, + { "sockbuf_max_incoming", "max", 2, 2, 0, + ARG_BER_LEN_T, + &sockbuf_max_incoming, + }, + { "sockbuf_max_incoming_auth", "max", 2, 2, 0, + ARG_BER_LEN_T, + &sockbuf_max_incoming_auth, + }, + { "tcp-buffer", "[listener=] [{read|write}=]size", 0, 0, 0, +#ifdef LDAP_TCP_BUFFER + ARG_MAGIC, + &config_tcp_buffer, +#else + ARG_IGNORED, + NULL, +#endif + }, + { "threads", "count", 2, 2, 0, + ARG_INT|ARG_MAGIC|CFG_THREADS, + &config_generic, + }, + { "threadqueues", "count", 2, 2, 0, + ARG_INT|ARG_MAGIC|CFG_THREADQS, + &config_generic, + }, + { "TLSCACertificate", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CACERT|ARG_BINARY|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + }, + { "TLSCACertificateFile", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CA_FILE|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + }, + { "TLSCACertificatePath", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CA_PATH|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + }, + { "TLSCertificate", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CERT|ARG_BINARY|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + }, + { "TLSCertificateFile", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CERT_FILE|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + }, + { "TLSCertificateKey", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_KEY|ARG_BINARY|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + }, + { "TLSCertificateKeyFile", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CERT_KEY|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + }, + { "TLSCipherSuite", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_CIPHER|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + }, + { "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 + }, + { "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 + }, + { "TLSRandFile", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_RAND|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + }, + { "TLSVerifyClient", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_VERIFY|ARG_STRING|ARG_MAGIC, + &config_tls_config, +#else + ARG_IGNORED, + NULL, +#endif + }, + { "TLSDHParamFile", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_DH_FILE|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + }, + { "TLSECName", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_ECNAME|ARG_STRING|ARG_MAGIC, + &config_tls_option, +#else + ARG_IGNORED, + NULL, +#endif + }, + { "TLSProtocolMin", NULL, 2, 2, 0, +#ifdef HAVE_TLS + CFG_TLS_PROTOCOL_MIN|ARG_STRING|ARG_MAGIC, + &config_tls_config, +#else + ARG_IGNORED, + NULL, +#endif + }, + + { NULL, NULL, 0, 0, 0, ARG_IGNORED, NULL } +}; + +static int +config_generic( ConfigArgs *c ) +{ + switch ( c->type ) { + case CFG_CONCUR: + ldap_pvt_thread_set_concurrency( c->value_int ); + break; + + case CFG_THREADS: + if ( c->value_int < 2 ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "threads=%d smaller than minimum value 2", + c->value_int ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + + } else if ( c->value_int > 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_int, 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_int ); + connection_pool_max = c->value_int; /* save for reference */ + break; + + case CFG_THREADQS: + if ( c->value_int < 1 ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "threadqueues=%d smaller than minimum value 1", + c->value_int ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + if ( slapMode & SLAP_SERVER_MODE ) + ldap_pvt_thread_pool_queues( &connection_pool, c->value_int ); + connection_pool_queues = c->value_int; /* save for reference */ + break; + + case CFG_LTHREADS: { + int mask = 0; + /* use a power of two */ + while ( c->value_uint > 1 ) { + c->value_uint >>= 1; + mask <<= 1; + mask |= 1; + } + slapd_daemon_mask = mask; + slapd_daemon_threads = mask + 1; + } break; + + case CFG_LOGFILE: { + if ( logfileName ) ch_free( logfileName ); + logfileName = c->value_string; + logfile = fopen( logfileName, "w" ); + if ( logfile ) lutil_debug_file( logfile ); + } break; + + default: + Debug( LDAP_DEBUG_ANY, "%s: unknown CFG_TYPE %d.\n", + c->log, c->type ); + return 1; + } + return 0; +} + +static int +config_fname( ConfigArgs *c ) +{ + return 0; +} + +/* + * [listener=] [{read|write}=] + */ + +#ifdef LDAP_TCP_BUFFER +static BerVarray tcp_buffer; +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, + Listener **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( url, &lud ) ) { + rc = LDAP_INVALID_SYNTAX; + goto done; + } + + *l = 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; +} + +static int +tcp_buffer_unparse( int size, int rw, Listener *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; + Listener *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; + Listener **ll = slapd_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; + + 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 slap_verbmasks *loglevel_ops; + +static int +loglevel_init( void ) +{ + slap_verbmasks lo[] = { + { BER_BVC("Any"), (slap_mask_t)LDAP_DEBUG_ANY }, + { BER_BVC("Trace"), LDAP_DEBUG_TRACE }, + { BER_BVC("Packets"), LDAP_DEBUG_PACKETS }, + { BER_BVC("Args"), LDAP_DEBUG_ARGS }, + { BER_BVC("Conns"), LDAP_DEBUG_CONNS }, + { BER_BVC("BER"), LDAP_DEBUG_BER }, + { BER_BVC("Filter"), LDAP_DEBUG_FILTER }, + { BER_BVC("Config"), LDAP_DEBUG_CONFIG }, + { BER_BVC("ACL"), LDAP_DEBUG_ACL }, + { BER_BVC("Stats"), LDAP_DEBUG_STATS }, + { BER_BVC("Stats2"), LDAP_DEBUG_STATS2 }, + { BER_BVC("Shell"), LDAP_DEBUG_SHELL }, + { BER_BVC("Parse"), LDAP_DEBUG_PARSE }, + { BER_BVC("Sync"), LDAP_DEBUG_SYNC }, + { BER_BVC("None"), LDAP_DEBUG_NONE }, + { BER_BVNULL, 0 } + }; + + return slap_verbmasks_init( &loglevel_ops, lo ); +} + +static void +loglevel_destroy( void ) +{ + if ( loglevel_ops ) { + (void)slap_verbmasks_destroy( loglevel_ops ); + } + loglevel_ops = NULL; +} + +int +str2loglevel( const char *s, int *l ) +{ + int i; + + if ( loglevel_ops == NULL ) { + loglevel_init(); + } + + i = verb_to_mask( s, loglevel_ops ); + + if ( BER_BVISNULL( &loglevel_ops[i].word ) ) { + return -1; + } + + *l = loglevel_ops[i].mask; + + return 0; +} + +int +loglevel2bvarray( int l, BerVarray *bva ) +{ + if ( loglevel_ops == NULL ) { + loglevel_init(); + } + + if ( l == 0 ) { + struct berval bv = BER_BVC("0"); + return value_add_one( bva, &bv ); + } + + return mask_to_verbs( loglevel_ops, l, bva ); +} + +int +loglevel_print( FILE *out ) +{ + int i; + + if ( loglevel_ops == NULL ) { + loglevel_init(); + } + + fprintf( out, "Installed log subsystems:\n\n" ); + for ( i = 0; !BER_BVISNULL( &loglevel_ops[i].word ); i++ ) { + unsigned mask = loglevel_ops[i].mask & 0xffffffffUL; + fprintf( out, + ( mask == ( (slap_mask_t)-1 & 0xffffffffUL ) ? + "\t%-30s (-1, 0xffffffff)\n" : + "\t%-30s (%u, 0x%x)\n" ), + loglevel_ops[i].word.bv_val, mask, mask ); + } + + fprintf( out, + "\nNOTE: custom log subsystems may be later installed " + "by specific code\n\n" ); + + return 0; +} + +static int config_syslog; + +static int +config_loglevel( ConfigArgs *c ) +{ + int i; + + if ( loglevel_ops == NULL ) { + loglevel_init(); + } + + if ( c->op == SLAP_CONFIG_EMIT ) { + /* Get default or commandline slapd setting */ + if ( ldap_syslog && !config_syslog ) config_syslog = ldap_syslog; + return loglevel2bvarray( config_syslog, &c->rvalue_vals ); + + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( !c->line ) { + config_syslog = 0; + } else { + i = verb_to_mask( c->line, loglevel_ops ); + config_syslog &= ~loglevel_ops[i].mask; + } + if ( slapMode & SLAP_SERVER_MODE ) { + ldap_syslog = config_syslog; + } + return 0; + } + + for ( i = 1; i < c->argc; i++ ) { + int level; + + if ( isdigit( (unsigned char)c->argv[i][0] ) || c->argv[i][0] == '-' ) { + if ( lutil_atoix( &level, c->argv[i], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "<%s> unable to parse level", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->argv[i] ); + return 1; + } + } else { + if ( str2loglevel( c->argv[i], &level ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), "<%s> unknown level", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->argv[i] ); + return 1; + } + } + /* Explicitly setting a zero clears all the levels */ + if ( level ) + config_syslog |= level; + else + config_syslog = 0; + } + if ( slapMode & SLAP_SERVER_MODE ) { + ldap_syslog = config_syslog; + } + 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 = 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; +} + +#ifdef HAVE_TLS +static int +config_tls_cleanup( ConfigArgs *c ) +{ + int rc = 0; + + if ( slap_tls_ld ) { + int opt = 1; + + ldap_pvt_tls_ctx_free( slap_tls_ctx ); + slap_tls_ctx = NULL; + + /* Force new ctx to be created */ + rc = ldap_pvt_tls_set_option( + slap_tls_ld, LDAP_OPT_X_TLS_NEWCTX, &opt ); + if ( rc == 0 ) { + /* The ctx's refcount is bumped up here */ + ldap_pvt_tls_get_option( + slap_tls_ld, LDAP_OPT_X_TLS_CTX, &slap_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, rc; + int berval = 0; + LDAP *ld = slap_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 ); + } else if ( c->op == LDAP_MOD_DELETE ) { + config_push_cleanup( c, config_tls_cleanup ); + return ldap_pvt_tls_set_option( ld, flag, NULL ); + } + if ( !berval ) ch_free( c->value_string ); + config_push_cleanup( c, config_tls_cleanup ); + rc = ldap_pvt_tls_set_option( + ld, flag, berval ? (void *)&c->value_bv : (void *)c->argv[1] ); + if ( berval ) ch_free( c->value_bv.bv_val ); + return rc; +} + +/* 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 slap_tls_get_config( slap_tls_ld, flag, &c->value_string ); + } else if ( c->op == LDAP_MOD_DELETE ) { + int i = 0; + config_push_cleanup( c, config_tls_cleanup ); + return ldap_pvt_tls_set_option( slap_tls_ld, flag, &i ); + } + ch_free( c->value_string ); + config_push_cleanup( c, config_tls_cleanup ); + 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( slap_tls_ld, flag, &i ); + } else { + return ldap_pvt_tls_config( slap_tls_ld, flag, c->argv[1] ); + } +} +#endif + +void +init_config_argv( ConfigArgs *c ) +{ + c->argv = ch_calloc( ARGS_STEP + 1, sizeof(*c->argv) ); + c->argv_size = ARGS_STEP + 1; +} + +ConfigTable * +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; + } + return Conf + i; +} + +int +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 +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 +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 = config_check_vals( Conf, c, 0 ); + if ( rc ) return rc; + return config_set_vals( Conf, c ); +} + +int +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; + 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 ( 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 = config_find_keyword( cft, c ); + if ( ct ) { + c->table = Cft_Global; + rc = 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 +read_config( const char *fname, const char *dir ) +{ + if ( !fname ) fname = LLOADD_DEFAULT_CONFIGFILE; + + cfn = ch_calloc( 1, sizeof(ConfigFile) ); + + return read_config_file( fname, 0, NULL, config_back_cf_table ); +} + +/* restrictops, allows, disallows, requires, loglevel */ + +int +bverb_to_mask( struct berval *bword, slap_verbmasks *v ) +{ + int i; + for ( i = 0; !BER_BVISNULL( &v[i].word ); i++ ) { + if ( !ber_bvstrcasecmp( bword, &v[i].word ) ) break; + } + return i; +} + +int +verb_to_mask( const char *word, slap_verbmasks *v ) +{ + struct berval bword; + ber_str2bv( word, 0, 0, &bword ); + return bverb_to_mask( &bword, v ); +} + +int +verbs_to_mask( int argc, char *argv[], slap_verbmasks *v, slap_mask_t *m ) +{ + int i, j; + for ( i = 1; i < argc; i++ ) { + j = verb_to_mask( argv[i], v ); + if ( BER_BVISNULL( &v[j].word ) ) return i; + while ( !v[j].mask ) + j--; + *m |= v[j].mask; + } + return 0; +} + +/* Mask keywords that represent multiple bits should occur before single + * bit keywords in the verbmasks array. + */ +int +mask_to_verbs( slap_verbmasks *v, slap_mask_t m, BerVarray *bva ) +{ + int i, rc = 1; + + if ( m ) { + for ( i = 0; !BER_BVISNULL( &v[i].word ); i++ ) { + if ( !v[i].mask ) continue; + if ( (m & v[i].mask) == v[i].mask ) { + value_add_one( bva, &v[i].word ); + rc = 0; + m ^= v[i].mask; + if ( !m ) break; + } + } + } + return rc; +} + +int +slap_verbmasks_init( slap_verbmasks **vp, slap_verbmasks *v ) +{ + int i; + + assert( *vp == NULL ); + + for ( i = 0; !BER_BVISNULL( &v[i].word ); i++ ) /* EMPTY */; + + *vp = ch_calloc( i + 1, sizeof(slap_verbmasks) ); + + for ( i = 0; !BER_BVISNULL( &v[i].word ); i++ ) { + ber_dupbv( &(*vp)[i].word, &v[i].word ); + *( (slap_mask_t *)&(*vp)[i].mask ) = v[i].mask; + } + + BER_BVZERO( &(*vp)[i].word ); + + return 0; +} + +int +slap_verbmasks_destroy( slap_verbmasks *v ) +{ + int i; + + assert( v != NULL ); + + for ( i = 0; !BER_BVISNULL( &v[i].word ); i++ ) { + ch_free( v[i].word.bv_val ); + } + + ch_free( v ); + + return 0; +} + +int +config_push_cleanup( ConfigArgs *ca, ConfigDriver *cleanup ) +{ + /* Stub, cleanups only run in online config */ + return 0; +} + +static slap_verbmasks tlskey[] = { + { BER_BVC("no"), SB_TLS_OFF }, + { BER_BVC("yes"), SB_TLS_ON }, + { BER_BVC("critical"), SB_TLS_CRITICAL }, + { 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 } +}; + +static slap_verbmasks versionkey[] = { + { BER_BVC("2"), LDAP_VERSION2 }, + { BER_BVC("3"), LDAP_VERSION3 }, + { BER_BVNULL, 0 } +}; + +int +slap_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 int +slap_sb_uri( + struct berval *val, + void *bcp, + slap_cf_aux_table *tab0, + const char *tabmsg, + int unparse ) +{ + slap_bindconf *bc = bcp; + if ( unparse ) { + if ( bc->sb_uri.bv_len >= val->bv_len ) return -1; + val->bv_len = bc->sb_uri.bv_len; + AC_MEMCPY( val->bv_val, bc->sb_uri.bv_val, val->bv_len ); + } else { + bc->sb_uri = *val; +#ifdef HAVE_TLS + if ( ldap_is_ldaps_url( val->bv_val ) ) bc->sb_tls_do_init = 1; +#endif + } + return 0; +} + +static slap_cf_aux_table bindkey[] = { + { BER_BVC("uri="), 0, 'x', 1, slap_sb_uri }, + { BER_BVC("version="), offsetof(slap_bindconf, sb_version), 'i', 0, versionkey }, + { 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 *)slap_keepalive_parse }, +#ifdef HAVE_TLS + { BER_BVC("starttls="), offsetof(slap_bindconf, sb_tls), 'i', 0, tlskey }, + { 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 +slap_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; + slap_cf_aux_table_parse_x *func = + (slap_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 +slap_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; + slap_cf_aux_table_parse_x *func = + (slap_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 +slap_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; +} + +int +bindconf_parse( const char *word, slap_bindconf *bc ) +{ + return slap_cf_aux_table_parse( word, bc, bindkey, "bind config" ); +} + +int +bindconf_unparse( slap_bindconf *bc, struct berval *bv ) +{ + return slap_cf_aux_table_unparse( bc, bv, bindkey ); +} + +void +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 +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( slap_tls_ld, LDAP_OPT_X_TLS_CACERTFILE, + &bc->sb_tls_cacert ); + if ( !bc->sb_tls_cacertdir ) + ldap_pvt_tls_get_option( slap_tls_ld, LDAP_OPT_X_TLS_CACERTDIR, + &bc->sb_tls_cacertdir ); + if ( !bc->sb_tls_cert ) + ldap_pvt_tls_get_option( + slap_tls_ld, LDAP_OPT_X_TLS_CERTFILE, &bc->sb_tls_cert ); + if ( !bc->sb_tls_key ) + ldap_pvt_tls_get_option( + slap_tls_ld, LDAP_OPT_X_TLS_KEYFILE, &bc->sb_tls_key ); + if ( !bc->sb_tls_cipher_suite ) + ldap_pvt_tls_get_option( slap_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 ) + slap_tls_get_config( slap_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 +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 +config_destroy( void ) +{ + free( line ); + if ( slapd_args_file ) free( slapd_args_file ); + if ( slapd_pid_file ) free( slapd_pid_file ); + loglevel_destroy(); +} + +/* See if the given URL (in plain and parsed form) matches + * any of the server's listener addresses. Return matching + * Listener or NULL for no match. + */ +Listener * +config_check_my_url( const char *url, LDAPURLDesc *lud ) +{ + Listener **l = slapd_get_listeners(); + int i, isMe; + + /* Try a straight compare with Listener 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( l[i]->sl_url.bv_val, &lu2 ); + 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; +} diff --git a/servers/lloadd/config.h b/servers/lloadd/config.h new file mode 100644 index 0000000000..fb1e2d0a10 --- /dev/null +++ b/servers/lloadd/config.h @@ -0,0 +1,147 @@ +/* config.h - configuration abstraction structure */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2020 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#include + +LDAP_BEGIN_DECL + +typedef struct ConfigTable { + const char *name; + const char *what; + int min_args; + int max_args; + int length; + unsigned int arg_type; + void *arg_item; +} ConfigTable; + +/* search entries are returned according to this order */ +typedef enum { + Cft_Abstract = 0, + Cft_Global, + Cft_Module, + Cft_Schema, + Cft_Backend, + Cft_Database, + Cft_Overlay, + Cft_Misc /* backend/overlay defined */ +} ConfigType; + +#define ARGS_USERLAND 0x00000fff + +/* types are enumerated, not a bitmask */ +#define ARGS_TYPES 0x0000f000 +#define ARG_INT 0x00001000 +#define ARG_LONG 0x00002000 +#define ARG_BER_LEN_T 0x00003000 +#define ARG_ON_OFF 0x00004000 +#define ARG_STRING 0x00005000 +#define ARG_BERVAL 0x00006000 +#define ARG_UINT 0x00008000 +#define ARG_ULONG 0x0000a000 +#define ARG_BINARY 0x0000b000 + +#define ARGS_SYNTAX 0xffff0000 +#define ARG_IGNORED 0x00080000 +#define ARG_PAREN 0x01000000 +#define ARG_NONZERO 0x02000000 +#define ARG_NO_INSERT 0x04000000 /* no arbitrary inserting */ +#define ARG_NO_DELETE 0x08000000 /* no runtime deletes */ +#define ARG_UNIQUE 0x10000000 +#define ARG_QUOTE 0x20000000 /* wrap with quotes before parsing */ +#define ARG_OFFSET 0x40000000 +#define ARG_MAGIC 0x80000000 + +#define ARG_BAD_CONF 0xdead0000 /* overload return values */ + +struct config_args_s; + +typedef int (ConfigDriver)( struct config_args_s *c ); + +struct config_reply_s { + int err; + char msg[SLAP_TEXT_BUFLEN]; +}; + +typedef struct config_args_s { + int argc; + char **argv; + int argv_size; + char *line; + char *tline; + const char *fname; + int lineno; + int linelen; + char log[MAXPATHLEN + STRLENOF(": line ") + + LDAP_PVT_INTTYPE_CHARS(unsigned long)]; +#define cr_msg reply.msg + ConfigReply reply; + int depth; + int valx; /* multi-valued value index */ + /* parsed first val for simple cases */ + union { + int v_int; + unsigned v_uint; + long v_long; + size_t v_ulong; + ber_len_t v_ber_t; + char *v_string; + struct berval v_bv; + } values; + /* return values for emit mode */ + BerVarray rvalue_vals; + BerVarray rvalue_nvals; +#define SLAP_CONFIG_EMIT 0x2000 /* emit instead of set */ +#define SLAP_CONFIG_ADD 0x4000 /* config file add vs LDAP add */ + int op; + int type; /* ConfigTable.arg_type & ARGS_USERLAND */ + void *ca_private; /* anything */ +#ifndef SLAP_CONFIG_CLEANUP_MAX +#define SLAP_CONFIG_CLEANUP_MAX 16 +#endif + ConfigDriver *cleanups[SLAP_CONFIG_CLEANUP_MAX]; + ConfigType table; /* which config table did we come from */ + int num_cleanups; +} ConfigArgs; + +#define value_int values.v_int +#define value_uint values.v_uint +#define value_long values.v_long +#define value_ulong values.v_ulong +#define value_ber_t values.v_ber_t +#define value_string values.v_string +#define value_bv values.v_bv + +int config_fp_parse_line( ConfigArgs *c ); + +int config_set_vals( ConfigTable *ct, ConfigArgs *c ); +int config_add_vals( ConfigTable *ct, ConfigArgs *c ); + +int config_push_cleanup( ConfigArgs *c, ConfigDriver *cleanup ); + +void init_config_argv( ConfigArgs *c ); +int read_config_file( const char *fname, int depth, ConfigArgs *cf, ConfigTable *cft ); + +ConfigTable *config_find_keyword( ConfigTable *ct, ConfigArgs *c ); + +Listener *config_check_my_url( const char *url, LDAPURLDesc *lud ); + +LDAP_END_DECL + +#endif /* CONFIG_H */ diff --git a/servers/lloadd/connection.c b/servers/lloadd/connection.c new file mode 100644 index 0000000000..f1d4ea55c0 --- /dev/null +++ b/servers/lloadd/connection.c @@ -0,0 +1,148 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2020 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include +#ifdef HAVE_LIMITS_H +#include +#endif + +#include +#include +#include +#include +#include + +#include "lutil.h" +#include "slap.h" + +static ldap_pvt_thread_mutex_t conn_nextid_mutex; +static unsigned long conn_nextid = 0; + +static void +connection_assign_nextid( Connection *conn ) +{ + ldap_pvt_thread_mutex_lock( &conn_nextid_mutex ); + conn->c_connid = conn_nextid++; + ldap_pvt_thread_mutex_unlock( &conn_nextid_mutex ); +} + +void +connection_destroy( Connection *c ) +{ + assert( c ); + + ldap_pvt_thread_mutex_destroy( &c->c_io_mutex ); + ldap_pvt_thread_mutex_destroy( &c->c_mutex ); + + ber_sockbuf_free( c->c_sb ); + ch_free( c ); +} + +Connection * +connection_init( ber_socket_t s, const char *peername, int flags ) +{ + Connection *c; + + assert( peername != NULL ); + +#ifndef HAVE_TLS + assert( !(flags & CONN_IS_TLS) ); +#endif + + if ( s == AC_SOCKET_INVALID ) { + Debug( LDAP_DEBUG_ANY, "connection_init: " + "init of socket fd=%ld invalid.\n", + (long)s ); + return NULL; + } + + assert( s >= 0 ); + + c = ch_calloc( 1, sizeof(Connection) ); + + c->c_sb = ber_sockbuf_alloc(); + ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_FD, &s ); + + { + ber_len_t max = sockbuf_max_incoming; + ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_SET_MAX_INCOMING, &max ); + } + +#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 ); +#ifdef LDAP_PF_LOCAL_SENDMSG + if ( !BER_BVISEMPTY( peerbv ) ) + ber_sockbuf_ctrl( c->c_sb, LBER_SB_OPT_UNGET_BUF, peerbv ); +#endif + } 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 + +#ifdef HAVE_TLS + if ( flags & CONN_IS_TLS ) { + /* TODO: will need an asynchronous TLS implementation in libldap */ + assert(0); + c->c_is_tls = 1; + c->c_needs_tls_accept = 1; + } else { + c->c_is_tls = 0; + c->c_needs_tls_accept = 0; + } +#endif + + ldap_pvt_thread_mutex_init( &c->c_mutex ); + ldap_pvt_thread_mutex_init( &c->c_io_mutex ); + + connection_assign_nextid( c ); + + Debug( LDAP_DEBUG_CONNS, "connection_init: " + "connection connid=%lu allocated for socket fd=%d\n", + c->c_connid, s ); + + return c; +fail: + connection_destroy( c ); + return NULL; +} diff --git a/servers/lloadd/daemon.c b/servers/lloadd/daemon.c new file mode 100644 index 0000000000..3017e34b68 --- /dev/null +++ b/servers/lloadd/daemon.c @@ -0,0 +1,1400 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2015 The OpenLDAP Foundation. + * Portions Copyright 2007 by Howard Chu, Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "slap.h" +#include "ldap_pvt_thread.h" +#include "lutil.h" + +#include "ldap_rq.h" + +#ifdef HAVE_TCPD +int allow_severity = LOG_INFO; +int deny_severity = LOG_NOTICE; +#endif /* TCP Wrappers */ + +#ifdef LDAP_PF_LOCAL +#include +/* this should go in as soon as it is accepted */ +#define LDAPI_MOD_URLEXT "x-mod" +#endif /* LDAP_PF_LOCAL */ + +#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; + +#ifndef SLAPD_MAX_DAEMON_THREADS +#define SLAPD_MAX_DAEMON_THREADS 16 +#endif +int slapd_daemon_threads = 1; +int slapd_daemon_mask; + +#ifdef LDAP_TCP_BUFFER +int slapd_tcp_rmem; +int slapd_tcp_wmem; +#endif /* LDAP_TCP_BUFFER */ + +struct event_base *listener_base = NULL; +Listener **slap_listeners = NULL; +static volatile sig_atomic_t listening = 1; /* 0 when slap_listeners closed */ +static ldap_pvt_thread_t listener_tid, *daemon_tid; + +#ifndef SLAPD_LISTEN_BACKLOG +#define SLAPD_LISTEN_BACKLOG 1024 +#endif /* ! SLAPD_LISTEN_BACKLOG */ + +#define DAEMON_ID(fd) ( fd & slapd_daemon_mask ) + +static int emfile; + +static volatile int waking; +#define WAKE_DAEMON( l, w ) \ + do { \ + if ( w ) { \ + event_active( slap_daemon[l].wakeup_event, EV_WRITE, 0 ); \ + } \ + } while (0) + +volatile sig_atomic_t slapd_shutdown = 0; +volatile sig_atomic_t slapd_gentle_shutdown = 0; +volatile sig_atomic_t slapd_abrupt_shutdown = 0; + +#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 slap_daemon_st { + ldap_pvt_thread_mutex_t sd_mutex; + + struct event_base *base; + struct event *wakeup_event; +} slap_daemon_st; + +static slap_daemon_st slap_daemon[SLAPD_MAX_DAEMON_THREADS]; + +static void daemon_wakeup_cb( evutil_socket_t sig, short what, void *arg ); + +/* + * Remove the descriptor from daemon control + */ +void +slapd_remove( ber_socket_t s, Sockbuf *sb, int wasactive, int wake, int locked ) +{ + int id = DAEMON_ID(s); + + if ( !locked ) ldap_pvt_thread_mutex_lock( &slap_daemon[id].sd_mutex ); + + if ( sb ) ber_sockbuf_free( sb ); + + /* If we ran out of file descriptors, we dropped a listener from + * the select() loop. Now that we're removing a session from our + * control, we can try to resume a dropped listener to use. + */ + if ( emfile && listening ) { + int i; + for ( i = 0; slap_listeners[i] != NULL; i++ ) { + Listener *lr = slap_listeners[i]; + + if ( lr->sl_sd == AC_SOCKET_INVALID ) continue; + if ( lr->sl_sd == s ) continue; + if ( lr->sl_mute ) { + lr->sl_mute = 0; + emfile--; + if ( DAEMON_ID(lr->sl_sd) != id ) + WAKE_DAEMON( DAEMON_ID(lr->sl_sd), wake ); + break; + } + } + /* Walked the entire list without enabling anything; emfile + * counter is stale. Reset it. + */ + if ( slap_listeners[i] == NULL ) emfile = 0; + } + ldap_pvt_thread_mutex_unlock( &slap_daemon[id].sd_mutex ); + WAKE_DAEMON( id, wake || slapd_gentle_shutdown == 2 ); +} + +static void +slapd_close( ber_socket_t s ) +{ + Debug( LDAP_DEBUG_CONNS, "slapd_close: " + "closing %ld\n", + (long)s ); + tcp_close( s ); +} + +static void +slap_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 +slap_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, "slap_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, "slap_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, "slap_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, "slap_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: + slap_free_listener_addresses(*sal); + return -1; +} + +static int +slap_open_listener( const char *url, int *listeners, int *cur ) +{ + int num, tmp, rc; + Listener l; + Listener *li; + LDAPURLDesc *lud; + 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 */ + + rc = ldap_url_parse( url, &lud ); + + if ( rc != LDAP_URL_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "slap_open_listener: " + "listen URL \"%s\" parse error=%d\n", + url, rc ); + return rc; + } + + 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, "slap_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 ); + + if ( !lud->lud_port ) { + lud->lud_port = l.sl_is_tls ? LDAPS_PORT : LDAP_PORT; + } +#endif /* HAVE_TLS */ + +#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 = slap_get_listener_addresses( LDAPI_SOCK, 0, &sal ); + } else { + err = slap_get_listener_addresses( lud->lud_host, 0, &sal ); + } +#else /* ! LDAP_PF_LOCAL */ + + Debug( LDAP_DEBUG_ANY, "slap_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 = slap_get_listener_addresses( NULL, port, &sal ); + } else { + err = slap_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 ) { + slap_free_listener_addresses( sal ); + return -1; + } + + /* If we got more than one address returned, we need to make space + * for it in the slap_listeners array. + */ + for ( num = 0; sal[num]; num++ ) /* empty */; + if ( num > 1 ) { + *listeners += num - 1; + slap_listeners = ch_realloc( + slap_listeners, ( *listeners + 1 ) * sizeof(Listener *) ); + } + + 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, "slap_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, "slap_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, "slap_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, "slap_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, "slap_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(Listener) ); + *li = l; + slap_listeners[*cur] = li; + (*cur)++; + sal++; + } + + slap_free_listener_addresses( psal ); + + if ( l.sl_url.bv_val == NULL ) { + Debug( LDAP_DEBUG_ANY, "slap_open_listener: " + "failed on %s\n", + url ); + return -1; + } + + Debug( LDAP_DEBUG_TRACE, "slap_open_listener: " + "listener initialized %s\n", + l.sl_url.bv_val ); + + return 0; +} + +int lloadd_inited = 0; + +int +slapd_daemon_init( const char *urls ) +{ + int i, j, n; + char **u; + + Debug( LDAP_DEBUG_ARGS, "slapd_daemon_init: %s\n", + urls ? urls : "" ); + +#ifdef HAVE_TCPD + ldap_pvt_thread_mutex_init( &sd_tcpd_mutex ); +#endif /* TCP Wrappers */ + + if ( urls == NULL ) urls = "ldap:///"; + + u = ldap_str2charray( urls, " " ); + + if ( u == NULL || u[0] == NULL ) { + Debug( LDAP_DEBUG_ANY, "slapd_daemon_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, "slapd_daemon_init: " + "listen on %s\n", + u[i] ); + } + + if ( i == 0 ) { + Debug( LDAP_DEBUG_ANY, "slapd_daemon_init: " + "no listeners to open (%s)\n", + urls ); + ldap_charray_free( u ); + return -1; + } + + Debug( LDAP_DEBUG_TRACE, "slapd_daemon_init: " + "%d listeners to open...\n", + i ); + slap_listeners = ch_malloc( ( i + 1 ) * sizeof(Listener *) ); + + for ( n = 0, j = 0; u[n]; n++ ) { + if ( slap_open_listener( u[n], &i, &j ) ) { + ldap_charray_free( u ); + return -1; + } + } + slap_listeners[j] = NULL; + + Debug( LDAP_DEBUG_TRACE, "slapd_daemon_init: " + "%d listeners opened\n", + i ); + + ldap_charray_free( u ); + + return !i; +} + +int +slapd_daemon_destroy( void ) +{ + if ( lloadd_inited ) { + int i; + + for ( i = 0; i < slapd_daemon_threads; i++ ) { + ldap_pvt_thread_mutex_destroy( &slap_daemon[i].sd_mutex ); + } + lloadd_inited = 0; +#ifdef HAVE_TCPD + ldap_pvt_thread_mutex_destroy( &sd_tcpd_mutex ); +#endif /* TCP Wrappers */ + } + + return 0; +} + +static void +close_listeners( int remove ) +{ + int l; + + if ( !listening ) return; + listening = 0; + + for ( l = 0; slap_listeners[l] != NULL; l++ ) { + Listener *lr = slap_listeners[l]; + + if ( lr->sl_sd != AC_SOCKET_INVALID ) { + int s = lr->sl_sd; + lr->sl_sd = AC_SOCKET_INVALID; + if ( remove ) slapd_remove( s, NULL, 0, 0, 0 ); + +#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 */ + + slapd_close( s ); + } + } +} + +static void +destroy_listeners( void ) +{ + Listener *lr, **ll = slap_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 ); + } + + evconnlistener_free( lr->listener ); + + free( lr ); + } + + free( slap_listeners ); + slap_listeners = NULL; +} + +static void +slap_listener( + struct evconnlistener *listener, + ber_socket_t s, + struct sockaddr *a, + int len, + void *arg ) +{ + Listener *sl = arg; + Connection *c; + Sockaddr *from = (Sockaddr *)a; +#ifdef SLAPD_RLOOKUPS + char hbuf[NI_MAXHOST]; +#endif /* SLAPD_RLOOKUPS */ + + const char *peeraddr = NULL; + /* we assume INET6_ADDRSTRLEN > INET_ADDRSTRLEN */ + char addr[INET6_ADDRSTRLEN]; +#ifdef LDAP_PF_LOCAL + char peername[MAXPATHLEN + sizeof("PATH=")]; +#ifdef LDAP_PF_LOCAL_SENDMSG + char peerbuf[8]; + struct berval peerbv = BER_BVNULL; +#endif +#elif defined(LDAP_PF_INET6) + char peername[sizeof("IP=[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535")]; +#else /* ! LDAP_PF_LOCAL && ! LDAP_PF_INET6 */ + char peername[sizeof("IP=255.255.255.255:65336")]; +#endif /* LDAP_PF_LOCAL */ + int cflag; + int tid; + char ebuf[128]; + + Debug( LDAP_DEBUG_TRACE, ">>> slap_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, "slap_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, "slap_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, "slap_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 */ + + 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: + if ( IN6_IS_ADDR_V4MAPPED( &from->sa_in6_addr.sin6_addr ) ) { +#if defined(HAVE_GETADDRINFO) && defined(HAVE_INET_NTOP) + peeraddr = inet_ntop( AF_INET, + ( (struct in_addr *)&from->sa_in6_addr.sin6_addr + .s6_addr[12] ), + addr, sizeof(addr) ); +#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + peeraddr = inet_ntoa( *( (struct in_addr *)&from->sa_in6_addr + .sin6_addr.s6_addr[12] ) ); +#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=%s:%d", peeraddr, + (unsigned)ntohs( from->sa_in6_addr.sin6_port ) ); + } else { + peeraddr = inet_ntop( AF_INET6, &from->sa_in6_addr.sin6_addr, + addr, sizeof(addr) ); + if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=[%s]:%d", peeraddr, + (unsigned)ntohs( from->sa_in6_addr.sin6_port ) ); + } + break; +#endif /* LDAP_PF_INET6 */ + + case AF_INET: { +#if defined(HAVE_GETADDRINFO) && defined(HAVE_INET_NTOP) + peeraddr = inet_ntop( + AF_INET, &from->sa_in_addr.sin_addr, addr, sizeof(addr) ); +#else /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + peeraddr = inet_ntoa( from->sa_in_addr.sin_addr ); +#endif /* ! HAVE_GETADDRINFO || ! HAVE_INET_NTOP */ + if ( !peeraddr ) peeraddr = SLAP_STRING_UNKNOWN; + sprintf( peername, "IP=%s:%d", peeraddr, + (unsigned)ntohs( from->sa_in_addr.sin_port ) ); + } break; + + default: + slapd_close( s ); + return; + } + +#ifdef HAVE_TLS + if ( sl->sl_is_tls ) cflag |= CONN_IS_TLS; +#endif + c = client_init( s, sl, peername, slap_daemon[tid].base, cflag ); + + if ( !c ) { + Debug( LDAP_DEBUG_ANY, "slap_listener: " + "client_init(%ld, %s, %s) failed.\n", + (long)s, peername, sl->sl_name.bv_val ); + slapd_close( s ); + } + + return; +} + +static void * +slap_listener_thread( void *ctx ) +{ + int rc = event_base_dispatch( listener_base ); + Debug( LDAP_DEBUG_ANY, "slap_listener_thread: " + "event loop finished: rc=%d\n", + rc ); + + return (void *)NULL; +} + +static void +listener_error_cb( struct evconnlistener *lev, void *arg ) +{ + Listener *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( &slap_daemon[0].sd_mutex ); + emfile++; + /* Stop listening until an existing session closes */ + l->sl_mute = 1; + evconnlistener_disable( lev ); + ldap_pvt_thread_mutex_unlock( &slap_daemon[0].sd_mutex ); + } 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 ); + } +} + +static int +slap_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; slap_listeners[l] != NULL; l++ ) { + if ( slap_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 ( slap_listeners[l]->sl_tcp_rmem > 0 ) { + size = slap_listeners[l]->sl_tcp_rmem; + } else if ( slapd_tcp_rmem > 0 ) { + size = slapd_tcp_rmem; + } + + if ( size > 0 ) { + optlen = sizeof(origsize); + rc = getsockopt( slap_listeners[l]->sl_sd, SOL_SOCKET, + SO_RCVBUF, (void *)&origsize, &optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "slap_listener_activate: " + "getsockopt(SO_RCVBUF) failed errno=%d (%s)\n", + err, AC_STRERROR_R( err, ebuf, sizeof(ebuf) ) ); + } + + optlen = sizeof(size); + rc = setsockopt( slap_listeners[l]->sl_sd, SOL_SOCKET, + SO_RCVBUF, (const void *)&size, optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "slapd_listener_activate: " + "setsockopt(SO_RCVBUF) failed errno=%d (%s)\n", + err, sock_errstr( err, ebuf, sizeof(ebuf) ) ); + } + + optlen = sizeof(realsize); + rc = getsockopt( slap_listeners[l]->sl_sd, SOL_SOCKET, + SO_RCVBUF, (void *)&realsize, &optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "slapd_listener_activate: " + "getsockopt(SO_RCVBUF) failed errno=%d (%s)\n", + err, sock_errstr( err, ebuf, sizeof(ebuf) ) ); + } + + Debug( LDAP_DEBUG_ANY, "slapd_listener_activate: " + "url=%s (#%d) RCVBUF original size=%d requested " + "size=%d real size=%d\n", + slap_listeners[l]->sl_url.bv_val, l, origsize, size, + realsize ); + } + + size = 0; + if ( slap_listeners[l]->sl_tcp_wmem > 0 ) { + size = slap_listeners[l]->sl_tcp_wmem; + } else if ( slapd_tcp_wmem > 0 ) { + size = slapd_tcp_wmem; + } + + if ( size > 0 ) { + optlen = sizeof(origsize); + rc = getsockopt( slap_listeners[l]->sl_sd, SOL_SOCKET, + SO_SNDBUF, (void *)&origsize, &optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "slapd_listener_activate: " + "getsockopt(SO_SNDBUF) failed errno=%d (%s)\n", + err, sock_errstr( err, ebuf, sizeof(ebuf) ) ); + } + + optlen = sizeof(size); + rc = setsockopt( slap_listeners[l]->sl_sd, SOL_SOCKET, + SO_SNDBUF, (const void *)&size, optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "slapd_listener_activate: " + "setsockopt(SO_SNDBUF) failed errno=%d (%s)\n", + err, sock_errstr( err, ebuf, sizeof(ebuf) ) ); + } + + optlen = sizeof(realsize); + rc = getsockopt( slap_listeners[l]->sl_sd, SOL_SOCKET, + SO_SNDBUF, (void *)&realsize, &optlen ); + + if ( rc ) { + int err = sock_errno(); + Debug( LDAP_DEBUG_ANY, "slapd_listener_activate: " + "getsockopt(SO_SNDBUF) failed errno=%d (%s)\n", + err, sock_errstr( err, ebuf, sizeof(ebuf) ) ); + } + + Debug( LDAP_DEBUG_ANY, "slapd_listener_activate: " + "url=%s (#%d) SNDBUF original size=%d requested " + "size=%d real size=%d\n", + slap_listeners[l]->sl_url.bv_val, l, origsize, size, + realsize ); + } + } +#endif /* LDAP_TCP_BUFFER */ + + slap_listeners[l]->sl_busy = 1; + listener = evconnlistener_new( listener_base, slap_listener, + slap_listeners[l], LEV_OPT_THREADSAFE, SLAPD_LISTEN_BACKLOG, + slap_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 = slap_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 = slap_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, "slap_listener_activate: " + "Attempt to listen to 0.0.0.0 failed, " + "already listening on ::, assuming IPv4 " + "included\n" ); + slapd_close( slap_listeners[l]->sl_sd ); + slap_listeners[l]->sl_sd = AC_SOCKET_INVALID; + continue; + } + } + } +#endif /* LDAP_PF_INET6 */ + Debug( LDAP_DEBUG_ANY, "slap_listener_activate: " + "listen(%s, 5) failed errno=%d (%s)\n", + slap_listeners[l]->sl_url.bv_val, err, + sock_errstr( err, ebuf, sizeof(ebuf) ) ); + return -1; + } + + slap_listeners[l]->base = listener_base; + slap_listeners[l]->listener = listener; + evconnlistener_set_error_cb( listener, listener_error_cb ); + } + + rc = ldap_pvt_thread_create( + &listener_tid, 0, slap_listener_thread, slap_listeners[l] ); + + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "slap_listener_activate(%d): " + "submit failed (%d)\n", + slap_listeners[l]->sl_sd, rc ); + } + return rc; +} + +static void * +slapd_daemon_task( void *ptr ) +{ + int rc; + int tid = (ldap_pvt_thread_t *)ptr - daemon_tid; + struct event_base *base; + struct event *event; + + base = event_base_new(); + if ( !base ) { + Debug( LDAP_DEBUG_ANY, "slapd_daemon_task: " + "failed to acquire event base\n" ); + return (void *)-1; + } + slap_daemon[tid].base = base; + + event = event_new( base, -1, EV_WRITE, daemon_wakeup_cb, ptr ); + if ( !event ) { + Debug( LDAP_DEBUG_ANY, "slapd_daemon_task: " + "failed to set up the wakeup event\n" ); + return (void *)-1; + } + event_add( event, NULL ); + slap_daemon[tid].wakeup_event = event; + + /* run */ + rc = event_base_dispatch( base ); + Debug( LDAP_DEBUG_ANY, "slapd_daemon_task: " + "Daemon %d, event loop finished: rc=%d\n", + tid, rc ); + + if ( !slapd_gentle_shutdown ) { + slapd_abrupt_shutdown = 1; + } + + return NULL; +} + +int +slapd_daemon( struct event_base *daemon_base ) +{ + int i, rc; + + assert( daemon_base != NULL ); + + if ( slapd_daemon_threads > SLAPD_MAX_DAEMON_THREADS ) + slapd_daemon_threads = SLAPD_MAX_DAEMON_THREADS; + + daemon_tid = + ch_malloc( slapd_daemon_threads * sizeof(ldap_pvt_thread_t) ); + + if ( (rc = slap_listener_activate()) != 0 ) { + return rc; + } + + for ( i = 0; i < slapd_daemon_threads; i++ ) { + ldap_pvt_thread_mutex_init( &slap_daemon[i].sd_mutex ); + /* threads that handle client and upstream sockets */ + rc = ldap_pvt_thread_create( + &daemon_tid[i], 0, slapd_daemon_task, &daemon_tid[i] ); + + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "lloadd startup: " + "listener ldap_pvt_thread_create failed (%d)\n", + rc ); + return rc; + } + } + + lloadd_inited = 1; + 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 ); + close_listeners( 0 ); + + /* wait for the listener threads to complete */ + destroy_listeners(); + + for ( i = 0; i < slapd_daemon_threads; i++ ) + ldap_pvt_thread_join( daemon_tid[i], (void *)NULL ); + + 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 ); + + ch_free( daemon_tid ); + daemon_tid = NULL; + + slapd_daemon_destroy(); + + 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 ); + if ( slapd_shutdown ) { + event_base_loopexit( slap_daemon[tid].base, NULL ); + } +} + +void +slap_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 < slapd_daemon_threads; i++ ) { + WAKE_DAEMON( i, 1 ); + } + event_base_loopexit( daemon_base, NULL ); + + errno = save_errno; +} + +Listener ** +slapd_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 slap_listeners; +} + +/* Reject all incoming requests */ +void +slap_suspend_listeners( void ) +{ + int i; + for ( i = 0; slap_listeners[i]; i++ ) { + slap_listeners[i]->sl_mute = 1; + evconnlistener_disable( slap_listeners[i]->listener ); + listen( slap_listeners[i]->sl_sd, 0 ); + } +} + +/* Resume after a suspend */ +void +slap_resume_listeners( void ) +{ + int i; + for ( i = 0; slap_listeners[i]; i++ ) { + slap_listeners[i]->sl_mute = 0; + listen( slap_listeners[i]->sl_sd, SLAPD_LISTEN_BACKLOG ); + evconnlistener_enable( slap_listeners[i]->listener ); + } +} diff --git a/servers/lloadd/design.md b/servers/lloadd/design.md new file mode 120000 index 0000000000..757e340177 --- /dev/null +++ b/servers/lloadd/design.md @@ -0,0 +1 @@ +../../doc/devel/lloadd/design.md \ No newline at end of file diff --git a/servers/lloadd/globals.c b/servers/lloadd/globals.c new file mode 120000 index 0000000000..3277f28f86 --- /dev/null +++ b/servers/lloadd/globals.c @@ -0,0 +1 @@ +../slapd/globals.c \ No newline at end of file diff --git a/servers/lloadd/init.c b/servers/lloadd/init.c new file mode 100644 index 0000000000..07bd849d30 --- /dev/null +++ b/servers/lloadd/init.c @@ -0,0 +1,144 @@ +/* init.c - initialize various things */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2020 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include + +#include +#include +#include + +#include "slap.h" +#include "lber_pvt.h" + +#include "ldap_rq.h" + +/* + * 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; + +static const char *slap_name = NULL; +int slapMode = SLAP_UNDEFINED_MODE; + +int +slap_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 ); + + slap_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 ); + + 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 +slap_destroy( void ) +{ + int rc = LDAP_SUCCESS; + + Debug( LDAP_DEBUG_TRACE, "%s destroy: " + "freeing system resources.\n", + slap_name ); + + ldap_pvt_thread_pool_free( &connection_pool ); + + switch ( slapMode & SLAP_MODE ) { + case SLAP_SERVER_MODE: + break; + + default: + Debug( LDAP_DEBUG_ANY, "slap_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 0000000000..7a5be047c9 --- /dev/null +++ b/servers/lloadd/libevent_support.c @@ -0,0 +1,161 @@ +/* libevent_support.c - routines to bridge libldap and libevent */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2017-2020 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "portable.h" + +#include + +#include +#include + +#include "slap.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 ); +} + +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 + }; + + if ( ldap_pvt_thread_initialize() ) { + return -1; + } + + evthread_set_lock_callbacks( &cbs ); + evthread_set_condition_callbacks( &cond_cbs ); + evthread_set_id_callback( ldap_pvt_thread_self ); + return 0; +} diff --git a/servers/lloadd/main.c b/servers/lloadd/main.c new file mode 100644 index 0000000000..0fd5200161 --- /dev/null +++ b/servers/lloadd/main.c @@ -0,0 +1,974 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2015 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ +/* Portions Copyright (c) 1995 Regents of the University of Michigan. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of Michigan at Ann Arbor. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + */ + +#include "portable.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "slap.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, slap_sig_shutdown }, + +#ifdef SIGPIPE + { SIGPIPE, sigpipe }, +#endif +#ifdef SIGHUP + { SIGHUP, slap_sig_shutdown }, +#endif + { SIGINT, slap_sig_shutdown }, + { SIGTERM, slap_sig_shutdown }, +#ifdef SIGTRAP + { SIGTRAP, slap_sig_shutdown }, +#endif +#ifdef LDAP_SIGCHLD + { LDAP_SIGCHLD, wait4child }, +#endif +#ifdef SIGBREAK + /* SIGBREAK is generated when Ctrl-Break is pressed. */ + { SIGBREAK, slap_sig_shutdown }, +#endif + { 0, NULL } +}; + +/* + * 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; + +void *slap_tls_ctx; +LDAP *slap_tls_ld; + +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