]> git.sur5r.net Git - openldap/commitdiff
ITS#8303 Asynchronous meta back-end for OpenLDAP
authorNadezhda Ivanova <nivanova@symas.com>
Sat, 30 Jan 2016 18:14:29 +0000 (20:14 +0200)
committerHoward Chu <hyc@openldap.org>
Mon, 1 Feb 2016 14:35:47 +0000 (14:35 +0000)
25 files changed:
configure.in
doc/man/man5/slapd-asyncmeta.5 [new file with mode: 0644]
servers/slapd/back-asyncmeta/Makefile.in [new file with mode: 0644]
servers/slapd/back-asyncmeta/abandon.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/add.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/back-asyncmeta.h [new file with mode: 0644]
servers/slapd/back-asyncmeta/bind.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/candidates.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/compare.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/config.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/conn.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/delete.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/dncache.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/init.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/map.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/message_queue.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/meta_result.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/modify.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/modrdn.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/proto-asyncmeta.h [new file with mode: 0644]
servers/slapd/back-asyncmeta/search.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/suffixmassage.c [new file with mode: 0644]
servers/slapd/back-asyncmeta/unbind.c [new file with mode: 0644]
servers/slapd/backover.c
tests/progs/slapd-mtread.c

index b97307d60427ef162f729c32a5617116f30f6178..dcf25e3f8301a00a1aac9034d2381bdc4d8ee274 100644 (file)
@@ -316,6 +316,8 @@ OL_ARG_ENABLE(mdb,[    --enable-mdb   enable mdb database backend],
        yes, [no yes mod], ol_enable_backends)dnl
 OL_ARG_ENABLE(meta,[    --enable-meta    enable metadirectory backend],
        no, [no yes mod], ol_enable_backends)dnl
+OL_ARG_ENABLE(asyncmeta,[    --enable-asyncmeta          enable asynchronous metadirectory backend],
+       no, [no yes mod], ol_enable_backends)dnl
 OL_ARG_ENABLE(monitor,[    --enable-monitor      enable monitor backend],
        yes, [no yes mod], ol_enable_backends)dnl
 OL_ARG_ENABLE(ndb,[    --enable-ndb      enable MySQL NDB Cluster backend],
@@ -504,6 +506,10 @@ if test $ol_enable_meta/$ol_enable_ldap = yes/no ; then
        AC_MSG_ERROR([--enable-meta requires --enable-ldap])
 fi
 
+if test $ol_enable_meta_async/$ol_enable_ldap = yes/no ; then
+       AC_MSG_ERROR([--enable-asyncmeta requires --enable-ldap])
+fi
+
 if test $ol_enable_lmpasswd = yes ; then
        if test $ol_with_tls = no ; then
                AC_MSG_ERROR([LAN Manager passwords require OpenSSL])
@@ -543,6 +549,7 @@ BUILD_HDB=no
 BUILD_LDAP=no
 BUILD_MDB=no
 BUILD_META=no
+BUILD_ASYNCMETA=no
 BUILD_MONITOR=no
 BUILD_NDB=no
 BUILD_NULL=no
@@ -2736,6 +2743,20 @@ if test "$ol_enable_meta" != no ; then
        AC_DEFINE_UNQUOTED(SLAPD_META,$MFLAG,[define to support LDAP Metadirectory backend])
 fi
 
+if test "$ol_enable_meta_async" != no ; then
+       BUILD_SLAPD=yes
+       BUILD_ASYNCMETA=$ol_enable_meta_async
+       BUILD_REWRITE=yes
+       if test "$ol_enable_meta_async" = mod ; then
+               SLAPD_DYNAMIC_BACKENDS="$SLAPD_DYNAMIC_BACKENDS back-asyncmeta"
+               MFLAG=SLAPD_MOD_DYNAMIC
+       else
+               SLAPD_STATIC_BACKENDS="$SLAPD_STATIC_BACKENDS back-asyncmeta"
+               MFLAG=SLAPD_MOD_STATIC
+       fi
+       AC_DEFINE_UNQUOTED(SLAPD_ASYNCMETA,$MFLAG,[define to support LDAP Async Metadirectory backend])
+fi
+
 if test "$ol_enable_ndb" != no ; then
        BUILD_SLAPD=yes
        BUILD_NDB=$ol_enable_ndb
@@ -3142,6 +3163,7 @@ dnl backends
   AC_SUBST(BUILD_LDAP)
   AC_SUBST(BUILD_MDB)
   AC_SUBST(BUILD_META)
+  AC_SUBST(BUILD_ASYNCMETA)
   AC_SUBST(BUILD_MONITOR)
   AC_SUBST(BUILD_NDB)
   AC_SUBST(BUILD_NULL)
@@ -3253,6 +3275,7 @@ AC_CONFIG_FILES([Makefile:build/top.mk:Makefile.in:build/dir.mk]
 [servers/slapd/back-ldif/Makefile:build/top.mk:servers/slapd/back-ldif/Makefile.in:build/mod.mk]
 [servers/slapd/back-mdb/Makefile:build/top.mk:servers/slapd/back-mdb/Makefile.in:build/mod.mk]
 [servers/slapd/back-meta/Makefile:build/top.mk:servers/slapd/back-meta/Makefile.in:build/mod.mk]
+[servers/slapd/back-asyncmeta/Makefile:build/top.mk:servers/slapd/back-asyncmeta/Makefile.in:build/mod.mk]
 [servers/slapd/back-monitor/Makefile:build/top.mk:servers/slapd/back-monitor/Makefile.in:build/mod.mk]
 [servers/slapd/back-ndb/Makefile:build/top.mk:servers/slapd/back-ndb/Makefile.in:build/mod.mk]
 [servers/slapd/back-null/Makefile:build/top.mk:servers/slapd/back-null/Makefile.in:build/mod.mk]
diff --git a/doc/man/man5/slapd-asyncmeta.5 b/doc/man/man5/slapd-asyncmeta.5
new file mode 100644 (file)
index 0000000..20f986a
--- /dev/null
@@ -0,0 +1,487 @@
+.TH SLAPD-ASYNCMETA 5 "RELEASEDATE" "OpenLDAP LDVERSION"
+.\" Copyright 2016 The OpenLDAP Foundation.
+.\" Portions Copyright 2016 Symas Corporation.
+.\" Copying restrictions apply.  See the COPYRIGHT file.
+.\" $OpenLDAP$
+.\"
+
+.SH NAME
+slapd\-asyncmeta \- asynchronous metadirectory backend to slapd
+.SH SYNOPSIS
+ETCDIR/slapd.conf
+.SH DESCRIPTION
+The
+.B asyncmeta
+backend to
+.BR slapd (8)
+performs basic LDAP proxying with respect to a set of remote LDAP
+servers, called "targets".
+The information contained in these servers can be presented as
+belonging to a single Directory Information Tree (DIT).
+
+.LP
+A good knowledge of the functionality of the
+.BR slapd\-meta(5)
+backend  is recommended.   This  backend has been designed as
+an asynchronous version of the
+.B meta
+backend. Unlike
+.B meta
+, the operation handling threads are no longer pending
+on the response from the remote server, thus decreasing the
+number of threads necessary to handle the same load. While
+.B asyncmeta
+maintains the functionality of
+.B meta
+and has a largely similar codebase,
+some changes in operation and some new configuration directives have been
+added. Some configuration options, such as
+.B conn-ttl,
+.B single-conn
+and
+.B use-temporary-conn
+have been removed, as they are no longer relevant.
+.LP
+.B New connection handling:
+.LP
+
+Unlike
+.B meta,
+which caches bound connections, the
+.B asyncmeta
+works with a configured maximum number of connections per target.
+For each request redirected to a target, a different connection is selected.
+Each connection has a queue, to which the request is added before it is sent to the
+remote server, and is removed after the last response for that request is received.
+ For each new request, the connection with the smallest number of pending requests
+is selected, or using round\-robin if the numbers are equal.
+.LP
+.B Overlays:
+.LP
+Due to implementation specifics, there is no guarantee that any of the existing OpenLDAP overlays will work with
+.B asyncmeta
+backend.
+
+.SH EXAMPLES
+Refer to
+.B slapd\-meta(5)
+for configuration examples.
+
+.SH CONFIGURATION
+These
+.B slapd.conf
+options apply to the ASYNCMETA backend database.
+That is, they must follow a "database asyncmeta" line and come before any
+subsequent "backend" or "database" lines.
+Other database options are described in the
+.BR slapd.conf (5)
+manual page.
+
+.SH SPECIAL CONFIGURATION DIRECTIVES
+Target configuration starts with the "uri" directive.
+All the configuration directives that are not specific to targets
+should be defined first for clarity, including those that are common
+to all backends.
+They are:
+
+.TP
+.B default\-target none
+This directive forces the backend to reject all those operations
+that must resolve to a single target in case none or multiple
+targets are selected.
+They include: add, delete, modify, modrdn; compare is not included, as
+well as bind since, as they don't alter entries, in case of multiple
+matches an attempt is made to perform the operation on any candidate
+target, with the constraint that at most one must succeed.
+This directive can also be used when processing targets to mark a
+specific target as default.
+
+.TP
+.B dncache\-ttl {DISABLED|forever|<ttl>}
+This directive sets the time-to-live of the DN cache.
+This caches the target that holds a given DN to speed up target
+selection in case multiple targets would result from an uncached
+search; forever means cache never expires; disabled means no DN
+caching; otherwise a valid ( > 0 ) ttl is required, in the format
+illustrated for the
+.B idle\-timeout
+directive.
+
+.TP
+.B onerr {CONTINUE|report|stop}
+This directive allows to select the behavior in case an error is returned
+by one target during a search.
+The default, \fBcontinue\fP, consists in continuing the operation,
+trying to return as much data as possible.
+If the value is set to \fBstop\fP, the search is terminated as soon
+as an error is returned by one target, and the error is immediately
+propagated to the client.
+If the value is set to \fBreport\fP, the search is continuated to the end
+but, in case at least one target returned an error code, the first
+non-success error code is returned.
+
+.TP
+.B max\-timeout\-ops <number>
+Specify the number of consecutive timed out requests,
+after which the connection will be considered faulty and dropped.
+
+.TP
+.B max-pending-ops <number>
+The maximum number of pending requests stored in a connection's queue.
+The default is 128. When this number is exceeded,
+.B LDAP_BUSY
+will be returned to the client.
+
+.TP
+.B max-target-conns <number>
+The maximum number of connections per target. Unlike
+.B slapd\-meta(5),
+no new connections will be created
+once this number is reached. The default value is 255.
+
+.TP
+.B norefs <NO|yes>
+If
+.BR yes ,
+do not return search reference responses.
+By default, they are returned unless request is LDAPv2.
+If set before any target specification, it affects all targets, unless
+overridden by any per-target directive.
+
+.TP
+.B noundeffilter <NO|yes>
+If
+.BR yes ,
+return success instead of searching if a filter is undefined or contains
+undefined portions.
+By default, the search is propagated after replacing undefined portions
+with
+.BR (!(objectClass=*)) ,
+which corresponds to the empty result set.
+If set before any target specification, it affects all targets, unless
+overridden by any per-target directive.
+
+.TP
+.B protocol\-version {0,2,3}
+This directive indicates what protocol version must be used to contact
+the remote server.
+If set to 0 (the default), the proxy uses the same protocol version
+used by the client, otherwise the requested protocol is used.
+The proxy returns \fIunwillingToPerform\fP if an operation that is
+incompatible with the requested protocol is attempted.
+If set before any target specification, it affects all targets, unless
+overridden by any per-target directive.
+
+.TP
+.B pseudoroot\-bind\-defer {YES|no}
+This directive, when set to
+.BR yes ,
+causes the authentication to the remote servers with the pseudo-root
+identity (the identity defined in each
+.B idassert-bind
+directive) to be deferred until actually needed by subsequent operations.
+Otherwise, all binds as the rootdn are propagated to the targets.
+
+.TP
+.B quarantine <interval>,<num>[;<interval>,<num>[...]]
+Turns on quarantine of URIs that returned
+.IR LDAP_UNAVAILABLE ,
+so that an attempt to reconnect only occurs at given intervals instead
+of any time a client requests an operation.
+The pattern is: retry only after at least
+.I interval
+seconds elapsed since last attempt, for exactly
+.I num
+times; then use the next pattern.
+If
+.I num
+for the last pattern is "\fB+\fP", it retries forever; otherwise,
+no more retries occur.
+This directive must appear before any target specification;
+it affects all targets with the same pattern.
+
+.TP
+.B rebind\-as\-user {NO|yes}
+If this option is given, the client's bind credentials are remembered
+for rebinds, when trying to re-establish a broken connection,
+or when chasing a referral, if
+.B chase\-referrals
+is set to
+.IR yes .
+
+.TP
+.B session\-tracking\-request {NO|yes}
+Adds session tracking control for all requests.
+The client's IP and hostname, and the identity associated to each request,
+if known, are sent to the remote server for informational purposes.
+This directive is incompatible with setting \fIprotocol\-version\fP to 2.
+If set before any target specification, it affects all targets, unless
+overridden by any per-target directive.
+
+.SH TARGET SPECIFICATION
+Target specification starts with a "uri" directive:
+
+.TP
+.B uri <protocol>://[<host>]/<naming context> [...]
+Identical to
+.B meta.
+See
+.B slapd\-meta(5)
+for details.
+
+.TP
+.B acl\-authcDN "<administrative DN for access control purposes>"
+DN which is used to query the target server for acl checking,
+as in the LDAP backend; it is supposed to have read access
+on the target server to attributes used on the proxy for acl checking.
+There is no risk of giving away such values; they are only used to
+check permissions.
+.B The acl\-authcDN identity is by no means implicitly used by the proxy
+.B when the client connects anonymously.
+
+.TP
+.B acl\-passwd <password>
+Password used with the
+.B
+acl\-authcDN
+above.
+
+.TP
+.B bind\-timeout <microseconds>
+This directive defines the timeout, in microseconds, used when polling
+for response after an asynchronous bind connection. See
+.B slapd\-meta(5)
+for details.
+
+.TP
+.B chase\-referrals {YES|no}
+enable/disable automatic referral chasing, which is delegated to the
+underlying libldap, with rebinding eventually performed if the
+\fBrebind\-as\-user\fP directive is used.  The default is to chase referrals.
+If set before any target specification, it affects all targets, unless
+overridden by any per-target directive.
+
+.TP
+.B client\-pr {accept-unsolicited|DISABLE|<size>}
+This feature allows to use RFC 2696 Paged Results control when performing
+search operations with a specific target,
+irrespective of the client's request. See
+.B slapd\-meta(5)
+for details.
+
+.TP
+.B default\-target [<target>]
+The "default\-target" directive can also be used during target specification.
+With no arguments it marks the current target as the default.
+The optional number marks target <target> as the default one, starting
+from 1.
+Target <target> must be defined.
+
+.TP
+.B filter <pattern>
+This directive allows specifying a
+.BR regex (5)
+pattern to indicate what search filter terms are actually served by a target.
+
+In a search request, if the search filter matches the \fIpattern\fP
+the target is considered while fulfilling the request; otherwise
+the target is ignored. There may be multiple occurrences of
+the
+.B filter
+directive for each target.
+
+.TP
+.B idassert\-authzFrom <authz-regexp>
+if defined, selects what
+.I local
+identities are authorized to exploit the identity assertion feature.
+The string
+.B <authz-regexp>
+follows the rules defined for the
+.I authzFrom
+attribute.
+See
+.BR slapd.conf (5),
+section related to
+.BR authz\-policy ,
+for details on the syntax of this field.
+
+.HP
+.hy 0
+.B idassert\-bind
+.B bindmethod=none|simple|sasl [binddn=<simple DN>] [credentials=<simple password>]
+.B [saslmech=<SASL mech>] [secprops=<properties>] [realm=<realm>]
+.B [authcId=<authentication ID>] [authzId=<authorization ID>]
+.B [authz={native|proxyauthz}] [mode=<mode>] [flags=<flags>]
+.B [starttls=no|yes|critical]
+.B [tls_cert=<file>]
+.B [tls_key=<file>]
+.B [tls_cacert=<file>]
+.B [tls_cacertdir=<path>]
+.B [tls_reqcert=never|allow|try|demand]
+.B [tls_ciphersuite=<ciphers>]
+.B [tls_protocol_min=<major>[.<minor>]]
+.B [tls_crlcheck=none|peer|all]
+Allows to define the parameters of the authentication method that is
+internally used by the proxy to authorize connections that are
+authenticated by other databases. See
+.B slapd\-meta(5)
+for details.
+
+.TP
+.B idle\-timeout <time>
+This directive causes a a persistent connection  to  be  dropped after
+it  has been idle for the specified time. The connection will be re-created
+the next time it is selected for use. A connection is considered idle if no
+attempts have been made by the backend to use it to send a request to
+the backend server. If there are still pending requests in
+its queue, the connection will be dropped after the last
+request one has either received a result or has timed out.
+
+[<d>d][<h>h][<m>m][<s>[s]]
+
+where <d>, <h>, <m> and <s> are respectively treated as days, hours,
+minutes and seconds.
+If set before any target specification, it affects all targets, unless
+overridden by any per-target directive.
+
+.TP
+.B keepalive  <idle>:<probes>:<interval>
+The
+.B keepalive
+parameter sets the values of \fIidle\fP, \fIprobes\fP, and \fIinterval\fP
+used to check whether a socket is alive;
+.I idle
+is the number of seconds a connection needs to remain idle before TCP
+starts sending keepalive probes;
+.I probes
+is the maximum number of keepalive probes TCP should send before dropping
+the connection;
+.I interval
+is interval in seconds between individual keepalive probes.
+Only some systems support the customization of these values;
+the
+.B keepalive
+parameter is ignored otherwise, and system-wide settings are used.
+
+.TP
+.B map "{attribute|objectclass} [<local name>|*] {<foreign name>|*}"
+This maps object classes and attributes as in the LDAP backend.
+See
+.BR slapd\-ldap (5).
+
+.TP
+.B network\-timeout <time>
+Sets the network timeout value after which
+.BR poll (2)/ select (2)
+following a
+.BR connect (2)
+returns in case of no activity.
+The value is in seconds, and it can be specified as for
+.BR idle\-timeout .
+If set before any target specification, it affects all targets, unless
+overridden by any per-target directive.
+
+.TP
+.B nretries {forever|never|<nretries>}
+This directive defines how many times a bind should be retried
+in case of temporary failure in contacting a target.  If defined
+before any target specification, it applies to all targets (by default,
+.BR 3
+times);
+the global value can be overridden by redefinitions inside each target
+specification.
+
+.TP
+.B rewrite* ...
+The rewrite options are identical to the
+.B meta
+backend. See the
+.B REWRITING
+section of
+.B slapd\-meta(5).
+
+.TP
+.B subtree\-{exclude|include} "<rule>"
+This directive allows to indicate what subtrees are actually served
+by a target. See
+.B slapd\-meta(5)
+for details.
+
+.TP
+.B suffixmassage "<virtual naming context>" "<real naming context>"
+All the directives starting with "rewrite" refer to the rewrite engine
+that has been added to slapd. See
+.B slapd\-meta(5)
+for details.
+
+.TP
+.B t\-f\-support {NO|yes|discover}
+enable if the remote server supports absolute filters
+(see \fIRFC 4526\fP for details).
+If set to
+.BR discover ,
+support is detected by reading the remote server's root DSE.
+If set before any target specification, it affects all targets, unless
+overridden by any per-target directive.
+
+.TP
+.B timeout [<op>=]<val> [...]
+This directive allows to set per-operation timeouts.
+Operations can be
+
+\fB<op> ::= bind, add, delete, modrdn, modify, compare, search\fP
+
+See
+.B slapd\-meta(5)
+for details.
+
+.TP
+.B tls {[try\-]start|[try\-]propagate}
+execute the StartTLS extended operation when the connection is initialized;
+only works if the URI directive protocol scheme is not \fBldaps://\fP.
+\fBpropagate\fP issues the StartTLS operation only if the original
+connection did.
+The \fBtry\-\fP prefix instructs the proxy to continue operations
+if the StartTLS operation failed; its use is highly deprecated.
+If set before any target specification, it affects all targets, unless
+overridden by any per-target directive.
+
+.SH SCENARIOS
+See
+.B slapd\-meta(5)
+for configuration scenarios.
+
+.SH ACLs
+ACL behavior is identical to meta. See
+.B slapd\-meta(5).
+
+.SH ACCESS CONTROL
+The
+.B asyncmeta
+backend does not honor all ACL semantics as described in
+.BR slapd.access (5).
+In general, access checking is delegated to the remote server(s).
+Only
+.B read (=r)
+access to the
+.B entry
+pseudo-attribute and to the other attribute values of the entries
+returned by the
+.B search
+operation is honored, which is performed by the frontend.
+
+.SH FILES
+.TP
+ETCDIR/slapd.conf
+default slapd configuration file
+.SH SEE ALSO
+.BR slapd.conf (5),
+.BR slapd\-meta (5),
+.BR slapd\-ldap (5),
+.BR slapo\-pcache (5),
+.BR slapd (8),
+.BR regex (7),
+.BR re_format (7).
+.SH AUTHOR
+Nadezhda Ivanova, based on back-meta by Pierangelo Masarati.
diff --git a/servers/slapd/back-asyncmeta/Makefile.in b/servers/slapd/back-asyncmeta/Makefile.in
new file mode 100644 (file)
index 0000000..b5591f9
--- /dev/null
@@ -0,0 +1,50 @@
+## Makefile.in for back-asyncmeta
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016 The OpenLDAP Foundation.
+## Portions Copyright 2016 Symas Corporation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This work was developed by Symas Corporation
+## based on back-meta module for inclusion in OpenLDAP Software.
+## This work was sponsored by Ericsson
+
+SRCS   = init.c config.c search.c message_queue.c bind.c unbind.c add.c compare.c \
+               delete.c modify.c modrdn.c suffixmassage.c map.c \
+               conn.c candidates.c dncache.c meta_result.c abandon.c
+OBJS   = init.lo config.lo search.lo message_queue.lo bind.lo unbind.lo add.lo compare.lo \
+               delete.lo modify.lo modrdn.lo suffixmassage.lo map.lo \
+               conn.lo candidates.lo dncache.lo meta_result.lo abandon.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-asyncmeta"
+BUILD_MOD = @BUILD_ASYNCMETA@
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = $(@BUILD_ASYNCMETA@_DEFS)
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(LIBS) $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBBASE = back_asyncmeta
+
+XINCPATH = -I.. -I$(srcdir)/..
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+       @touch $@
diff --git a/servers/slapd/back-asyncmeta/abandon.c b/servers/slapd/back-asyncmeta/abandon.c
new file mode 100644 (file)
index 0000000..79b98d6
--- /dev/null
@@ -0,0 +1,52 @@
+/* abandon.c - abandon request handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "ldap_rq.h"
+
+/* function is unused */
+int
+asyncmeta_back_abandon( Operation *op, SlapReply *rs )
+{
+       Operation *t_op;
+
+       /* Find the ops being abandoned */
+       ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+
+       LDAP_STAILQ_FOREACH( t_op, &op->o_conn->c_ops, o_next ) {
+               if ( t_op->o_msgid == op->orn_msgid ) {
+                       t_op->o_abandon = 1;
+               }
+       }
+       ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+       return LDAP_SUCCESS;
+}
diff --git a/servers/slapd/back-asyncmeta/add.c b/servers/slapd/back-asyncmeta/add.c
new file mode 100644 (file)
index 0000000..661c4fd
--- /dev/null
@@ -0,0 +1,368 @@
+/* add.c - add request handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "ldap_rq.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+
+void
+asyncmeta_sender_error(Operation *op,
+                      SlapReply *rs,
+                      slap_callback *cb)
+{
+       if (cb != NULL) {
+               op->o_callback = cb;
+       }
+       send_ldap_result(op, rs);
+}
+
+meta_search_candidate_t
+asyncmeta_back_add_start(Operation *op,
+                        SlapReply *rs,
+                        a_metaconn_t *mc,
+                        bm_context_t *bc,
+                        int candidate)
+{
+       int             isupdate;
+       Attribute       *a;
+       int i;
+       LDAPMod         **attrs;
+       struct berval   mapped;
+       a_dncookie      dc;
+       a_metainfo_t    *mi = mc->mc_info;
+       a_metatarget_t  *mt = mi->mi_targets[ candidate ];
+       struct berval   mdn;
+       meta_search_candidate_t retcode = META_SEARCH_CANDIDATE;
+       BerElement *ber = NULL;
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+       SlapReply               *candidates = bc->candidates;
+       ber_int_t       msgid;
+       LDAPControl             **ctrls = NULL;
+       int rc, nretries = 1;
+
+
+       dc.target = mt;
+       dc.conn = op->o_conn;
+       dc.rs = rs;
+       dc.ctx = "addDN";
+
+       mdn.bv_len = 0;
+
+       switch (asyncmeta_dn_massage( &dc, &bc->op->o_req_dn, &mdn ) )
+       {
+       case LDAP_SUCCESS:
+               break;
+       case LDAP_UNWILLING_TO_PERFORM:
+               rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+               rs->sr_text = "Operation not allowed";
+               retcode = META_SEARCH_ERR;
+               goto doreturn;
+       default:
+               rs->sr_err = LDAP_NO_SUCH_OBJECT;
+               retcode = META_SEARCH_NOT_CANDIDATE;
+               goto doreturn;
+       }
+
+       /* Count number of attributes in entry ( +1 ) */
+       for ( i = 1, a = op->ora_e->e_attrs; a; i++, a = a->a_next );
+
+       /* Create array of LDAPMods for ldap_add() */
+       attrs = ch_malloc( sizeof( LDAPMod * )*i );
+
+       dc.ctx = "addAttrDN";
+       isupdate = be_shadow_update( op );
+       for ( i = 0, a = op->ora_e->e_attrs; a; a = a->a_next ) {
+               int j, is_oc = 0;
+
+               if ( !isupdate && !get_relax( op ) && a->a_desc->ad_type->sat_no_user_mod  )
+               {
+                       continue;
+               }
+
+               if ( a->a_desc == slap_schema.si_ad_objectClass
+                               || a->a_desc == slap_schema.si_ad_structuralObjectClass )
+               {
+                       is_oc = 1;
+                       mapped = a->a_desc->ad_cname;
+
+               } else {
+                       asyncmeta_map( &mt->mt_rwmap.rwm_at,
+                                       &a->a_desc->ad_cname, &mapped, BACKLDAP_MAP );
+                       if ( BER_BVISNULL( &mapped ) || BER_BVISEMPTY( &mapped ) ) {
+                               continue;
+                       }
+               }
+
+               attrs[ i ] = ch_malloc( sizeof( LDAPMod ) );
+               if ( attrs[ i ] == NULL ) {
+                       continue;
+               }
+               attrs[ i ]->mod_op = LDAP_MOD_BVALUES;
+               attrs[ i ]->mod_type = mapped.bv_val;
+
+               if ( is_oc ) {
+                       for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); j++ );
+
+                       attrs[ i ]->mod_bvalues =
+                               (struct berval **)ch_malloc( ( j + 1 ) *
+                               sizeof( struct berval * ) );
+                       for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); ) {
+                               struct ldapmapping      *mapping;
+
+                               asyncmeta_mapping( &mt->mt_rwmap.rwm_oc,
+                                               &a->a_vals[ j ], &mapping, BACKLDAP_MAP );
+
+                               if ( mapping == NULL ) {
+                                       if ( mt->mt_rwmap.rwm_oc.drop_missing ) {
+                                               continue;
+                                       }
+                                       attrs[ i ]->mod_bvalues[ j ] = &a->a_vals[ j ];
+
+                               } else {
+                                       attrs[ i ]->mod_bvalues[ j ] = &mapping->dst;
+                               }
+                               j++;
+                       }
+                       attrs[ i ]->mod_bvalues[ j ] = NULL;
+
+               } else {
+                       /*
+                        * FIXME: dn-valued attrs should be rewritten
+                        * to allow their use in ACLs at the back-ldap
+                        * level.
+                        */
+                       if ( a->a_desc->ad_type->sat_syntax ==
+                               slap_schema.si_syn_distinguishedName )
+                       {
+                               (void)asyncmeta_dnattr_rewrite( &dc, a->a_vals );
+                               if ( a->a_vals == NULL ) {
+                                       continue;
+                               }
+                       }
+
+                       for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); j++ )
+                               ;
+
+                       attrs[ i ]->mod_bvalues = ch_malloc( ( j + 1 ) * sizeof( struct berval * ) );
+                       for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); j++ ) {
+                               attrs[ i ]->mod_bvalues[ j ] = &a->a_vals[ j ];
+                       }
+                       attrs[ i ]->mod_bvalues[ j ] = NULL;
+               }
+               i++;
+       }
+       attrs[ i ] = NULL;
+
+retry:;
+       ctrls = op->o_ctrls;
+       if ( asyncmeta_controls_add( op, rs, mc, candidate, &ctrls ) != LDAP_SUCCESS )
+       {
+               candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+               retcode = META_SEARCH_ERR;
+               goto done;
+       }
+
+       ber = ldap_build_add_req( msc->msc_ld, mdn.bv_val, attrs, ctrls, NULL, &msgid);
+       if (ber) {
+               candidates[ candidate ].sr_msgid = msgid;
+               rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_ADD,
+                                               mdn.bv_val, ber, msgid );
+               if (rc == msgid)
+                       rc = LDAP_SUCCESS;
+               else
+                       rc = LDAP_SERVER_DOWN;
+
+               switch ( rc ) {
+               case LDAP_SUCCESS:
+                       retcode = META_SEARCH_CANDIDATE;
+                       asyncmeta_set_msc_time(msc);
+                       break;
+
+               case LDAP_SERVER_DOWN:
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_clear_one_msc(NULL, mc, candidate);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       if ( nretries && asyncmeta_retry( op, rs, &mc, candidate, LDAP_BACK_DONTSEND ) ) {
+                               nretries = 0;
+                               /* if the identity changed, there might be need to re-authz */
+                               (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+                               goto retry;
+                       }
+
+               default:
+                       candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+                       retcode = META_SEARCH_ERR;
+               }
+       }
+
+done:
+
+       (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+       for ( --i; i >= 0; --i ) {
+               free( attrs[ i ]->mod_bvalues );
+               free( attrs[ i ] );
+       }
+       free( attrs );
+       if ( mdn.bv_val != op->ora_e->e_dn ) {
+               free( mdn.bv_val );
+               BER_BVZERO( &mdn );
+       }
+
+doreturn:;
+       Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_add_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid );
+       return retcode;
+}
+
+
+int
+asyncmeta_back_add( Operation *op, SlapReply *rs )
+{
+       a_metainfo_t    *mi = ( a_metainfo_t * )op->o_bd->be_private;
+       a_metatarget_t  *mt;
+       a_metaconn_t    *mc;
+       int             rc, candidate = -1;
+       OperationBuffer opbuf;
+       bm_context_t *bc;
+       SlapReply *candidates;
+       slap_callback *cb = op->o_callback;
+
+       Debug(LDAP_DEBUG_ARGS, "==> asyncmeta_back_add: %s\n",
+             op->o_req_dn.bv_val, 0, 0 );
+
+       asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets );
+       if (bc == NULL) {
+               rs->sr_err = LDAP_OTHER;
+               asyncmeta_sender_error(op, rs, cb);
+               return rs->sr_err;
+       }
+
+       candidates = bc->candidates;
+       mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0);
+       if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+               asyncmeta_sender_error(op, rs, cb);
+               asyncmeta_clear_bm_context(bc);
+               return rs->sr_err;
+       }
+
+       mt = mi->mi_targets[ candidate ];
+       bc->timeout = mt->mt_timeout[ SLAP_OP_ADD ];
+       bc->retrying = LDAP_BACK_RETRYING;
+       bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying );
+       bc->stoptime = op->o_time + bc->timeout;
+
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+       rc = asyncmeta_add_message_queue(mc, bc);
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+       if (rc != LDAP_SUCCESS) {
+               rs->sr_err = LDAP_BUSY;
+               rs->sr_text = "Maximum pending ops limit exceeded";
+               asyncmeta_clear_bm_context(bc);
+               asyncmeta_sender_error(op, rs, cb);
+               goto finish;
+       }
+
+       rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate);
+       switch (rc)
+       {
+       case META_SEARCH_CANDIDATE:
+               /* target is already bound, just send the request */
+               Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_add:  "
+                      "cnd=\"%ld\"\n", op->o_log_prefix, candidate , 0);
+
+               rc = asyncmeta_back_add_start( op, rs, mc, bc, candidate);
+               if (rc == META_SEARCH_ERR) {
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_drop_bc(mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       asyncmeta_sender_error(op, rs, cb);
+                       asyncmeta_clear_bm_context(bc);
+                       goto finish;
+
+               }
+                       break;
+       case META_SEARCH_NOT_CANDIDATE:
+               Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_add: NOT_CANDIDATE "
+                      "cnd=\"%ld\"\n", op->o_log_prefix, candidate , 0);
+               candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+               ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+               asyncmeta_drop_bc(mc, bc);
+               ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+               asyncmeta_sender_error(op, rs, cb);
+               asyncmeta_clear_bm_context(bc);
+               goto finish;
+
+       case META_SEARCH_NEED_BIND:
+       case META_SEARCH_CONNECTING:
+               Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_add: NEED_BIND "
+                      "cnd=\"%ld\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+               rc = asyncmeta_dobind_init(op, rs, bc, mc, candidate);
+               if (rc == META_SEARCH_ERR) {
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_drop_bc(mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       asyncmeta_sender_error(op, rs, cb);
+                       asyncmeta_clear_bm_context(bc);
+                       goto finish;
+               }
+               break;
+       case META_SEARCH_BINDING:
+                       Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_add: BINDING "
+                              "cnd=\"%ld\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+                       /* Todo add the context to the message queue but do not send the request
+                          the receiver must send this when we are done binding */
+                       /* question - how would do receiver know to which targets??? */
+                       break;
+
+       case META_SEARCH_ERR:
+                       Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_add: ERR "
+                              "cnd=\"%ldd\"\n", op->o_log_prefix, candidate , 0);
+                       candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+                       candidates[ candidate ].sr_type = REP_RESULT;
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_drop_bc(mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       asyncmeta_sender_error(op, rs, cb);
+                       asyncmeta_clear_bm_context(bc);
+                       goto finish;
+               default:
+                       assert( 0 );
+                       break;
+               }
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+       asyncmeta_start_one_listener(mc, candidates, candidate);
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+finish:
+       return rs->sr_err;
+}
diff --git a/servers/slapd/back-asyncmeta/back-asyncmeta.h b/servers/slapd/back-asyncmeta/back-asyncmeta.h
new file mode 100644 (file)
index 0000000..1d7bea1
--- /dev/null
@@ -0,0 +1,847 @@
+/* back-asyncmeta.h - main header file for back-asyncmeta module */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#ifndef SLAPD_LDAP_H
+#error "include servers/slapd/back-ldap/back-ldap.h before this file!"
+#endif /* SLAPD_LDAP_H */
+
+#ifndef SLAPD_ASYNCMETA_H
+#define SLAPD_ASYNCMETA_H
+
+#ifndef ENABLE_REWRITE
+#error "--enable-rewrite is required!"
+#endif
+
+#ifdef LDAP_DEVEL
+#define SLAPD_META_CLIENT_PR 1
+#endif /* LDAP_DEVEL */
+
+#include "proto-asyncmeta.h"
+
+/* String rewrite library */
+#include "rewrite.h"
+#include "ldap_rq.h"
+
+LDAP_BEGIN_DECL
+
+/*
+ * Set META_BACK_PRINT_CONNTREE larger than 0 to dump the connection tree (debug only)
+ */
+#ifndef META_BACK_PRINT_CONNTREE
+#define META_BACK_PRINT_CONNTREE 0
+#endif /* !META_BACK_PRINT_CONNTREE */
+
+/* from back-ldap.h before rwm removal */
+struct ldapmap {
+       int drop_missing;
+
+       Avlnode *map;
+       Avlnode *remap;
+};
+
+struct ldapmapping {
+       struct berval src;
+       struct berval dst;
+};
+
+struct ldaprwmap {
+       /*
+        * DN rewriting
+        */
+       struct rewrite_info *rwm_rw;
+       BerVarray rwm_bva_rewrite;
+
+       /*
+        * Attribute/objectClass mapping
+        */
+       struct ldapmap rwm_oc;
+       struct ldapmap rwm_at;
+       BerVarray rwm_bva_map;
+};
+
+/* Whatever context asyncmeta_dn_massage needs... */
+typedef struct a_dncookie {
+       struct a_metatarget_t   *target;
+
+       Connection              *conn;
+       char                    *ctx;
+       SlapReply               *rs;
+} a_dncookie;
+
+int asyncmeta_dn_massage(a_dncookie *dc, struct berval *dn,
+       struct berval *res);
+
+extern int asyncmeta_conn_dup( void *c1, void *c2 );
+extern void asyncmeta_conn_free( void *c );
+
+/* attributeType/objectClass mapping */
+int asyncmeta_mapping_cmp (const void *, const void *);
+int asyncmeta_mapping_dup (void *, void *);
+
+void asyncmeta_map_init ( struct ldapmap *lm, struct ldapmapping ** );
+int asyncmeta_mapping ( struct ldapmap *map, struct berval *s,
+       struct ldapmapping **m, int remap );
+void asyncmeta_map ( struct ldapmap *map, struct berval *s, struct berval *m,
+       int remap );
+#define BACKLDAP_MAP   0
+#define BACKLDAP_REMAP 1
+char *
+asyncmeta_map_filter(
+       struct ldapmap *at_map,
+       struct ldapmap *oc_map,
+       struct berval *f,
+       int remap );
+
+int
+asyncmeta_map_attrs(
+       Operation *op,
+       struct ldapmap *at_map,
+       AttributeName *a,
+       int remap,
+       char ***mapped_attrs );
+
+extern int
+asyncmeta_filter_map_rewrite(
+       a_dncookie      *dc,
+       Filter          *f,
+       struct berval   *fstr,
+       int             remap,
+       void            *memctx );
+
+/* suffix massaging by means of librewrite */
+extern int
+asyncmeta_suffix_massage_config( struct rewrite_info *info,
+       struct berval *pvnc,
+       struct berval *nvnc,
+       struct berval *prnc,
+       struct berval *nrnc );
+
+extern int
+asyncmeta_back_referral_result_rewrite(
+       a_dncookie      *dc,
+       BerVarray       a_vals,
+       void            *memctx );
+extern int
+asyncmeta_dnattr_rewrite(
+       a_dncookie      *dc,
+       BerVarray       a_vals );
+extern int
+asyncmeta_dnattr_result_rewrite(
+       a_dncookie      *dc,
+       BerVarray       a_vals );
+
+
+/* (end of) from back-ldap.h before rwm removal */
+
+/*
+ * A a_metasingleconn_t can be in the following, mutually exclusive states:
+ *
+ *     - none                  (0x0U)
+ *     - creating              META_BACK_FCONN_CREATING
+ *     - initialized           META_BACK_FCONN_INITED
+ *     - binding               LDAP_BACK_FCONN_BINDING
+ *     - bound/anonymous       LDAP_BACK_FCONN_ISBOUND/LDAP_BACK_FCONN_ISANON
+ *
+ * possible modifiers are:
+ *
+ *     - privileged            LDAP_BACK_FCONN_ISPRIV
+ *     - privileged, TLS       LDAP_BACK_FCONN_ISTLS
+ *     - subjected to idassert LDAP_BACK_FCONN_ISIDASR
+ *     - tainted               LDAP_BACK_FCONN_TAINTED
+ */
+
+#define META_BACK_FCONN_INITED         (0x00100000U)
+#define META_BACK_FCONN_CREATING       (0x00200000U)
+
+#define        META_BACK_CONN_INITED(lc)               LDAP_BACK_CONN_ISSET((lc), META_BACK_FCONN_INITED)
+#define        META_BACK_CONN_INITED_SET(lc)           LDAP_BACK_CONN_SET((lc), META_BACK_FCONN_INITED)
+#define        META_BACK_CONN_INITED_CLEAR(lc)         LDAP_BACK_CONN_CLEAR((lc), META_BACK_FCONN_INITED)
+#define        META_BACK_CONN_INITED_CPY(lc, mlc)      LDAP_BACK_CONN_CPY((lc), META_BACK_FCONN_INITED, (mlc))
+#define        META_BACK_CONN_CREATING(lc)             LDAP_BACK_CONN_ISSET((lc), META_BACK_FCONN_CREATING)
+#define        META_BACK_CONN_CREATING_SET(lc)         LDAP_BACK_CONN_SET((lc), META_BACK_FCONN_CREATING)
+#define        META_BACK_CONN_CREATING_CLEAR(lc)       LDAP_BACK_CONN_CLEAR((lc), META_BACK_FCONN_CREATING)
+#define        META_BACK_CONN_CREATING_CPY(lc, mlc)    LDAP_BACK_CONN_CPY((lc), META_BACK_FCONN_CREATING, (mlc))
+
+struct a_metainfo_t;
+struct a_metaconn_t;
+struct a_metatarget_t;
+#define        META_NOT_CANDIDATE              ((ber_tag_t)0x0)
+#define        META_CANDIDATE                  ((ber_tag_t)0x1)
+#define        META_BINDING                    ((ber_tag_t)0x2)
+#define        META_RETRYING                   ((ber_tag_t)0x4)
+
+typedef struct bm_context_t {
+       LDAP_SLIST_ENTRY(bm_context_t) bc_next;
+       time_t                  timeout;
+       time_t          stoptime;
+       ldap_back_send_t        sendok;
+       ldap_back_send_t        retrying;
+       int candidate_match;
+       int sent;
+       int bc_active;
+       int searchtime; /* stoptime is a search timelimit */
+       int is_ok;
+       SlapReply               rs;
+       Operation               *op;
+       LDAPControl     **ctrls;
+       SlapReply               *candidates;
+} bm_context_t;
+
+typedef struct a_metasingleconn_t {
+#define META_CND_ISSET(rs,f)           ( ( (rs)->sr_tag & (f) ) == (f) )
+#define META_CND_SET(rs,f)             ( (rs)->sr_tag |= (f) )
+#define META_CND_CLEAR(rs,f)           ( (rs)->sr_tag &= ~(f) )
+
+#define META_CANDIDATE_RESET(rs)       ( (rs)->sr_tag = 0 )
+#define META_IS_CANDIDATE(rs)          META_CND_ISSET( (rs), META_CANDIDATE )
+#define META_CANDIDATE_SET(rs)         META_CND_SET( (rs), META_CANDIDATE )
+#define META_CANDIDATE_CLEAR(rs)       META_CND_CLEAR( (rs), META_CANDIDATE )
+#define META_IS_BINDING(rs)            META_CND_ISSET( (rs), META_BINDING )
+#define META_BINDING_SET(rs)           META_CND_SET( (rs), META_BINDING )
+#define META_BINDING_CLEAR(rs)         META_CND_CLEAR( (rs), META_BINDING )
+#define META_IS_RETRYING(rs)           META_CND_ISSET( (rs), META_RETRYING )
+#define META_RETRYING_SET(rs)          META_CND_SET( (rs), META_RETRYING )
+#define META_RETRYING_CLEAR(rs)                META_CND_CLEAR( (rs), META_RETRYING )
+
+       LDAP                    *msc_ld;
+       LDAP                    *msc_ldr;
+       time_t                  msc_time;
+       struct berval           msc_bound_ndn;
+       struct berval           msc_cred;
+       unsigned                msc_mscflags;
+       /* NOTE: lc_lcflags is redefined to msc_mscflags to reuse the macros
+        * defined for back-ldap */
+#define        lc_lcflags              msc_mscflags
+
+       int msc_timeout_ops;
+               /* Connection for the select */
+       Connection *conn;
+} a_metasingleconn_t;
+
+typedef struct a_metaconn_t {
+       ldapconn_base_t         lc_base;
+#define        mc_base                 lc_base
+//#define      mc_conn                 mc_base.lcb_conn
+//#define      mc_local_ndn            mc_base.lcb_local_ndn
+//#define      mc_refcnt               mc_base.lcb_refcnt
+//#define      mc_create_time          mc_base.lcb_create_time
+//#define      mc_time                 mc_base.lcb_time
+
+       LDAP_TAILQ_ENTRY(a_metaconn_t)  mc_q;
+
+       /* NOTE: msc_mscflags is used to recycle the #define
+        * in metasingleconn_t */
+       unsigned                msc_mscflags;
+       int     mc_active;
+
+       /*
+        * means that the connection is bound;
+        * of course only one target actually is ...
+        */
+       int                     mc_authz_target;
+#define META_BOUND_NONE                (-1)
+#define META_BOUND_ALL         (-2)
+
+       struct a_metainfo_t     *mc_info;
+
+       int pending_ops;
+       ldap_pvt_thread_mutex_t mc_om_mutex;
+       /* queue for pending operations */
+       LDAP_SLIST_HEAD(BCList, bm_context_t) mc_om_list;
+       /* supersedes the connection stuff */
+       a_metasingleconn_t      *mc_conns;
+} a_metaconn_t;
+
+typedef enum meta_st_t {
+#if 0 /* todo */
+       META_ST_EXACT = LDAP_SCOPE_BASE,
+#endif
+       META_ST_SUBTREE = LDAP_SCOPE_SUBTREE,
+       META_ST_SUBORDINATE = LDAP_SCOPE_SUBORDINATE,
+       META_ST_REGEX /* last + 1 */
+} meta_st_t;
+
+typedef struct a_metasubtree_t {
+       meta_st_t ms_type;
+       union {
+               struct berval msu_dn;
+               struct {
+                       struct berval msr_regex_pattern;
+                       regex_t msr_regex;
+               } msu_regex;
+       } ms_un;
+#define ms_dn ms_un.msu_dn
+#define ms_regex ms_un.msu_regex.msr_regex
+#define ms_regex_pattern ms_un.msu_regex.msr_regex_pattern
+
+       struct a_metasubtree_t *ms_next;
+} a_metasubtree_t;
+
+typedef struct metafilter_t {
+       struct metafilter_t *mf_next;
+       struct berval mf_regex_pattern;
+       regex_t mf_regex;
+} metafilter_t;
+
+typedef struct a_metacommon_t {
+       int                             mc_version;
+       int                             mc_nretries;
+#define META_RETRY_UNDEFINED   (-2)
+#define META_RETRY_FOREVER     (-1)
+#define META_RETRY_NEVER       (0)
+#define META_RETRY_DEFAULT     (10)
+
+       unsigned                mc_flags;
+#define        META_BACK_CMN_ISSET(mc,f)               ( ( (mc)->mc_flags & (f) ) == (f) )
+#define        META_BACK_CMN_QUARANTINE(mc)            META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_QUARANTINE )
+#define        META_BACK_CMN_CHASE_REFERRALS(mc)       META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_CHASE_REFERRALS )
+#define        META_BACK_CMN_NOREFS(mc)                META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_NOREFS )
+#define        META_BACK_CMN_NOUNDEFFILTER(mc)         META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_NOUNDEFFILTER )
+#define        META_BACK_CMN_SAVECRED(mc)              META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_SAVECRED )
+#define        META_BACK_CMN_ST_REQUEST(mc)            META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_ST_REQUEST )
+
+#ifdef SLAPD_META_CLIENT_PR
+       /*
+        * client-side paged results:
+        * -1: accept unsolicited paged results responses
+        *  0: off
+        * >0: always request paged results with size == mt_ps
+        */
+#define META_CLIENT_PR_DISABLE                 (0)
+#define META_CLIENT_PR_ACCEPT_UNSOLICITED      (-1)
+       ber_int_t               mc_ps;
+#endif /* SLAPD_META_CLIENT_PR */
+
+       slap_retry_info_t       mc_quarantine;
+       time_t                  mc_network_timeout;
+       struct timeval  mc_bind_timeout;
+#define META_BIND_TIMEOUT      LDAP_BACK_RESULT_UTIMEOUT
+       time_t                  mc_timeout[ SLAP_OP_LAST ];
+} a_metacommon_t;
+
+typedef struct a_metatarget_t {
+       char                    *mt_uri;
+       ldap_pvt_thread_mutex_t mt_uri_mutex;
+
+       /* TODO: we might want to enable different strategies
+        * for different targets */
+       LDAP_REBIND_PROC        *mt_rebind_f;
+       LDAP_URLLIST_PROC       *mt_urllist_f;
+       void                    *mt_urllist_p;
+
+       metafilter_t    *mt_filter;
+       a_metasubtree_t         *mt_subtree;
+       /* F: subtree-include; T: subtree-exclude */
+       int                     mt_subtree_exclude;
+
+       int                     mt_scope;
+
+       struct berval           mt_psuffix;             /* pretty suffix */
+       struct berval           mt_nsuffix;             /* normalized suffix */
+
+       struct berval           mt_binddn;
+       struct berval           mt_bindpw;
+
+       /* we only care about the TLS options here */
+       slap_bindconf           mt_tls;
+
+       slap_idassert_t         mt_idassert;
+#define        mt_idassert_mode        mt_idassert.si_mode
+#define        mt_idassert_authcID     mt_idassert.si_bc.sb_authcId
+#define        mt_idassert_authcDN     mt_idassert.si_bc.sb_binddn
+#define        mt_idassert_passwd      mt_idassert.si_bc.sb_cred
+#define        mt_idassert_authzID     mt_idassert.si_bc.sb_authzId
+#define        mt_idassert_authmethod  mt_idassert.si_bc.sb_method
+#define        mt_idassert_sasl_mech   mt_idassert.si_bc.sb_saslmech
+#define        mt_idassert_sasl_realm  mt_idassert.si_bc.sb_realm
+#define        mt_idassert_secprops    mt_idassert.si_bc.sb_secprops
+#define        mt_idassert_tls         mt_idassert.si_bc.sb_tls
+#define        mt_idassert_flags       mt_idassert.si_flags
+#define        mt_idassert_authz       mt_idassert.si_authz
+
+       struct ldaprwmap        mt_rwmap;
+
+       sig_atomic_t            mt_isquarantined;
+       ldap_pvt_thread_mutex_t mt_quarantine_mutex;
+
+       a_metacommon_t  mt_mc;
+#define        mt_nretries     mt_mc.mc_nretries
+#define        mt_flags        mt_mc.mc_flags
+#define        mt_version      mt_mc.mc_version
+#define        mt_ps           mt_mc.mc_ps
+#define        mt_network_timeout      mt_mc.mc_network_timeout
+#define        mt_bind_timeout mt_mc.mc_bind_timeout
+#define        mt_timeout      mt_mc.mc_timeout
+#define        mt_quarantine   mt_mc.mc_quarantine
+
+#define        META_BACK_TGT_ISSET(mt,f)               ( ( (mt)->mt_flags & (f) ) == (f) )
+#define        META_BACK_TGT_ISMASK(mt,m,f)            ( ( (mt)->mt_flags & (m) ) == (f) )
+
+#define META_BACK_TGT_SAVECRED(mt)             META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_SAVECRED )
+
+#define META_BACK_TGT_USE_TLS(mt)              META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_USE_TLS )
+#define META_BACK_TGT_PROPAGATE_TLS(mt)                META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_PROPAGATE_TLS )
+#define META_BACK_TGT_TLS_CRITICAL(mt)         META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_TLS_CRITICAL )
+
+#define META_BACK_TGT_CHASE_REFERRALS(mt)      META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_CHASE_REFERRALS )
+
+#define        META_BACK_TGT_T_F(mt)                   META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_T_F_MASK, LDAP_BACK_F_T_F )
+#define        META_BACK_TGT_T_F_DISCOVER(mt)          META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_T_F_MASK2, LDAP_BACK_F_T_F_DISCOVER )
+
+#define        META_BACK_TGT_ABANDON(mt)               META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_ABANDON )
+#define        META_BACK_TGT_IGNORE(mt)                META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_IGNORE )
+#define        META_BACK_TGT_CANCEL(mt)                META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_EXOP )
+#define        META_BACK_TGT_CANCEL_DISCOVER(mt)       META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK2, LDAP_BACK_F_CANCEL_EXOP_DISCOVER )
+#define        META_BACK_TGT_QUARANTINE(mt)            META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_QUARANTINE )
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+#define        META_BACK_TGT_ST_REQUEST(mt)            META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_ST_REQUEST )
+#define        META_BACK_TGT_ST_RESPONSE(mt)           META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_ST_RESPONSE )
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+#define        META_BACK_TGT_NOREFS(mt)                META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_NOREFS )
+#define        META_BACK_TGT_NOUNDEFFILTER(mt)         META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_NOUNDEFFILTER )
+
+#define META_BACK_CFG_MAX_PENDING_OPS          0x80
+#define META_BACK_CFG_MAX_TARGET_CONNS          0xFF
+/* the interval of the timeout checking loop in microseconds
+ * possibly make this configurabe? */
+#define META_BACK_CFG_MAX_TIMEOUT_LOOP          0x70000
+       slap_mask_t             mt_rep_flags;
+       int                     mt_timeout_ops;
+} a_metatarget_t;
+
+typedef struct a_metadncache_t {
+       ldap_pvt_thread_mutex_t mutex;
+       Avlnode                 *tree;
+
+#define META_DNCACHE_DISABLED   (0)
+#define META_DNCACHE_FOREVER    ((time_t)(-1))
+       time_t                  ttl;  /* seconds; 0: no cache, -1: no expiry */
+} a_metadncache_t;
+
+typedef struct a_metacandidates_t {
+       int                     mc_ntargets;
+       SlapReply               *mc_candidates;
+} a_metacandidates_t;
+
+/*
+ * Hook to allow mucking with a_metainfo_t/a_metatarget_t when quarantine is over
+ */
+typedef int (*asyncmeta_quarantine_f)( struct a_metainfo_t *, int target, void * );
+
+struct meta_out_message_t;
+
+typedef struct a_metainfo_t {
+       int                     mi_ntargets;
+       int                     mi_defaulttarget;
+#define META_DEFAULT_TARGET_NONE       (-1)
+
+#define        mi_nretries     mi_mc.mc_nretries
+#define        mi_flags        mi_mc.mc_flags
+#define        mi_version      mi_mc.mc_version
+#define        mi_ps           mi_mc.mc_ps
+#define        mi_network_timeout      mi_mc.mc_network_timeout
+#define        mi_bind_timeout mi_mc.mc_bind_timeout
+#define        mi_timeout      mi_mc.mc_timeout
+#define        mi_quarantine   mi_mc.mc_quarantine
+
+       a_metatarget_t          **mi_targets;
+       a_metacandidates_t      *mi_candidates;
+
+       LDAP_REBIND_PROC        *mi_rebind_f;
+       LDAP_URLLIST_PROC       *mi_urllist_f;
+
+       a_metadncache_t         mi_cache;
+
+       struct {
+               int                                             mic_num;
+               LDAP_TAILQ_HEAD(mc_conn_priv_q, a_metaconn_t)   mic_priv;
+       }                       mi_conn_priv[ LDAP_BACK_PCONN_LAST ];
+       int                     mi_conn_priv_max;
+
+       /* NOTE: quarantine uses the connection mutex */
+       asyncmeta_quarantine_f  mi_quarantine_f;
+       void                    *mi_quarantine_p;
+
+#define        li_flags                mi_flags
+/* uses flags as defined in <back-ldap/back-ldap.h> */
+#define        META_BACK_F_ONERR_STOP          LDAP_BACK_F_ONERR_STOP
+#define        META_BACK_F_ONERR_REPORT        (0x02000000U)
+#define        META_BACK_F_ONERR_MASK          (META_BACK_F_ONERR_STOP|META_BACK_F_ONERR_REPORT)
+#define        META_BACK_F_DEFER_ROOTDN_BIND   (0x04000000U)
+#define        META_BACK_F_PROXYAUTHZ_ALWAYS   (0x08000000U)   /* users always proxyauthz */
+#define        META_BACK_F_PROXYAUTHZ_ANON     (0x10000000U)   /* anonymous always proxyauthz */
+#define        META_BACK_F_PROXYAUTHZ_NOANON   (0x20000000U)   /* anonymous remains anonymous */
+
+#define        META_BACK_ONERR_STOP(mi)        LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_STOP )
+#define        META_BACK_ONERR_REPORT(mi)      LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_REPORT )
+#define        META_BACK_ONERR_CONTINUE(mi)    ( !LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_MASK ) )
+
+#define META_BACK_DEFER_ROOTDN_BIND(mi)        LDAP_BACK_ISSET( (mi), META_BACK_F_DEFER_ROOTDN_BIND )
+#define META_BACK_PROXYAUTHZ_ALWAYS(mi)        LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_ALWAYS )
+#define META_BACK_PROXYAUTHZ_ANON(mi)  LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_ANON )
+#define META_BACK_PROXYAUTHZ_NOANON(mi)        LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_NOANON )
+
+#define META_BACK_QUARANTINE(mi)       LDAP_BACK_ISSET( (mi), LDAP_BACK_F_QUARANTINE )
+
+       time_t                  mi_idle_timeout;
+       struct re_s *mi_task;
+
+       a_metacommon_t  mi_mc;
+       ldap_extra_t    *mi_ldap_extra;
+
+       int                    mi_max_timeout_ops;
+       int                    mi_max_pending_ops;
+       int                    mi_max_target_conns;
+       /* mutex for access to the connection structures */
+       ldap_pvt_thread_mutex_t mi_mc_mutex;
+       int                    mi_num_conns;
+       int                    mi_next_conn;
+       a_metaconn_t          *mi_conns;
+
+} a_metainfo_t;
+
+typedef enum meta_op_type {
+       META_OP_ALLOW_MULTIPLE = 0,
+       META_OP_REQUIRE_SINGLE,
+       META_OP_REQUIRE_ALL
+} meta_op_type;
+
+extern a_metaconn_t *
+asyncmeta_getconn(
+       Operation               *op,
+       SlapReply               *rs,
+       SlapReply               *candidates,
+       int                     *candidate,
+       ldap_back_send_t        sendok,
+       int                     alloc_new);
+
+extern int
+asyncmeta_retry(
+       Operation               *op,
+       SlapReply               *rs,
+       a_metaconn_t            **mcp,
+       int                     candidate,
+       ldap_back_send_t        sendok );
+
+extern void
+asyncmeta_conn_free(
+       void                    *v_mc );
+
+extern int
+asyncmeta_init_one_conn(
+       Operation               *op,
+       SlapReply               *rs,
+       a_metaconn_t            *mc,
+       int                     candidate,
+       int                     ispriv,
+       ldap_back_send_t        sendok,
+       int                     dolock );
+
+extern void
+asyncmeta_quarantine(
+       Operation               *op,
+       a_metainfo_t            *mi,
+       SlapReply               *rs,
+       int                     candidate );
+
+extern int
+asyncmeta_dobind(
+       Operation               *op,
+       SlapReply               *rs,
+       a_metaconn_t            *mc,
+       ldap_back_send_t        sendok,
+       SlapReply               *candidates);
+
+extern int
+asyncmeta_single_dobind(
+       Operation               *op,
+       SlapReply               *rs,
+       a_metaconn_t            **mcp,
+       int                     candidate,
+       ldap_back_send_t        sendok,
+       int                     retries,
+       int                     dolock );
+
+extern int
+asyncmeta_proxy_authz_cred(
+       a_metaconn_t            *mc,
+       int                     candidate,
+       Operation               *op,
+       SlapReply               *rs,
+       ldap_back_send_t        sendok,
+       struct berval           *binddn,
+       struct berval           *bindcred,
+       int                     *method );
+
+extern int
+asyncmeta_cancel(
+       a_metaconn_t            *mc,
+       Operation               *op,
+       SlapReply               *rs,
+       ber_int_t               msgid,
+       int                     candidate,
+       ldap_back_send_t        sendok );
+
+extern int
+asyncmeta_op_result(
+       a_metaconn_t            *mc,
+       Operation               *op,
+       SlapReply               *rs,
+       int                     candidate,
+       ber_int_t               msgid,
+       time_t                  timeout,
+       ldap_back_send_t        sendok );
+
+extern int
+asyncmeta_controls_add(
+       Operation       *op,
+       SlapReply       *rs,
+       a_metaconn_t    *mc,
+       int             candidate,
+       LDAPControl     ***pctrls );
+
+extern int
+asyncmeta_LTX_init_module(
+       int                     argc,
+       char                    *argv[] );
+
+/*
+ * Candidate stuff
+ */
+extern int
+asyncmeta_is_candidate(
+       a_metatarget_t          *mt,
+       struct berval           *ndn,
+       int                     scope );
+
+extern int
+asyncmeta_select_unique_candidate(
+       a_metainfo_t            *mi,
+       struct berval           *ndn );
+
+extern int
+asyncmeta_clear_unused_candidates(
+       Operation               *op,
+       int                     candidate,
+       a_metaconn_t            *mc,
+       SlapReply       *candidates);
+
+/*
+ * Dn cache stuff (experimental)
+ */
+extern int
+asyncmeta_dncache_cmp(
+       const void              *c1,
+       const void              *c2 );
+
+extern int
+asyncmeta_dncache_dup(
+       void                    *c1,
+       void                    *c2 );
+
+#define META_TARGET_NONE       (-1)
+#define META_TARGET_MULTIPLE   (-2)
+extern int
+asyncmeta_dncache_get_target(
+       a_metadncache_t         *cache,
+       struct berval           *ndn );
+
+extern int
+meta_dncache_update_entry(
+       a_metadncache_t         *cache,
+       struct berval           *ndn,
+       int                     target );
+
+extern int
+asyncmeta_dncache_delete_entry(
+       a_metadncache_t         *cache,
+       struct berval           *ndn );
+
+extern void
+asyncmeta_dncache_free( void *entry );
+
+extern void
+asyncmeta_back_map_free( struct ldapmap *lm );
+
+extern int
+asyncmeta_subtree_destroy( a_metasubtree_t *ms );
+
+extern void
+asyncmeta_filter_destroy( metafilter_t *mf );
+
+extern int
+asyncmeta_target_finish( a_metainfo_t *mi, a_metatarget_t *mt,
+       const char *log, char *msg, size_t msize
+);
+
+
+extern LDAP_REBIND_PROC                asyncmeta_back_default_rebind;
+extern LDAP_URLLIST_PROC       asyncmeta_back_default_urllist;
+
+/* IGNORE means that target does not (no longer) participate
+ * in the search;
+ * NOTREADY means the search on that target has not been initialized yet
+ */
+#define        META_MSGID_IGNORE       (-1)
+#define        META_MSGID_NEED_BIND    (-2)
+#define        META_MSGID_CONNECTING   (-3)
+#define META_MSGID_UNDEFINED    (-4)
+
+typedef enum meta_search_candidate_t {
+       META_SEARCH_UNDEFINED = -2,
+       META_SEARCH_ERR = -1,
+       META_SEARCH_NOT_CANDIDATE,
+       META_SEARCH_CANDIDATE,
+       META_SEARCH_BINDING,
+       META_SEARCH_NEED_BIND,
+       META_SEARCH_CONNECTING
+} meta_search_candidate_t;
+
+Operation* asyncmeta_copy_op(Operation *op);
+void asyncmeta_clear_bm_context(bm_context_t *bc);
+
+int asyncmeta_add_message_queue(a_metaconn_t *mc, bm_context_t *bc);
+void asyncmeta_drop_bc(a_metaconn_t *mc, bm_context_t *bc);
+
+bm_context_t *
+asyncmeta_find_message(ber_int_t msgid, a_metaconn_t *mc, int candidate);
+
+bm_context_t *
+asyncmeta_find_message_by_opmsguid(ber_int_t msgid, a_metaconn_t *mc, int remove);
+
+void* asyncmeta_op_handle_result(void *ctx, void *arg);
+int asyncmeta_back_cleanup( Operation *op, SlapReply *rs, bm_context_t *bm );
+
+int
+asyncmeta_clear_one_msc(
+       Operation       *op,
+       a_metaconn_t    *msc,
+       int             candidate );
+
+a_metaconn_t *
+asyncmeta_get_next_mc( a_metainfo_t *mi );
+
+void* asyncmeta_timeout_loop(void *ctx, void *arg);
+int
+asyncmeta_start_timeout_loop(a_metatarget_t *mt, a_metainfo_t *mi);
+void asyncmeta_set_msc_time(a_metasingleconn_t *msc);
+void asyncmeta_clear_message_queue(a_metasingleconn_t *msc);
+
+int asyncmeta_back_cancel(
+       a_metaconn_t    *mc,
+       Operation               *op,
+       ber_int_t               msgid,
+       int                             candidate );
+
+int
+asyncmeta_back_cancel_msc(
+       Operation               *op,
+       SlapReply               *rs,
+       ber_int_t               msgid,
+       a_metasingleconn_t      *msc,
+       int                     candidate,
+       ldap_back_send_t        sendok );
+
+int
+asyncmeta_back_abandon_candidate(
+       a_metaconn_t            *mc,
+       Operation               *op,
+       ber_int_t               msgid,
+       int                     candidate );
+void
+asyncmeta_send_result(bm_context_t* bc, int error, char *text);
+
+int asyncmeta_new_bm_context(Operation *op, SlapReply *rs, bm_context_t **new_bc, int ntargets);
+int asyncmeta_start_listeners(a_metaconn_t *mc, SlapReply *candidates);
+int asyncmeta_start_one_listener(a_metaconn_t *mc, SlapReply *candidates, int candidate);
+
+meta_search_candidate_t
+asyncmeta_back_search_start(
+       Operation             *op,
+       SlapReply             *rs,
+       a_metaconn_t          *mc,
+       bm_context_t          *bc,
+       int                   candidate,
+       struct berval         *prcookie,
+       ber_int_t             prsize );
+
+meta_search_candidate_t
+asyncmeta_dobind_init(
+       Operation            *op,
+       SlapReply            *rs,
+       bm_context_t         *bc,
+       a_metaconn_t         *mc,
+       int                  candidate);
+
+meta_search_candidate_t
+asyncmeta_dobind_init_with_retry(
+       Operation            *op,
+       SlapReply            *rs,
+       bm_context_t         *bc,
+       a_metaconn_t         *mc,
+       int                  candidate);
+
+meta_search_candidate_t
+asyncmeta_back_add_start(Operation *op,
+                        SlapReply *rs,
+                        a_metaconn_t *mc,
+                        bm_context_t *bc,
+                        int candidate);
+meta_search_candidate_t
+asyncmeta_back_modify_start(Operation *op,
+                        SlapReply *rs,
+                        a_metaconn_t *mc,
+                        bm_context_t *bc,
+                        int candidate);
+
+meta_search_candidate_t
+asyncmeta_back_modrdn_start(Operation *op,
+                        SlapReply *rs,
+                        a_metaconn_t *mc,
+                        bm_context_t *bc,
+                        int candidate);
+meta_search_candidate_t
+asyncmeta_back_delete_start(Operation *op,
+                        SlapReply *rs,
+                        a_metaconn_t *mc,
+                        bm_context_t *bc,
+                        int candidate);
+
+meta_search_candidate_t
+asyncmeta_back_compare_start(Operation *op,
+                        SlapReply *rs,
+                        a_metaconn_t *mc,
+                        bm_context_t *bc,
+                        int candidate);
+
+
+void
+asyncmeta_sender_error(Operation *op,
+                      SlapReply *rs,
+                      slap_callback *cb);
+
+
+LDAP_END_DECL
+
+#endif /* SLAPD_ASYNCMETA_H */
diff --git a/servers/slapd/back-asyncmeta/bind.c b/servers/slapd/back-asyncmeta/bind.c
new file mode 100644 (file)
index 0000000..08db5e9
--- /dev/null
@@ -0,0 +1,1978 @@
+/* bind.c - bind request handler functions for binding
+ * to remote targets for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+
+
+#define AVL_INTERNAL
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+#include "lutil_ldap.h"
+
+static int
+asyncmeta_proxy_authz_bind(
+       a_metaconn_t            *mc,
+       int                     candidate,
+       Operation               *op,
+       SlapReply               *rs,
+       ldap_back_send_t        sendok,
+       int                     dolock );
+
+static int
+asyncmeta_single_bind(
+       Operation               *op,
+       SlapReply               *rs,
+       a_metaconn_t            *mc,
+       int                     candidate );
+
+int
+asyncmeta_back_bind( Operation *op, SlapReply *rs )
+{
+       a_metainfo_t    *mi = ( a_metainfo_t * )op->o_bd->be_private;
+       a_metaconn_t    *mc = NULL;
+
+       int             rc = LDAP_OTHER,
+                       i,
+                       gotit = 0,
+                       isroot = 0;
+
+       SlapReply       *candidates;
+
+       candidates = op->o_tmpcalloc(mi->mi_ntargets, sizeof(SlapReply),op->o_tmpmemctx);
+       rs->sr_err = LDAP_SUCCESS;
+
+       Debug( LDAP_DEBUG_ARGS, "%s asyncmeta_back_bind: dn=\"%s\".\n",
+               op->o_log_prefix, op->o_req_dn.bv_val, 0 );
+
+       /* the test on the bind method should be superfluous */
+       switch ( be_rootdn_bind( op, rs ) ) {
+       case LDAP_SUCCESS:
+               if ( META_BACK_DEFER_ROOTDN_BIND( mi ) ) {
+                       /* frontend will return success */
+                       return rs->sr_err;
+               }
+
+               isroot = 1;
+               /* fallthru */
+
+       case SLAP_CB_CONTINUE:
+               break;
+
+       default:
+               /* be_rootdn_bind() sent result */
+               return rs->sr_err;
+       }
+
+       /* we need asyncmeta_getconn() not send result even on error,
+        * because we want to intercept the error and make it
+        * invalidCredentials */
+       mc = asyncmeta_getconn( op, rs, candidates, NULL, LDAP_BACK_BIND_DONTSEND, 1 );
+       if ( !mc ) {
+               if ( LogTest( LDAP_DEBUG_ANY ) ) {
+                       char    buf[ SLAP_TEXT_BUFLEN ];
+
+                       snprintf( buf, sizeof( buf ),
+                               "asyncmeta_back_bind: no target "
+                               "for dn \"%s\" (%d%s%s).",
+                               op->o_req_dn.bv_val, rs->sr_err,
+                               rs->sr_text ? ". " : "",
+                               rs->sr_text ? rs->sr_text : "" );
+                       Debug( LDAP_DEBUG_ANY,
+                               "%s %s\n",
+                               op->o_log_prefix, buf, 0 );
+               }
+
+               /* FIXME: there might be cases where we don't want
+                * to map the error onto invalidCredentials */
+               switch ( rs->sr_err ) {
+               case LDAP_NO_SUCH_OBJECT:
+               case LDAP_UNWILLING_TO_PERFORM:
+                       rs->sr_err = LDAP_INVALID_CREDENTIALS;
+                       rs->sr_text = NULL;
+                       break;
+               }
+               send_ldap_result( op, rs );
+               return rs->sr_err;
+       }
+
+       /*
+        * Each target is scanned ...
+        */
+       mc->mc_authz_target = META_BOUND_NONE;
+       for ( i = 0; i < mi->mi_ntargets; i++ ) {
+               a_metatarget_t  *mt = mi->mi_targets[ i ];
+               int             lerr;
+
+               /*
+                * Skip non-candidates
+                */
+               if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+                       continue;
+               }
+
+               if ( gotit == 0 ) {
+                       /* set rc to LDAP_SUCCESS only if at least
+                        * one candidate has been tried */
+                       rc = LDAP_SUCCESS;
+                       gotit = 1;
+
+               } else if ( !isroot ) {
+                       /*
+                        * A bind operation is expected to have
+                        * ONE CANDIDATE ONLY!
+                        */
+                       Debug( LDAP_DEBUG_ANY,
+                               "### %s asyncmeta_back_bind: more than one"
+                               " candidate selected...\n",
+                               op->o_log_prefix, 0, 0 );
+               }
+
+               if ( isroot ) {
+                       if ( mt->mt_idassert_authmethod == LDAP_AUTH_NONE
+                               || BER_BVISNULL( &mt->mt_idassert_authcDN ) )
+                       {
+                               a_metasingleconn_t      *msc = &mc->mc_conns[ i ];
+
+                               if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+                                       ch_free( msc->msc_bound_ndn.bv_val );
+                                       BER_BVZERO( &msc->msc_bound_ndn );
+                               }
+
+                               if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+                                       /* destroy sensitive data */
+                                       memset( msc->msc_cred.bv_val, 0,
+                                               msc->msc_cred.bv_len );
+                                       ch_free( msc->msc_cred.bv_val );
+                                       BER_BVZERO( &msc->msc_cred );
+                               }
+
+                               continue;
+                       }
+
+
+                       (void)asyncmeta_proxy_authz_bind( mc, i, op, rs, LDAP_BACK_DONTSEND, 1 );
+                       lerr = rs->sr_err;
+
+               } else {
+                       lerr = asyncmeta_single_bind( op, rs, mc, i );
+               }
+
+               if ( lerr != LDAP_SUCCESS ) {
+                       rc = rs->sr_err = lerr;
+
+                       /* FIXME: in some cases (e.g. unavailable)
+                        * do not assume it's not candidate; rather
+                        * mark this as an error to be eventually
+                        * reported to client */
+                       META_CANDIDATE_CLEAR( &candidates[ i ] );
+                       break;
+               }
+       }
+
+       if ( mc != NULL ) {
+               for ( i = 0; i < mi->mi_ntargets; i++ ) {
+                       a_metasingleconn_t      *msc = &mc->mc_conns[ i ];
+                       if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+                               ch_free( msc->msc_bound_ndn.bv_val );
+                       }
+
+                       if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+                               /* destroy sensitive data */
+                               memset( msc->msc_cred.bv_val, 0,
+                                       msc->msc_cred.bv_len );
+                               ch_free( msc->msc_cred.bv_val );
+                       }
+               }
+               asyncmeta_back_conn_free( mc );
+       }
+
+       /*
+        * rc is LDAP_SUCCESS if at least one bind succeeded,
+        * err is the last error that occurred during a bind;
+        * if at least (and at most?) one bind succeeds, fine.
+        */
+       if ( rc != LDAP_SUCCESS ) {
+
+               /*
+                * deal with bind failure ...
+                */
+
+               /*
+                * no target was found within the naming context,
+                * so bind must fail with invalid credentials
+                */
+               if ( rs->sr_err == LDAP_SUCCESS && gotit == 0 ) {
+                       rs->sr_err = LDAP_INVALID_CREDENTIALS;
+               } else {
+                       rs->sr_err = slap_map_api2result( rs );
+               }
+               send_ldap_result( op, rs );
+               return rs->sr_err;
+
+       }
+       return LDAP_SUCCESS;
+}
+
+static int
+asyncmeta_bind_op_result(
+       Operation               *op,
+       SlapReply               *rs,
+       a_metaconn_t            *mc,
+       int                     candidate,
+       int                     msgid,
+       ldap_back_send_t        sendok,
+       int                     dolock )
+{
+       a_metainfo_t            *mi = mc->mc_info;
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+       LDAPMessage             *res;
+       struct timeval          tv;
+       int                     rc;
+       int                     nretries = mt->mt_nretries;
+       char                    buf[ SLAP_TEXT_BUFLEN ];
+
+       Debug( LDAP_DEBUG_TRACE,
+               ">>> %s asyncmeta_bind_op_result[%d]\n",
+               op->o_log_prefix, candidate, 0 );
+
+       /* make sure this is clean */
+       assert( rs->sr_ctrls == NULL );
+
+       if ( rs->sr_err == LDAP_SUCCESS ) {
+               time_t          stoptime = (time_t)(-1),
+                               timeout;
+               int             timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+                               LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+               const char      *timeout_text = "Operation timed out";
+               slap_op_t       opidx = slap_req2op( op->o_tag );
+
+               /* since timeout is not specified, compute and use
+                * the one specific to the ongoing operation */
+               if ( opidx == LDAP_REQ_SEARCH ) {
+                       if ( op->ors_tlimit <= 0 ) {
+                               timeout = 0;
+
+                       } else {
+                               timeout = op->ors_tlimit;
+                               timeout_err = LDAP_TIMELIMIT_EXCEEDED;
+                               timeout_text = NULL;
+                       }
+
+               } else {
+                       timeout = mt->mt_timeout[ opidx ];
+               }
+
+               /* better than nothing :) */
+               if ( timeout == 0 ) {
+                       if ( mi->mi_idle_timeout ) {
+                               timeout = mi->mi_idle_timeout;
+
+                       }
+               }
+
+               if ( timeout ) {
+                       stoptime = op->o_time + timeout;
+               }
+
+               LDAP_BACK_TV_SET( &tv );
+
+               /*
+                * handle response!!!
+                */
+retry:;
+               rc = ldap_result( msc->msc_ld, msgid, LDAP_MSG_ALL, &tv, &res );
+               switch ( rc ) {
+               case 0:
+                       if ( nretries != META_RETRY_NEVER
+                               || ( timeout && slap_get_time() <= stoptime ) )
+                       {
+                               ldap_pvt_thread_yield();
+                               if ( nretries > 0 ) {
+                                       nretries--;
+                               }
+                               tv = mt->mt_bind_timeout;
+                               goto retry;
+                       }
+
+                       /* don't let anyone else use this handler,
+                        * because there's a pending bind that will not
+                        * be acknowledged */
+                       assert( LDAP_BACK_CONN_BINDING( msc ) );
+
+#ifdef DEBUG_205
+                       Debug( LDAP_DEBUG_ANY, "### %s asyncmeta_bind_op_result ldap_unbind_ext[%d] ld=%p\n",
+                               op->o_log_prefix, candidate, (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+                       rs->sr_err = timeout_err;
+                       rs->sr_text = timeout_text;
+                       break;
+
+               case -1:
+                       ldap_get_option( msc->msc_ld, LDAP_OPT_ERROR_NUMBER,
+                               &rs->sr_err );
+
+                       snprintf( buf, sizeof( buf ),
+                               "err=%d (%s) nretries=%d",
+                               rs->sr_err, ldap_err2string( rs->sr_err ), nretries );
+                       Debug( LDAP_DEBUG_ANY,
+                               "### %s asyncmeta_bind_op_result[%d]: %s.\n",
+                               op->o_log_prefix, candidate, buf );
+                       break;
+
+               default:
+                       /* only touch when activity actually took place... */
+                       if ( mi->mi_idle_timeout != 0 && msc->msc_time < op->o_time ) {
+                               msc->msc_time = op->o_time;
+                       }
+
+                       /* FIXME: matched? referrals? response controls? */
+                       rc = ldap_parse_result( msc->msc_ld, res, &rs->sr_err,
+                                       NULL, NULL, NULL, NULL, 1 );
+                       if ( rc != LDAP_SUCCESS ) {
+                               rs->sr_err = rc;
+                       }
+                       rs->sr_err = slap_map_api2result( rs );
+                       break;
+               }
+       }
+
+       rs->sr_err = slap_map_api2result( rs );
+       Debug( LDAP_DEBUG_TRACE,
+               "<<< %s asyncmeta_bind_op_result[%d] err=%d\n",
+               op->o_log_prefix, candidate, rs->sr_err );
+
+       return rs->sr_err;
+}
+
+/*
+ * asyncmeta_single_bind
+ *
+ * attempts to perform a bind with creds
+ */
+static int
+asyncmeta_single_bind(
+       Operation               *op,
+       SlapReply               *rs,
+       a_metaconn_t            *mc,
+       int                     candidate )
+{
+       a_metainfo_t            *mi = mc->mc_info;
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+       struct berval           mdn = BER_BVNULL;
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+       int                     msgid;
+       a_dncookie              dc;
+       struct berval           save_o_dn;
+       int                     save_o_do_not_cache;
+       LDAPControl             **ctrls = NULL;
+
+       if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+               ch_free( msc->msc_bound_ndn.bv_val );
+               BER_BVZERO( &msc->msc_bound_ndn );
+       }
+
+       if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+               /* destroy sensitive data */
+               memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len );
+               ch_free( msc->msc_cred.bv_val );
+               BER_BVZERO( &msc->msc_cred );
+       }
+
+       /*
+        * Rewrite the bind dn if needed
+        */
+       dc.target = mt;
+       dc.conn = op->o_conn;
+       dc.rs = rs;
+       dc.ctx = "bindDN";
+
+       if ( asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn ) ) {
+               rs->sr_text = "DN rewrite error";
+               rs->sr_err = LDAP_OTHER;
+               return rs->sr_err;
+       }
+
+       /* don't add proxyAuthz; set the bindDN */
+       save_o_dn = op->o_dn;
+       save_o_do_not_cache = op->o_do_not_cache;
+       op->o_do_not_cache = 1;
+       op->o_dn = op->o_req_dn;
+
+       ctrls = op->o_ctrls;
+       rs->sr_err = asyncmeta_controls_add( op, rs, mc, candidate, &ctrls );
+       op->o_dn = save_o_dn;
+       op->o_do_not_cache = save_o_do_not_cache;
+       if ( rs->sr_err != LDAP_SUCCESS ) {
+               goto return_results;
+       }
+
+       /* FIXME: this fixes the bind problem right now; we need
+        * to use the asynchronous version to get the "matched"
+        * and more in case of failure ... */
+       /* FIXME: should we check if at least some of the op->o_ctrls
+        * can/should be passed? */
+       for (;;) {
+               rs->sr_err = ldap_sasl_bind( msc->msc_ld, mdn.bv_val,
+                       LDAP_SASL_SIMPLE, &op->orb_cred,
+                       ctrls, NULL, &msgid );
+               if ( rs->sr_err != LDAP_X_CONNECTING ) {
+                       break;
+               }
+               ldap_pvt_thread_yield();
+       }
+
+       mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+       asyncmeta_bind_op_result( op, rs, mc, candidate, msgid, LDAP_BACK_DONTSEND, 1 );
+       if ( rs->sr_err != LDAP_SUCCESS ) {
+               goto return_results;
+       }
+
+       /* If defined, proxyAuthz will be used also when
+        * back-ldap is the authorizing backend; for this
+        * purpose, a successful bind is followed by a
+        * bind with the configured identity assertion */
+       /* NOTE: use with care */
+       if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) {
+               asyncmeta_proxy_authz_bind( mc, candidate, op, rs, LDAP_BACK_SENDERR, 1 );
+               if ( !LDAP_BACK_CONN_ISBOUND( msc ) ) {
+                       goto return_results;
+               }
+               goto cache_refresh;
+       }
+
+       ber_bvreplace( &msc->msc_bound_ndn, &op->o_req_ndn );
+       LDAP_BACK_CONN_ISBOUND_SET( msc );
+       mc->mc_authz_target = candidate;
+
+       if ( META_BACK_TGT_SAVECRED( mt ) ) {
+               if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+                       memset( msc->msc_cred.bv_val, 0,
+                               msc->msc_cred.bv_len );
+               }
+               ber_bvreplace( &msc->msc_cred, &op->orb_cred );
+               ldap_set_rebind_proc( msc->msc_ld, mt->mt_rebind_f, msc );
+       }
+
+cache_refresh:;
+       if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED
+                       && !BER_BVISEMPTY( &op->o_req_ndn ) )
+       {
+               ( void )asyncmeta_dncache_update_entry( &mi->mi_cache,
+                               &op->o_req_ndn, candidate );
+       }
+
+return_results:;
+       if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+               free( mdn.bv_val );
+       }
+
+       if ( META_BACK_TGT_QUARANTINE( mt ) ) {
+               asyncmeta_quarantine( op, mi, rs, candidate );
+       }
+       ldap_unbind_ext( msc->msc_ld, NULL, NULL );
+       msc->msc_ld = NULL;
+       ldap_ld_free( msc->msc_ldr, 0, NULL, NULL );
+       msc->msc_ldr = NULL;
+       return rs->sr_err;
+}
+
+/*
+ * asyncmeta_back_single_dobind
+ */
+int
+asyncmeta_back_single_dobind(
+       Operation               *op,
+       SlapReply               *rs,
+       a_metaconn_t            **mcp,
+       int                     candidate,
+       ldap_back_send_t        sendok,
+       int                     nretries,
+       int                     dolock )
+{
+       a_metaconn_t            *mc = *mcp;
+       a_metainfo_t            *mi = mc->mc_info;
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+       int                     msgid;
+
+       assert( !LDAP_BACK_CONN_ISBOUND( msc ) );
+
+       if ( op->o_conn != NULL &&
+               !op->o_do_not_cache &&
+               ( BER_BVISNULL( &msc->msc_bound_ndn ) ||
+                       BER_BVISEMPTY( &msc->msc_bound_ndn ) ||
+                       ( LDAP_BACK_CONN_ISPRIV( mc ) && dn_match( &msc->msc_bound_ndn, &mt->mt_idassert_authcDN ) ) ||
+                       ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) ) )
+       {
+               (void)asyncmeta_proxy_authz_bind( mc, candidate, op, rs, sendok, dolock );
+
+       } else {
+               char *binddn = "";
+               struct berval cred = BER_BVC( "" );
+
+               /* use credentials if available */
+               if ( !BER_BVISNULL( &msc->msc_bound_ndn )
+                       && !BER_BVISNULL( &msc->msc_cred ) )
+               {
+                       binddn = msc->msc_bound_ndn.bv_val;
+                       cred = msc->msc_cred;
+               }
+
+               for (;;) {
+                       rs->sr_err = ldap_sasl_bind( msc->msc_ld,
+                               binddn, LDAP_SASL_SIMPLE, &cred,
+                               NULL, NULL, &msgid );
+                       if ( rs->sr_err != LDAP_X_CONNECTING ) {
+                               break;
+                       }
+                       ldap_pvt_thread_yield();
+               }
+
+               rs->sr_err = asyncmeta_bind_op_result( op, rs, mc, candidate, msgid, sendok, dolock );
+
+               /* if bind succeeded, but anonymous, clear msc_bound_ndn */
+               if ( rs->sr_err != LDAP_SUCCESS || binddn[0] == '\0' ) {
+                       if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+                               ber_memfree( msc->msc_bound_ndn.bv_val );
+                               BER_BVZERO( &msc->msc_bound_ndn );
+                       }
+
+                       if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+                               memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len );
+                               ber_memfree( msc->msc_cred.bv_val );
+                               BER_BVZERO( &msc->msc_cred );
+                       }
+               }
+       }
+
+       if ( META_BACK_TGT_QUARANTINE( mt ) ) {
+               asyncmeta_quarantine( op, mi, rs, candidate );
+       }
+
+       return rs->sr_err;
+}
+
+/*
+ * asyncmeta_back_default_rebind
+ *
+ * This is a callback used for chasing referrals using the same
+ * credentials as the original user on this session.
+ */
+int
+asyncmeta_back_default_rebind(
+       LDAP                    *ld,
+       LDAP_CONST char         *url,
+       ber_tag_t               request,
+       ber_int_t               msgid,
+       void                    *params )
+{
+       a_metasingleconn_t      *msc = ( a_metasingleconn_t * )params;
+
+       return ldap_sasl_bind_s( ld, msc->msc_bound_ndn.bv_val,
+                       LDAP_SASL_SIMPLE, &msc->msc_cred,
+                       NULL, NULL, NULL );
+}
+
+/*
+ * meta_back_default_urllist
+ *
+ * This is a callback used for mucking with the urllist
+ */
+int
+asyncmeta_back_default_urllist(
+       LDAP            *ld,
+       LDAPURLDesc     **urllist,
+       LDAPURLDesc     **url,
+       void            *params )
+{
+       a_metatarget_t  *mt = (a_metatarget_t *)params;
+       LDAPURLDesc     **urltail;
+
+       if ( urllist == url ) {
+               return LDAP_SUCCESS;
+       }
+
+       for ( urltail = &(*url)->lud_next; *urltail; urltail = &(*urltail)->lud_next )
+               /* count */ ;
+
+       *urltail = *urllist;
+       *urllist = *url;
+       *url = NULL;
+
+       ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex );
+       if ( mt->mt_uri ) {
+               ch_free( mt->mt_uri );
+       }
+
+       ldap_get_option( ld, LDAP_OPT_URI, (void *)&mt->mt_uri );
+       ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex );
+
+       return LDAP_SUCCESS;
+}
+
+int
+asyncmeta_back_cancel(
+       a_metaconn_t            *mc,
+       Operation               *op,
+       ber_int_t               msgid,
+       int                     candidate )
+{
+
+       a_metainfo_t            *mi = mc->mc_info;
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+
+       int                     rc = LDAP_OTHER;
+
+       Debug( LDAP_DEBUG_TRACE, ">>> %s asyncmeta_back_cancel[%d] msgid=%d\n",
+               op->o_log_prefix, candidate, msgid );
+
+       if (msc->msc_ld == NULL) {
+               Debug( LDAP_DEBUG_TRACE, ">>> %s asyncmeta_back_cancel[%d] msgid=%d\n already reset",
+               op->o_log_prefix, candidate, msgid );
+               return LDAP_SUCCESS;
+       }
+
+       /* default behavior */
+       if ( META_BACK_TGT_ABANDON( mt ) ) {
+               rc = ldap_abandon_ext( msc->msc_ld, msgid, NULL, NULL );
+
+       } else if ( META_BACK_TGT_IGNORE( mt ) ) {
+               rc = ldap_pvt_discard( msc->msc_ld, msgid );
+
+       } else if ( META_BACK_TGT_CANCEL( mt ) ) {
+               rc = ldap_cancel_s( msc->msc_ld, msgid, NULL, NULL );
+
+       } else {
+               assert( 0 );
+       }
+
+       Debug( LDAP_DEBUG_TRACE, "<<< %s asyncmeta_back_cancel[%d] err=%d\n",
+               op->o_log_prefix, candidate, rc );
+
+       return rc;
+}
+
+
+int
+asyncmeta_back_abandon_candidate(
+       a_metaconn_t            *mc,
+       Operation               *op,
+       ber_int_t               msgid,
+       int                     candidate )
+{
+
+       a_metainfo_t            *mi = mc->mc_info;
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+
+       int                     rc = LDAP_OTHER;
+
+       Debug( LDAP_DEBUG_TRACE, ">>> %s asyncmeta_back_abandon[%d] msgid=%d\n",
+               op->o_log_prefix, candidate, msgid );
+
+       rc = ldap_abandon_ext( msc->msc_ld, msgid, NULL, NULL );
+
+       Debug( LDAP_DEBUG_TRACE, "<<< %s asyncmeta_back_abandon[%d] err=%d\n",
+               op->o_log_prefix, candidate, rc );
+
+       return rc;
+}
+
+int
+asyncmeta_back_cancel_msc(
+       Operation               *op,
+       SlapReply               *rs,
+       ber_int_t               msgid,
+       a_metasingleconn_t      *msc,
+       int                     candidate,
+       ldap_back_send_t        sendok )
+{
+       a_metainfo_t            *mi = (a_metainfo_t *)op->o_bd->be_private;
+
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+
+       int                     rc = LDAP_OTHER;
+
+       Debug( LDAP_DEBUG_TRACE, ">>> %s asyncmeta_back_cancel_msc[%d] msgid=%d\n",
+               op->o_log_prefix, candidate, msgid );
+
+       /* default behavior */
+       if ( META_BACK_TGT_ABANDON( mt ) ) {
+               rc = ldap_abandon_ext( msc->msc_ld, msgid, NULL, NULL );
+
+       } else if ( META_BACK_TGT_IGNORE( mt ) ) {
+               rc = ldap_pvt_discard( msc->msc_ld, msgid );
+
+       } else if ( META_BACK_TGT_CANCEL( mt ) ) {
+               rc = ldap_cancel_s( msc->msc_ld, msgid, NULL, NULL );
+
+       } else {
+               assert( 0 );
+       }
+
+       Debug( LDAP_DEBUG_TRACE, "<<< %s asyncmeta_back_cancel_msc[%d] err=%d\n",
+               op->o_log_prefix, candidate, rc );
+
+       return rc;
+}
+
+/*
+ * FIXME: error return must be handled in a cleaner way ...
+ */
+int
+asyncmeta_back_op_result(
+       a_metaconn_t            *mc,
+       Operation               *op,
+       SlapReply               *rs,
+       int                     candidate,
+       ber_int_t               msgid,
+       time_t                  timeout,
+       ldap_back_send_t        sendok )
+{
+       a_metainfo_t    *mi = mc->mc_info;
+
+       const char      *save_text = rs->sr_text,
+                       *save_matched = rs->sr_matched;
+       BerVarray       save_ref = rs->sr_ref;
+       LDAPControl     **save_ctrls = rs->sr_ctrls;
+       void            *matched_ctx = NULL;
+
+       char            *matched = NULL;
+       char            *text = NULL;
+       char            **refs = NULL;
+       LDAPControl     **ctrls = NULL;
+
+       assert( mc != NULL );
+
+       rs->sr_text = NULL;
+       rs->sr_matched = NULL;
+       rs->sr_ref = NULL;
+       rs->sr_ctrls = NULL;
+
+       if ( candidate != META_TARGET_NONE ) {
+               a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+               a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+
+               if ( LDAP_ERR_OK( rs->sr_err ) ) {
+                       int             rc;
+                       struct timeval  tv;
+                       LDAPMessage     *res = NULL;
+                       time_t          stoptime = (time_t)(-1);
+                       int             timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+                                               LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+                       const char      *timeout_text = "Operation timed out";
+
+                       /* if timeout is not specified, compute and use
+                        * the one specific to the ongoing operation */
+                       if ( timeout == (time_t)(-1) ) {
+                               slap_op_t       opidx = slap_req2op( op->o_tag );
+
+                               if ( opidx == SLAP_OP_SEARCH ) {
+                                       if ( op->ors_tlimit <= 0 ) {
+                                               timeout = 0;
+
+                                       } else {
+                                               timeout = op->ors_tlimit;
+                                               timeout_err = LDAP_TIMELIMIT_EXCEEDED;
+                                               timeout_text = NULL;
+                                       }
+
+                               } else {
+                                       timeout = mt->mt_timeout[ opidx ];
+                               }
+                       }
+
+                       /* better than nothing :) */
+                       if ( timeout == 0 ) {
+                               if ( mi->mi_idle_timeout ) {
+                                       timeout = mi->mi_idle_timeout;
+
+                               }
+                       }
+
+                       if ( timeout ) {
+                               stoptime = op->o_time + timeout;
+                       }
+
+                       LDAP_BACK_TV_SET( &tv );
+
+retry:;
+                       rc = ldap_result( msc->msc_ld, msgid, LDAP_MSG_ALL, &tv, &res );
+                       switch ( rc ) {
+                       case 0:
+                               if ( timeout && slap_get_time() > stoptime ) {
+                                       (void)asyncmeta_back_cancel( mc, op, msgid, candidate );
+                                       rs->sr_err = timeout_err;
+                                       rs->sr_text = timeout_text;
+                                       break;
+                               }
+
+                               LDAP_BACK_TV_SET( &tv );
+                               ldap_pvt_thread_yield();
+                               goto retry;
+
+                       case -1:
+                               ldap_get_option( msc->msc_ld, LDAP_OPT_RESULT_CODE,
+                                               &rs->sr_err );
+                               break;
+
+
+                       /* otherwise get the result; if it is not
+                        * LDAP_SUCCESS, record it in the reply
+                        * structure (this includes
+                        * LDAP_COMPARE_{TRUE|FALSE}) */
+                       default:
+                               /* only touch when activity actually took place... */
+                               if ( mi->mi_idle_timeout != 0 && msc->msc_time < op->o_time ) {
+                                       msc->msc_time = op->o_time;
+                               }
+
+                               rc = ldap_parse_result( msc->msc_ld, res, &rs->sr_err,
+                                               &matched, &text, &refs, &ctrls, 1 );
+                               res = NULL;
+                               if ( rc == LDAP_SUCCESS ) {
+                                       rs->sr_text = text;
+                               } else {
+                                       rs->sr_err = rc;
+                               }
+                               rs->sr_err = slap_map_api2result( rs );
+
+                               /* RFC 4511: referrals can only appear
+                                * if result code is LDAP_REFERRAL */
+                               if ( refs != NULL
+                                       && refs[ 0 ] != NULL
+                                       && refs[ 0 ][ 0 ] != '\0' )
+                               {
+                                       if ( rs->sr_err != LDAP_REFERRAL ) {
+                                               Debug( LDAP_DEBUG_ANY,
+                                                       "%s asyncmeta_back_op_result[%d]: "
+                                                       "got referrals with err=%d\n",
+                                                       op->o_log_prefix,
+                                                       candidate, rs->sr_err );
+
+                                       } else {
+                                               int     i;
+
+                                               for ( i = 0; refs[ i ] != NULL; i++ )
+                                                       /* count */ ;
+                                               rs->sr_ref = op->o_tmpalloc( sizeof( struct berval ) * ( i + 1 ),
+                                                       op->o_tmpmemctx );
+                                               for ( i = 0; refs[ i ] != NULL; i++ ) {
+                                                       ber_str2bv( refs[ i ], 0, 0, &rs->sr_ref[ i ] );
+                                               }
+                                               BER_BVZERO( &rs->sr_ref[ i ] );
+                                       }
+
+                               } else if ( rs->sr_err == LDAP_REFERRAL ) {
+                                       Debug( LDAP_DEBUG_ANY,
+                                               "%s asyncmeta_back_op_result[%d]: "
+                                               "got err=%d with null "
+                                               "or empty referrals\n",
+                                               op->o_log_prefix,
+                                               candidate, rs->sr_err );
+
+                                       rs->sr_err = LDAP_NO_SUCH_OBJECT;
+                               }
+
+                               if ( ctrls != NULL ) {
+                                       rs->sr_ctrls = ctrls;
+                               }
+                       }
+
+                       assert( res == NULL );
+               }
+
+               /* if the error in the reply structure is not
+                * LDAP_SUCCESS, try to map it from client
+                * to server error */
+               if ( !LDAP_ERR_OK( rs->sr_err ) ) {
+                       rs->sr_err = slap_map_api2result( rs );
+
+                       /* internal ops ( op->o_conn == NULL )
+                        * must not reply to client */
+                       if ( op->o_conn && !op->o_do_not_cache && matched ) {
+
+                               /* record the (massaged) matched
+                                * DN into the reply structure */
+                               rs->sr_matched = matched;
+                       }
+               }
+
+               if ( META_BACK_TGT_QUARANTINE( mt ) ) {
+                       asyncmeta_quarantine( op, mi, rs, candidate );
+               }
+
+       } else {
+               int     i,
+                       err = rs->sr_err;
+
+               for ( i = 0; i < mi->mi_ntargets; i++ ) {
+                       a_metasingleconn_t      *msc = &mc->mc_conns[ i ];
+                       char                    *xtext = NULL;
+                       char                    *xmatched = NULL;
+
+                       if ( msc->msc_ld == NULL ) {
+                               continue;
+                       }
+
+                       rs->sr_err = LDAP_SUCCESS;
+
+                       ldap_get_option( msc->msc_ld, LDAP_OPT_RESULT_CODE, &rs->sr_err );
+                       if ( rs->sr_err != LDAP_SUCCESS ) {
+                               /*
+                                * better check the type of error. In some cases
+                                * (search ?) it might be better to return a
+                                * success if at least one of the targets gave
+                                * positive result ...
+                                */
+                               ldap_get_option( msc->msc_ld,
+                                               LDAP_OPT_DIAGNOSTIC_MESSAGE, &xtext );
+                               if ( xtext != NULL && xtext [ 0 ] == '\0' ) {
+                                       ldap_memfree( xtext );
+                                       xtext = NULL;
+                               }
+
+                               ldap_get_option( msc->msc_ld,
+                                               LDAP_OPT_MATCHED_DN, &xmatched );
+                               if ( xmatched != NULL && xmatched[ 0 ] == '\0' ) {
+                                       ldap_memfree( xmatched );
+                                       xmatched = NULL;
+                               }
+
+                               rs->sr_err = slap_map_api2result( rs );
+
+                               if ( LogTest( LDAP_DEBUG_ANY ) ) {
+                                       char    buf[ SLAP_TEXT_BUFLEN ];
+
+                                       snprintf( buf, sizeof( buf ),
+                                               "asyncmeta_back_op_result[%d] "
+                                               "err=%d text=\"%s\" matched=\"%s\"",
+                                               i, rs->sr_err,
+                                               ( xtext ? xtext : "" ),
+                                               ( xmatched ? xmatched : "" ) );
+                                       Debug( LDAP_DEBUG_ANY, "%s %s.\n",
+                                               op->o_log_prefix, buf, 0 );
+                               }
+
+                               /*
+                                * FIXME: need to rewrite "match" (need rwinfo)
+                                */
+                               switch ( rs->sr_err ) {
+                               default:
+                                       err = rs->sr_err;
+                                       if ( xtext != NULL ) {
+                                               if ( text ) {
+                                                       ldap_memfree( text );
+                                               }
+                                               text = xtext;
+                                               xtext = NULL;
+                                       }
+                                       if ( xmatched != NULL ) {
+                                               if ( matched ) {
+                                                       ldap_memfree( matched );
+                                               }
+                                               matched = xmatched;
+                                               xmatched = NULL;
+                                       }
+                                       break;
+                               }
+
+                               if ( xtext ) {
+                                       ldap_memfree( xtext );
+                               }
+
+                               if ( xmatched ) {
+                                       ldap_memfree( xmatched );
+                               }
+                       }
+
+                       if ( META_BACK_TGT_QUARANTINE( mi->mi_targets[ i ] ) ) {
+                               asyncmeta_quarantine( op, mi, rs, i );
+                       }
+               }
+
+               if ( err != LDAP_SUCCESS ) {
+                       rs->sr_err = err;
+               }
+       }
+
+       if ( matched != NULL ) {
+               struct berval   dn, pdn;
+
+               ber_str2bv( matched, 0, 0, &dn );
+               if ( dnPretty( NULL, &dn, &pdn, op->o_tmpmemctx ) == LDAP_SUCCESS ) {
+                       ldap_memfree( matched );
+                       matched_ctx = op->o_tmpmemctx;
+                       matched = pdn.bv_val;
+               }
+               rs->sr_matched = matched;
+       }
+
+       if ( rs->sr_err == LDAP_UNAVAILABLE ) {
+               if ( !( sendok & LDAP_BACK_RETRYING ) ) {
+                       if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) {
+                               if ( rs->sr_text == NULL ) rs->sr_text = "Proxy operation retry failed";
+                               send_ldap_result( op, rs );
+                       }
+               }
+
+       } else if ( op->o_conn &&
+               ( ( ( sendok & LDAP_BACK_SENDOK ) && LDAP_ERR_OK( rs->sr_err ) )
+                       || ( ( sendok & LDAP_BACK_SENDERR ) && !LDAP_ERR_OK( rs->sr_err ) ) ) )
+       {
+               send_ldap_result( op, rs );
+       }
+       if ( matched ) {
+               op->o_tmpfree( (char *)rs->sr_matched, matched_ctx );
+       }
+       if ( text ) {
+               ldap_memfree( text );
+       }
+       if ( rs->sr_ref ) {
+               op->o_tmpfree( rs->sr_ref, op->o_tmpmemctx );
+               rs->sr_ref = NULL;
+       }
+       if ( refs ) {
+               ber_memvfree( (void **)refs );
+       }
+       if ( ctrls ) {
+               assert( rs->sr_ctrls != NULL );
+               ldap_controls_free( ctrls );
+       }
+
+       rs->sr_text = save_text;
+       rs->sr_matched = save_matched;
+       rs->sr_ref = save_ref;
+       rs->sr_ctrls = save_ctrls;
+
+       return( LDAP_ERR_OK( rs->sr_err ) ? LDAP_SUCCESS : rs->sr_err );
+}
+
+/*
+ * meta_back_proxy_authz_cred()
+ *
+ * prepares credentials & method for meta_back_proxy_authz_bind();
+ * or, if method is SASL, performs the SASL bind directly.
+ */
+int
+asyncmeta_back_proxy_authz_cred(
+       a_metaconn_t            *mc,
+       int                     candidate,
+       Operation               *op,
+       SlapReply               *rs,
+       ldap_back_send_t        sendok,
+       struct berval           *binddn,
+       struct berval           *bindcred,
+       int                     *method )
+{
+       a_metainfo_t            *mi = mc->mc_info;
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+       struct berval           ndn;
+       int                     dobind = 0;
+
+       /* don't proxyAuthz if protocol is not LDAPv3 */
+       switch ( mt->mt_version ) {
+       case LDAP_VERSION3:
+               break;
+
+       case 0:
+               if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) {
+                       break;
+               }
+               /* fall thru */
+
+       default:
+               rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+               if ( sendok & LDAP_BACK_SENDERR ) {
+                       send_ldap_result( op, rs );
+               }
+               LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+               goto done;
+       }
+
+       if ( op->o_tag == LDAP_REQ_BIND ) {
+               ndn = op->o_req_ndn;
+
+       } else if ( !BER_BVISNULL( &op->o_conn->c_ndn ) ) {
+               ndn = op->o_conn->c_ndn;
+
+       } else {
+               ndn = op->o_ndn;
+       }
+       rs->sr_err = LDAP_SUCCESS;
+
+       /*
+        * FIXME: we need to let clients use proxyAuthz
+        * otherwise we cannot do symmetric pools of servers;
+        * we have to live with the fact that a user can
+        * authorize itself as any ID that is allowed
+        * by the authzTo directive of the "proxyauthzdn".
+        */
+       /*
+        * NOTE: current Proxy Authorization specification
+        * and implementation do not allow proxy authorization
+        * control to be provided with Bind requests
+        */
+       /*
+        * if no bind took place yet, but the connection is bound
+        * and the "proxyauthzdn" is set, then bind as
+        * "proxyauthzdn" and explicitly add the proxyAuthz
+        * control to every operation with the dn bound
+        * to the connection as control value.
+        */
+
+       /* bind as proxyauthzdn only if no idassert mode
+        * is requested, or if the client's identity
+        * is authorized */
+       switch ( mt->mt_idassert_mode ) {
+       case LDAP_BACK_IDASSERT_LEGACY:
+               if ( !BER_BVISNULL( &ndn ) && !BER_BVISEMPTY( &ndn ) ) {
+                       if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) && !BER_BVISEMPTY( &mt->mt_idassert_authcDN ) )
+                       {
+                               *binddn = mt->mt_idassert_authcDN;
+                               *bindcred = mt->mt_idassert_passwd;
+                               dobind = 1;
+                       }
+               }
+               break;
+
+       default:
+               /* NOTE: rootdn can always idassert */
+               if ( BER_BVISNULL( &ndn )
+                       && mt->mt_idassert_authz == NULL
+                       && !( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) )
+               {
+                       if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+                               rs->sr_err = LDAP_INAPPROPRIATE_AUTH;
+                               if ( sendok & LDAP_BACK_SENDERR ) {
+                                       send_ldap_result( op, rs );
+                               }
+                               LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+                               goto done;
+
+                       }
+
+                       rs->sr_err = LDAP_SUCCESS;
+                       *binddn = slap_empty_bv;
+                       *bindcred = slap_empty_bv;
+                       break;
+
+               } else if ( mt->mt_idassert_authz && !be_isroot( op ) ) {
+                       struct berval authcDN;
+
+                       if ( BER_BVISNULL( &ndn ) ) {
+                               authcDN = slap_empty_bv;
+
+                       } else {
+                               authcDN = ndn;
+                       }
+                       rs->sr_err = slap_sasl_matches( op, mt->mt_idassert_authz,
+                                       &authcDN, &authcDN );
+                       if ( rs->sr_err != LDAP_SUCCESS ) {
+                               if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+                                       if ( sendok & LDAP_BACK_SENDERR ) {
+                                               send_ldap_result( op, rs );
+                                       }
+                                       LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+                                       goto done;
+                               }
+
+                               rs->sr_err = LDAP_SUCCESS;
+                               *binddn = slap_empty_bv;
+                               *bindcred = slap_empty_bv;
+                               break;
+                       }
+               }
+
+               *binddn = mt->mt_idassert_authcDN;
+               *bindcred = mt->mt_idassert_passwd;
+               dobind = 1;
+               break;
+       }
+
+       if ( dobind && mt->mt_idassert_authmethod == LDAP_AUTH_SASL ) {
+#ifdef HAVE_CYRUS_SASL
+               void            *defaults = NULL;
+               struct berval   authzID = BER_BVNULL;
+               int             freeauthz = 0;
+
+               /* if SASL supports native authz, prepare for it */
+               if ( ( !op->o_do_not_cache || !op->o_is_auth_check ) &&
+                               ( mt->mt_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) )
+               {
+                       switch ( mt->mt_idassert_mode ) {
+                       case LDAP_BACK_IDASSERT_OTHERID:
+                       case LDAP_BACK_IDASSERT_OTHERDN:
+                               authzID = mt->mt_idassert_authzID;
+                               break;
+
+                       case LDAP_BACK_IDASSERT_ANONYMOUS:
+                               BER_BVSTR( &authzID, "dn:" );
+                               break;
+
+                       case LDAP_BACK_IDASSERT_SELF:
+                               if ( BER_BVISNULL( &ndn ) ) {
+                                       /* connection is not authc'd, so don't idassert */
+                                       BER_BVSTR( &authzID, "dn:" );
+                                       break;
+                               }
+                               authzID.bv_len = STRLENOF( "dn:" ) + ndn.bv_len;
+                               authzID.bv_val = slap_sl_malloc( authzID.bv_len + 1, op->o_tmpmemctx );
+                               AC_MEMCPY( authzID.bv_val, "dn:", STRLENOF( "dn:" ) );
+                               AC_MEMCPY( authzID.bv_val + STRLENOF( "dn:" ),
+                                               ndn.bv_val, ndn.bv_len + 1 );
+                               freeauthz = 1;
+                               break;
+
+                       default:
+                               break;
+                       }
+               }
+
+               if ( mt->mt_idassert_secprops != NULL ) {
+                       rs->sr_err = ldap_set_option( msc->msc_ld,
+                               LDAP_OPT_X_SASL_SECPROPS,
+                               (void *)mt->mt_idassert_secprops );
+
+                       if ( rs->sr_err != LDAP_OPT_SUCCESS ) {
+                               rs->sr_err = LDAP_OTHER;
+                               if ( sendok & LDAP_BACK_SENDERR ) {
+                                       send_ldap_result( op, rs );
+                               }
+                               LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+                               goto done;
+                       }
+               }
+
+               defaults = lutil_sasl_defaults( msc->msc_ld,
+                               mt->mt_idassert_sasl_mech.bv_val,
+                               mt->mt_idassert_sasl_realm.bv_val,
+                               mt->mt_idassert_authcID.bv_val,
+                               mt->mt_idassert_passwd.bv_val,
+                               authzID.bv_val );
+               if ( defaults == NULL ) {
+                       rs->sr_err = LDAP_OTHER;
+                       LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+                       if ( sendok & LDAP_BACK_SENDERR ) {
+                               send_ldap_result( op, rs );
+                       }
+                       goto done;
+               }
+
+               rs->sr_err = ldap_sasl_interactive_bind_s( msc->msc_ld, binddn->bv_val,
+                               mt->mt_idassert_sasl_mech.bv_val, NULL, NULL,
+                               LDAP_SASL_QUIET, lutil_sasl_interact,
+                               defaults );
+
+               rs->sr_err = slap_map_api2result( rs );
+               if ( rs->sr_err != LDAP_SUCCESS ) {
+                       LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+                       if ( sendok & LDAP_BACK_SENDERR ) {
+                               send_ldap_result( op, rs );
+                       }
+
+               } else {
+                       LDAP_BACK_CONN_ISBOUND_SET( msc );
+               }
+
+               lutil_sasl_freedefs( defaults );
+               if ( freeauthz ) {
+                       slap_sl_free( authzID.bv_val, op->o_tmpmemctx );
+               }
+
+               goto done;
+#endif /* HAVE_CYRUS_SASL */
+       }
+
+       *method = mt->mt_idassert_authmethod;
+       switch ( mt->mt_idassert_authmethod ) {
+       case LDAP_AUTH_NONE:
+               BER_BVSTR( binddn, "" );
+               BER_BVSTR( bindcred, "" );
+               /* fallthru */
+
+       case LDAP_AUTH_SIMPLE:
+               break;
+
+       default:
+               /* unsupported! */
+               LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+               rs->sr_err = LDAP_AUTH_METHOD_NOT_SUPPORTED;
+               if ( sendok & LDAP_BACK_SENDERR ) {
+                       send_ldap_result( op, rs );
+               }
+               break;
+       }
+
+done:;
+
+       if ( !BER_BVISEMPTY( binddn ) ) {
+               LDAP_BACK_CONN_ISIDASSERT_SET( msc );
+       }
+
+       return rs->sr_err;
+}
+
+static int
+asyncmeta_proxy_authz_bind(
+       a_metaconn_t *mc,
+       int candidate,
+       Operation *op,
+       SlapReply *rs,
+       ldap_back_send_t sendok,
+       int dolock )
+{
+       a_metainfo_t            *mi = mc->mc_info;
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+       struct berval           binddn = BER_BVC( "" ),
+                               cred = BER_BVC( "" );
+       int                     method = LDAP_AUTH_NONE,
+                               rc;
+
+       rc = asyncmeta_back_proxy_authz_cred( mc, candidate, op, rs, sendok, &binddn, &cred, &method );
+       if ( rc == LDAP_SUCCESS && !LDAP_BACK_CONN_ISBOUND( msc ) ) {
+               int     msgid;
+
+               switch ( method ) {
+               case LDAP_AUTH_NONE:
+               case LDAP_AUTH_SIMPLE:
+                       for (;;) {
+                               rs->sr_err = ldap_sasl_bind( msc->msc_ld,
+                                       binddn.bv_val, LDAP_SASL_SIMPLE,
+                                       &cred, NULL, NULL, &msgid );
+                               if ( rs->sr_err != LDAP_X_CONNECTING ) {
+                                       break;
+                               }
+                               ldap_pvt_thread_yield();
+                       }
+
+                       rc = asyncmeta_bind_op_result( op, rs, mc, candidate, msgid, sendok, dolock );
+                       if ( rc == LDAP_SUCCESS ) {
+                               /* set rebind stuff in case of successful proxyAuthz bind,
+                                * so that referral chasing is attempted using the right
+                                * identity */
+                               LDAP_BACK_CONN_ISBOUND_SET( msc );
+                               ber_bvreplace( &msc->msc_bound_ndn, &binddn );
+
+                               if ( META_BACK_TGT_SAVECRED( mt ) ) {
+                                       if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+                                               memset( msc->msc_cred.bv_val, 0,
+                                                       msc->msc_cred.bv_len );
+                                       }
+                                       ber_bvreplace( &msc->msc_cred, &cred );
+                                       ldap_set_rebind_proc( msc->msc_ld, mt->mt_rebind_f, msc );
+                               }
+                       }
+                       break;
+
+               default:
+                       assert( 0 );
+                       break;
+               }
+       }
+
+       return LDAP_BACK_CONN_ISBOUND( msc );
+}
+
+/*
+ * Add controls;
+ *
+ * if any needs to be added, it is prepended to existing ones,
+ * in a newly allocated array.  The companion function
+ * mi->mi_ldap_extra->controls_free() must be used to restore the original
+ * status of op->o_ctrls.
+ */
+int
+asyncmeta_controls_add(
+               Operation       *op,
+               SlapReply       *rs,
+               a_metaconn_t    *mc,
+               int             candidate,
+               LDAPControl     ***pctrls )
+{
+       a_metainfo_t            *mi = mc->mc_info;
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+
+       LDAPControl             **ctrls = NULL;
+       /* set to the maximum number of controls this backend can add */
+       LDAPControl             c[ 2 ] = {{ 0 }};
+       int                     n = 0, i, j1 = 0, j2 = 0;
+
+       *pctrls = NULL;
+
+       rs->sr_err = LDAP_SUCCESS;
+
+       /* don't add controls if protocol is not LDAPv3 */
+       switch ( mt->mt_version ) {
+       case LDAP_VERSION3:
+               break;
+
+       case 0:
+               if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) {
+                       break;
+               }
+               /* fall thru */
+
+       default:
+               goto done;
+       }
+
+       /* put controls that go __before__ existing ones here */
+
+       /* proxyAuthz for identity assertion */
+       switch ( mi->mi_ldap_extra->proxy_authz_ctrl( op, rs, &msc->msc_bound_ndn,
+               mt->mt_version, &mt->mt_idassert, &c[ j1 ] ) )
+       {
+       case SLAP_CB_CONTINUE:
+               break;
+
+       case LDAP_SUCCESS:
+               j1++;
+               break;
+
+       default:
+               goto done;
+       }
+
+       /* put controls that go __after__ existing ones here */
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+       /* session tracking */
+       if ( META_BACK_TGT_ST_REQUEST( mt ) ) {
+               switch ( slap_ctrl_session_tracking_request_add( op, rs, &c[ j1 + j2 ] ) ) {
+               case SLAP_CB_CONTINUE:
+                       break;
+
+               case LDAP_SUCCESS:
+                       j2++;
+                       break;
+
+               default:
+                       goto done;
+               }
+       }
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+       if ( rs->sr_err == SLAP_CB_CONTINUE ) {
+               rs->sr_err = LDAP_SUCCESS;
+       }
+
+       /* if nothing to do, just bail out */
+       if ( j1 == 0 && j2 == 0 ) {
+               goto done;
+       }
+
+       assert( j1 + j2 <= (int) (sizeof( c )/sizeof( c[0] )) );
+
+       if ( op->o_ctrls ) {
+               for ( n = 0; op->o_ctrls[ n ]; n++ )
+                       /* just count ctrls */ ;
+       }
+
+       ctrls = op->o_tmpalloc( (n + j1 + j2 + 1) * sizeof( LDAPControl * ) + ( j1 + j2 ) * sizeof( LDAPControl ),
+                       op->o_tmpmemctx );
+       if ( j1 ) {
+               ctrls[ 0 ] = (LDAPControl *)&ctrls[ n + j1 + j2 + 1 ];
+               *ctrls[ 0 ] = c[ 0 ];
+               for ( i = 1; i < j1; i++ ) {
+                       ctrls[ i ] = &ctrls[ 0 ][ i ];
+                       *ctrls[ i ] = c[ i ];
+               }
+       }
+
+       i = 0;
+       if ( op->o_ctrls ) {
+               for ( i = 0; op->o_ctrls[ i ]; i++ ) {
+                       ctrls[ i + j1 ] = op->o_ctrls[ i ];
+               }
+       }
+
+       n += j1;
+       if ( j2 ) {
+               ctrls[ n ] = (LDAPControl *)&ctrls[ n + j2 + 1 ] + j1;
+               *ctrls[ n ] = c[ j1 ];
+               for ( i = 1; i < j2; i++ ) {
+                       ctrls[ n + i ] = &ctrls[ n ][ i ];
+                       *ctrls[ n + i ] = c[ i ];
+               }
+       }
+
+       ctrls[ n + j2 ] = NULL;
+
+done:;
+       if ( ctrls == NULL ) {
+               ctrls = op->o_ctrls;
+       }
+
+       *pctrls = ctrls;
+
+       return rs->sr_err;
+}
+
+#if 0
+/*
+ * Add controls;
+ *
+ * same as asyncmeta_controls_add, but creates a new controls array
+ * to be used by the operation copy
+ */
+int
+asyncmeta_controls_add_copy(
+               Operation       *op,
+               SlapReply       *rs,
+               a_metaconn_t    *mc,
+               int             candidate,
+               LDAPControl     ***pctrls )
+{
+       a_metainfo_t            *mi = (a_metainfo_t *)op->o_bd->be_private;
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+       a_metasingleconn_t      *msc = mc->mc_conns[ candidate ];
+
+       LDAPControl             **ctrls = NULL;
+       /* set to the maximum number of controls this backend can add */
+       LDAPControl             c[ 2 ] = {{ 0 }};
+       int                     n = 0, i, j1 = 0, j2 = 0;
+
+       *pctrls = NULL;
+
+       rs->sr_err = LDAP_SUCCESS;
+
+       /* don't add controls if protocol is not LDAPv3 */
+       switch ( mt->mt_version ) {
+       case LDAP_VERSION3:
+               break;
+
+       case 0:
+               if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) {
+                       break;
+               }
+               /* fall thru */
+
+       default:
+               goto done;
+       }
+
+       /* put controls that go __before__ existing ones here */
+
+       /* proxyAuthz for identity assertion */
+       switch ( mi->mi_ldap_extra->proxy_authz_ctrl( op, rs, &msc->msc_bound_ndn,
+               mt->mt_version, &mt->mt_idassert, &c[ j1 ] ) )
+       {
+       case SLAP_CB_CONTINUE:
+               break;
+
+       case LDAP_SUCCESS:
+               j1++;
+               break;
+
+       default:
+               goto done;
+       }
+
+       /* put controls that go __after__ existing ones here */
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+       /* session tracking */
+       if ( META_BACK_TGT_ST_REQUEST( mt ) ) {
+               switch ( slap_ctrl_session_tracking_request_add( op, rs, &c[ j1 + j2 ] ) ) {
+               case SLAP_CB_CONTINUE:
+                       break;
+
+               case LDAP_SUCCESS:
+                       j2++;
+                       break;
+
+               default:
+                       goto copy;
+               }
+       }
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+       if ( rs->sr_err == SLAP_CB_CONTINUE ) {
+               rs->sr_err = LDAP_SUCCESS;
+       }
+
+copy:
+       if ( op->o_ctrls ) {
+               for ( n = 0; op->o_ctrls[ n ]; n++ )
+                       /* just count ctrls */ ;
+       }
+
+       ctrls = ch_calloc( (n + j1 + j2 + 1) * sizeof( LDAPControl * ) + ( j1 + j2 ) * sizeof( LDAPControl ));
+       if ( j1 ) {
+               ctrls[ 0 ] = (LDAPControl *)&ctrls[ n + j1 + j2 + 1 ];
+               *ctrls[ 0 ] = c[ 0 ];
+               for ( i = 1; i < j1; i++ ) {
+                       ctrls[ i ] = &ctrls[ 0 ][ i ];
+                       *ctrls[ i ] = c[ i ];
+               }
+       }
+
+       i = 0;
+       if ( op->o_ctrls ) {
+               for ( i = 0; op->o_ctrls[ i ]; i++ ) {
+                       ctrls[ i + j1 ] = op->o_ctrls[ i ];
+               }
+       }
+
+       n += j1;
+       if ( j2 ) {
+               ctrls[ n ] = (LDAPControl *)&ctrls[ n + j2 + 1 ] + j1;
+               *ctrls[ n ] = c[ j1 ];
+               for ( i = 1; i < j2; i++ ) {
+                       ctrls[ n + i ] = &ctrls[ n ][ i ];
+                       *ctrls[ n + i ] = c[ i ];
+               }
+       }
+
+       ctrls[ n + j2 ] = NULL;
+
+done:;
+
+       op->o_ctrls = ctrls;
+
+       return rs->sr_err;
+}
+#endif
+
+/*
+ * asyncmeta_dobind_init()
+ *
+ * initiates bind for a candidate target
+ */
+meta_search_candidate_t
+asyncmeta_dobind_init(Operation *op, SlapReply *rs, bm_context_t *bc, a_metaconn_t *mc, int candidate)
+{
+       SlapReply               *candidates = bc->candidates;
+       a_metainfo_t            *mi = ( a_metainfo_t * )mc->mc_info;
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+       ber_socket_t s;
+       struct berval           binddn = msc->msc_bound_ndn,
+                               cred = msc->msc_cred;
+       int                     method;
+
+       int                     rc;
+       ber_int_t       msgid;
+
+       meta_search_candidate_t retcode;
+
+       Debug( LDAP_DEBUG_TRACE, "%s >>> asyncmeta_search_dobind_init[%d]\n",
+               op->o_log_prefix, candidate, 0 );
+
+       if ( mc->mc_authz_target == META_BOUND_ALL ) {
+               return META_SEARCH_CANDIDATE;
+       }
+
+       retcode = META_SEARCH_BINDING;
+       if ( LDAP_BACK_CONN_ISBOUND( msc ) || LDAP_BACK_CONN_ISANON( msc ) ) {
+               /* already bound (or anonymous) */
+
+#ifdef DEBUG_205
+               char    buf[ SLAP_TEXT_BUFLEN ] = { '\0' };
+               int     bound = 0;
+
+               if ( LDAP_BACK_CONN_ISBOUND( msc ) ) {
+                       bound = 1;
+               }
+
+               snprintf( buf, sizeof( buf ), " mc=%p ld=%p%s DN=\"%s\"",
+                       (void *)mc, (void *)msc->msc_ld,
+                       bound ? " bound" : " anonymous",
+                       bound == 0 ? "" : msc->msc_bound_ndn.bv_val );
+               Debug( LDAP_DEBUG_ANY, "### %s asyncmeta_search_dobind_init[%d]%s\n",
+                       op->o_log_prefix, candidate, buf );
+#endif /* DEBUG_205 */
+
+               retcode = META_SEARCH_CANDIDATE;
+
+       } else if ( META_BACK_CONN_CREATING( msc ) || LDAP_BACK_CONN_BINDING( msc ) ) {
+               /* another thread is binding the target for this conn; wait */
+
+#ifdef DEBUG_205
+               char    buf[ SLAP_TEXT_BUFLEN ] = { '\0' };
+
+               snprintf( buf, sizeof( buf ), " mc=%p ld=%p needbind",
+                       (void *)mc, (void *)msc->msc_ld );
+               Debug( LDAP_DEBUG_ANY, "### %s asyncmeta_search_dobind_init[%d]%s\n",
+                       op->o_log_prefix, candidate, buf );
+#endif /* DEBUG_205 */
+
+               candidates[ candidate ].sr_msgid = META_MSGID_NEED_BIND;
+               retcode = META_SEARCH_NEED_BIND;
+
+       } else {
+               /* we'll need to bind the target for this conn */
+
+#ifdef DEBUG_205
+               char buf[ SLAP_TEXT_BUFLEN ];
+
+               snprintf( buf, sizeof( buf ), " mc=%p ld=%p binding",
+                       (void *)mc, (void *)msc->msc_ld );
+               Debug( LDAP_DEBUG_ANY, "### %s asyncmeta_search_dobind_init[%d]%s\n",
+                       op->o_log_prefix, candidate, buf );
+#endif /* DEBUG_205 */
+
+               if ( msc->msc_ld == NULL ) {
+                       /* for some reason (e.g. because formerly in "binding"
+                        * state, with eventual connection expiration or invalidation)
+                        * it was not initialized as expected */
+
+                       Debug( LDAP_DEBUG_ANY, "%s asyncmeta_search_dobind_init[%d] mc=%p ld=NULL\n",
+                               op->o_log_prefix, candidate, (void *)mc );
+
+                       rc = asyncmeta_init_one_conn( op, rs, mc, candidate,
+                               LDAP_BACK_CONN_ISPRIV( mc ), LDAP_BACK_DONTSEND, 0 );
+                       switch ( rc ) {
+                       case LDAP_SUCCESS:
+                               assert( msc->msc_ld != NULL );
+                               break;
+
+                       case LDAP_SERVER_DOWN:
+                       case LDAP_UNAVAILABLE:
+                               goto down;
+
+                       default:
+                               goto other;
+                       }
+               }
+
+               LDAP_BACK_CONN_BINDING_SET( msc );
+       }
+
+       if ( retcode != META_SEARCH_BINDING ) {
+               return retcode;
+       }
+
+       if ( op->o_conn != NULL &&
+               !op->o_do_not_cache &&
+               ( BER_BVISNULL( &msc->msc_bound_ndn ) ||
+                       BER_BVISEMPTY( &msc->msc_bound_ndn ) ||
+                       ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) ) )
+       {
+               rc = asyncmeta_back_proxy_authz_cred( mc, candidate, op, rs, LDAP_BACK_DONTSEND, &binddn, &cred, &method );
+               switch ( rc ) {
+               case LDAP_SUCCESS:
+                       break;
+               case LDAP_UNAVAILABLE:
+                       goto down;
+               default:
+                       goto other;
+               }
+
+               /* NOTE: we copy things here, even if bind didn't succeed yet,
+                * because the connection is not shared until bind is over */
+               if ( !BER_BVISNULL( &binddn ) ) {
+                       ldap_pvt_thread_mutex_lock(&mc->mc_om_mutex);
+
+                       ber_bvreplace( &msc->msc_bound_ndn, &binddn );
+                       if ( META_BACK_TGT_SAVECRED( mt ) && !BER_BVISNULL( &cred ) ) {
+                               if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+                                       memset( msc->msc_cred.bv_val, 0,
+                                               msc->msc_cred.bv_len );
+                               }
+                               ber_bvreplace( &msc->msc_cred, &cred );
+                       }
+                       ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+               }
+               if ( LDAP_BACK_CONN_ISBOUND( msc ) ) {
+                       /* apparently, idassert was configured with SASL bind,
+                        * so bind occurred inside meta_back_proxy_authz_cred() */
+                       LDAP_BACK_CONN_BINDING_CLEAR( msc );
+                       return META_SEARCH_CANDIDATE;
+               }
+
+               /* paranoid */
+               switch ( method ) {
+               case LDAP_AUTH_NONE:
+               case LDAP_AUTH_SIMPLE:
+                       /* do a simple bind with binddn, cred */
+                       break;
+
+               default:
+                       assert( 0 );
+                       break;
+               }
+       }
+
+       assert( msc->msc_ld != NULL );
+
+       if ( !BER_BVISEMPTY( &binddn ) && BER_BVISEMPTY( &cred ) ) {
+               /* bind anonymously? */
+               Debug( LDAP_DEBUG_ANY, "%s asyncmeta_search_dobind_init[%d] mc=%p: "
+                       "non-empty dn with empty cred; binding anonymously\n",
+                       op->o_log_prefix, candidate, (void *)mc );
+               cred = slap_empty_bv;
+
+       } else if ( BER_BVISEMPTY( &binddn ) && !BER_BVISEMPTY( &cred ) ) {
+               /* error */
+               Debug( LDAP_DEBUG_ANY, "%s asyncmeta_search_dobind_init[%d] mc=%p: "
+                       "empty dn with non-empty cred: error\n",
+                       op->o_log_prefix, candidate, (void *)mc );
+               rc = LDAP_OTHER;
+               goto other;
+       }
+retry_bind:
+       rc = ldap_sasl_bind( msc->msc_ld, binddn.bv_val, LDAP_SASL_SIMPLE, &cred,
+                       NULL, NULL, &msgid );
+       ldap_get_option( msc->msc_ld, LDAP_OPT_RESULT_CODE, &rc );
+
+       if (rc == LDAP_SERVER_DOWN ) {
+               goto down;
+       }
+       candidates[ candidate ].sr_msgid = msgid;
+       asyncmeta_set_msc_time(msc);
+#ifdef DEBUG_205
+       {
+               char buf[ SLAP_TEXT_BUFLEN ];
+
+               snprintf( buf, sizeof( buf ), "asyncmeta_search_dobind_init[%d] mc=%p ld=%p rc=%d",
+                       candidate, (void *)mc, (void *)mc->mc_conns[ candidate ].msc_ld, rc );
+               Debug( LDAP_DEBUG_ANY, "### %s %s\n",
+                       op->o_log_prefix, buf, 0 );
+       }
+#endif /* DEBUG_205 */
+
+       switch ( rc ) {
+       case LDAP_SUCCESS:
+               assert( msgid >= 0 );
+               META_BINDING_SET( &candidates[ candidate ] );
+               rs->sr_err = LDAP_SUCCESS;
+               return META_SEARCH_BINDING;
+
+       case LDAP_X_CONNECTING:
+               /* must retry, same conn */
+               candidates[ candidate ].sr_msgid = META_MSGID_CONNECTING;
+               LDAP_BACK_CONN_BINDING_CLEAR( msc );
+               goto retry_bind;
+
+       case LDAP_SERVER_DOWN:
+down:;
+               retcode = META_SEARCH_ERR;
+               rs->sr_err = LDAP_UNAVAILABLE;
+               candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+               break;
+
+               /* fall thru */
+
+       default:
+other:;
+               rs->sr_err = rc;
+               rc = slap_map_api2result( rs );
+               candidates[ candidate ].sr_err = rc;
+               if ( META_BACK_ONERR_STOP( mi ) ) {
+                       retcode = META_SEARCH_ERR;
+
+               } else {
+                       retcode = META_SEARCH_NOT_CANDIDATE;
+               }
+               candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+               break;
+       }
+
+       return retcode;
+}
+
+
+
+
+meta_search_candidate_t
+asyncmeta_dobind_init_with_retry(Operation *op, SlapReply *rs, bm_context_t *bc, a_metaconn_t *mc, int candidate)
+{
+
+       int rc, retries = 1;
+       a_metasingleconn_t *msc = &mc->mc_conns[candidate];
+       a_metainfo_t            *mi = mc->mc_info;
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+       SlapReply               *candidates = bc->candidates;
+
+retry_dobind:
+       rc = asyncmeta_dobind_init(op, rs, bc, mc, candidate);
+       if (rs->sr_err != LDAP_UNAVAILABLE) {
+               return rc;
+       } else if (retries <= 0) {
+               ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+               if (mc->mc_active < 1) {
+                       asyncmeta_clear_one_msc(NULL, mc, candidate);
+               }
+               ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+               return rc;
+       }
+       /* need to retry */
+       retries--;
+       if ( LogTest( LDAP_DEBUG_ANY ) ) {
+               char    buf[ SLAP_TEXT_BUFLEN ];
+
+               /* this lock is required; however,
+                * it's invoked only when logging is on */
+               ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex );
+               snprintf( buf, sizeof( buf ),
+                         "retrying URI=\"%s\" DN=\"%s\"",
+                         mt->mt_uri,
+                         BER_BVISNULL( &msc->msc_bound_ndn ) ?
+                         "" : msc->msc_bound_ndn.bv_val );
+               ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex );
+
+               Debug( LDAP_DEBUG_ANY,
+                      "%s asyncmeta_search_dobind_init_with_retry[%d]: %s.\n",
+                      op->o_log_prefix, candidate, buf );
+       }
+
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+       if (mc->mc_active < 1) {
+               asyncmeta_clear_one_msc(NULL, mc, candidate);
+       }
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+
+       ( void )rewrite_session_delete( mt->mt_rwmap.rwm_rw, op->o_conn );
+
+       rc = asyncmeta_init_one_conn( op, rs, mc, candidate,
+                                     LDAP_BACK_CONN_ISPRIV( mc ), LDAP_BACK_DONTSEND, 0 );
+
+       if (rs->sr_err != LDAP_SUCCESS) {
+               ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+               if (mc->mc_active < 1) {
+                       asyncmeta_clear_one_msc(NULL, mc, candidate);
+               }
+               ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+               return META_SEARCH_ERR;
+       }
+
+       goto retry_dobind;
+       return rc;
+}
diff --git a/servers/slapd/back-asyncmeta/candidates.c b/servers/slapd/back-asyncmeta/candidates.c
new file mode 100644 (file)
index 0000000..39a933b
--- /dev/null
@@ -0,0 +1,289 @@
+/* candidates.c - candidate targets selection and processing for
+ * back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
++ * This work was developed by Symas Corporation
++ * based on back-meta module for inclusion in OpenLDAP Software.
++ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include "ac/string.h"
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+/*
+ * The meta-directory has one suffix, called <suffix>.
+ * It handles a pool of target servers, each with a branch suffix
+ * of the form <branch X>,<suffix>, where <branch X> may be empty.
+ *
+ * When the meta-directory receives a request with a request DN that belongs
+ * to a branch, the corresponding target is invoked. When the request DN
+ * does not belong to a specific branch, all the targets that
+ * are compatible with the request DN are selected as candidates, and
+ * the request is spawned to all the candidate targets
+ *
+ * A request is characterized by a request DN. The following cases are
+ * handled:
+ *     - the request DN is the suffix: <dn> == <suffix>,
+ *             all the targets are candidates (search ...)
+ *     - the request DN is a branch suffix: <dn> == <branch X>,<suffix>, or
+ *     - the request DN is a subtree of a branch suffix:
+ *             <dn> == <rdn>,<branch X>,<suffix>,
+ *             the target is the only candidate.
+ *
+ * A possible extension will include the handling of multiple suffixes
+ */
+
+static a_metasubtree_t *
+asyncmeta_subtree_match( a_metatarget_t *mt, struct berval *ndn, int scope )
+{
+       a_metasubtree_t *ms = mt->mt_subtree;
+
+       for ( ms = mt->mt_subtree; ms; ms = ms->ms_next ) {
+               switch ( ms->ms_type ) {
+               case META_ST_SUBTREE:
+                       if ( dnIsSuffix( ndn, &ms->ms_dn ) ) {
+                               return ms;
+                       }
+                       break;
+
+               case META_ST_SUBORDINATE:
+                       if ( dnIsSuffix( ndn, &ms->ms_dn ) &&
+                               ( ndn->bv_len > ms->ms_dn.bv_len || scope != LDAP_SCOPE_BASE ) )
+                       {
+                               return ms;
+                       }
+                       break;
+
+               case META_ST_REGEX:
+                       /* NOTE: cannot handle scope */
+                       if ( regexec( &ms->ms_regex, ndn->bv_val, 0, NULL, 0 ) == 0 ) {
+                               return ms;
+                       }
+                       break;
+               }
+       }
+
+       return NULL;
+}
+
+/*
+ * returns 1 if suffix is candidate for dn, otherwise 0
+ *
+ * Note: this function should never be called if dn is the <suffix>.
+ */
+int
+asyncmeta_is_candidate(
+       a_metatarget_t  *mt,
+       struct berval   *ndn,
+       int             scope )
+{
+       struct berval rdn;
+       int d = ndn->bv_len - mt->mt_nsuffix.bv_len;
+
+       if ( d >= 0 ) {
+               if ( !dnIsSuffix( ndn, &mt->mt_nsuffix ) ) {
+                       return META_NOT_CANDIDATE;
+               }
+
+               /*
+                * |  match  | exclude |
+                * +---------+---------+-------------------+
+                * |    T    |    T    | not candidate     |
+                * |    F    |    T    | continue checking |
+                * +---------+---------+-------------------+
+                * |    T    |    F    | candidate         |
+                * |    F    |    F    | not candidate     |
+                * +---------+---------+-------------------+
+                */
+
+               if ( mt->mt_subtree ) {
+                       int match = ( asyncmeta_subtree_match( mt, ndn, scope ) != NULL );
+
+                       if ( !mt->mt_subtree_exclude ) {
+                               return match ? META_CANDIDATE : META_NOT_CANDIDATE;
+                       }
+
+                       if ( match /* && mt->mt_subtree_exclude */ ) {
+                               return META_NOT_CANDIDATE;
+                       }
+               }
+
+               switch ( mt->mt_scope ) {
+               case LDAP_SCOPE_SUBTREE:
+               default:
+                       return META_CANDIDATE;
+
+               case LDAP_SCOPE_SUBORDINATE:
+                       if ( d > 0 ) {
+                               return META_CANDIDATE;
+                       }
+                       break;
+
+               /* nearly useless; not allowed by config */
+               case LDAP_SCOPE_ONELEVEL:
+                       if ( d > 0 ) {
+                               rdn.bv_val = ndn->bv_val;
+                               rdn.bv_len = (ber_len_t)d - STRLENOF( "," );
+                               if ( dnIsOneLevelRDN( &rdn ) ) {
+                                       return META_CANDIDATE;
+                               }
+                       }
+                       break;
+
+               /* nearly useless; not allowed by config */
+               case LDAP_SCOPE_BASE:
+                       if ( d == 0 ) {
+                               return META_CANDIDATE;
+                       }
+                       break;
+               }
+
+       } else /* if ( d < 0 ) */ {
+               if ( !dnIsSuffix( &mt->mt_nsuffix, ndn ) ) {
+                       return META_NOT_CANDIDATE;
+               }
+
+               switch ( scope ) {
+               case LDAP_SCOPE_SUBTREE:
+               case LDAP_SCOPE_SUBORDINATE:
+                       /*
+                        * suffix longer than dn, but common part matches
+                        */
+                       return META_CANDIDATE;
+
+               case LDAP_SCOPE_ONELEVEL:
+                       rdn.bv_val = mt->mt_nsuffix.bv_val;
+                       rdn.bv_len = (ber_len_t)(-d) - STRLENOF( "," );
+                       if ( dnIsOneLevelRDN( &rdn ) ) {
+                               return META_CANDIDATE;
+                       }
+                       break;
+               }
+       }
+
+       return META_NOT_CANDIDATE;
+}
+
+/*
+ * meta_back_select_unique_candidate
+ *
+ * returns the index of the candidate in case it is unique, otherwise
+ * META_TARGET_NONE if none matches, or
+ * META_TARGET_MULTIPLE if more than one matches
+ * Note: ndn MUST be normalized.
+ */
+int
+asyncmeta_select_unique_candidate(
+       a_metainfo_t    *mi,
+       struct berval   *ndn )
+{
+       int     i, candidate = META_TARGET_NONE;
+
+       for ( i = 0; i < mi->mi_ntargets; i++ ) {
+               a_metatarget_t  *mt = mi->mi_targets[ i ];
+
+               if ( asyncmeta_is_candidate( mt, ndn, LDAP_SCOPE_BASE ) ) {
+                       if ( candidate == META_TARGET_NONE ) {
+                               candidate = i;
+
+                       } else {
+                               return META_TARGET_MULTIPLE;
+                       }
+               }
+       }
+
+       return candidate;
+}
+
+/*
+ * asyncmeta_clear_unused_candidates
+ *
+ * clears all candidates except candidate
+ */
+int
+asyncmeta_clear_unused_candidates(
+       Operation       *op,
+       int             candidate,
+       a_metaconn_t    *mc,
+       SlapReply       *candidates)
+{
+       a_metainfo_t    *mi = mc->mc_info;
+       int             i;
+
+       for ( i = 0; i < mi->mi_ntargets; ++i ) {
+               if ( i == candidate ) {
+                       continue;
+               }
+               META_CANDIDATE_RESET( &candidates[ i ] );
+       }
+
+       return 0;
+}
+
+int
+asyncmeta_clear_one_msc(
+       Operation       *op,
+       a_metaconn_t    *mc,
+       int             candidate )
+{
+       a_metasingleconn_t *msc;
+       if (mc == NULL) {
+               return 0;
+       }
+       msc = &mc->mc_conns[candidate];
+       if (msc->conn) {
+               connection_client_stop( msc->conn );
+               msc->conn = NULL;
+       }
+       if ( msc->msc_ld != NULL ) {
+
+#ifdef DEBUG_205
+               char    buf[ BUFSIZ ];
+
+               snprintf( buf, sizeof( buf ), "asyncmeta_clear_one_msc ldap_unbind_ext[%d] ld=%p",
+                       candidate, (void *)msc->msc_ld );
+               Debug( LDAP_DEBUG_ANY, "### %s %s\n",
+                      op ? op->o_log_prefix : "", buf, 0 );
+#endif /* DEBUG_205 */
+
+               ldap_unbind_ext( msc->msc_ld, NULL, NULL );
+               msc->msc_ld = NULL;
+               ldap_ld_free( msc->msc_ldr, 0, NULL, NULL );
+               msc->msc_ldr = NULL;
+       }
+
+       if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+               ber_memfree_x( msc->msc_bound_ndn.bv_val, NULL );
+               BER_BVZERO( &msc->msc_bound_ndn );
+       }
+
+       if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+               memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len );
+               ber_memfree_x( msc->msc_cred.bv_val, NULL );
+               BER_BVZERO( &msc->msc_cred );
+       }
+       msc->msc_time = 0;
+       msc->msc_mscflags = 0;
+       msc->msc_timeout_ops = 0;
+       return 0;
+}
diff --git a/servers/slapd/back-asyncmeta/compare.c b/servers/slapd/back-asyncmeta/compare.c
new file mode 100644 (file)
index 0000000..ee091f6
--- /dev/null
@@ -0,0 +1,293 @@
+/* compare.c - compare exop handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
++ * This work was developed by Symas Corporation
++ * based on back-meta module for inclusion in OpenLDAP Software.
++ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+
+meta_search_candidate_t
+asyncmeta_back_compare_start(Operation *op,
+                           SlapReply *rs,
+                           a_metaconn_t *mc,
+                           bm_context_t *bc,
+                           int candidate)
+{
+       a_dncookie      dc;
+       a_metainfo_t    *mi = mc->mc_info;
+       a_metatarget_t  *mt = mi->mi_targets[ candidate ];
+       struct berval   mdn = BER_BVNULL;
+       struct berval   mapped_attr = op->orc_ava->aa_desc->ad_cname;
+       struct berval   mapped_value = op->orc_ava->aa_value;
+       int rc = 0, nretries = 1;
+       LDAPControl     **ctrls = NULL;
+       meta_search_candidate_t retcode = META_SEARCH_CANDIDATE;
+       BerElement *ber = NULL;
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+       SlapReply               *candidates = bc->candidates;
+       ber_int_t       msgid;
+
+       dc.target = mt;
+       dc.conn = op->o_conn;
+       dc.rs = rs;
+       dc.ctx = "compareDN";
+
+       switch ( asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn ) ) {
+       case LDAP_UNWILLING_TO_PERFORM:
+               rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+               retcode = META_SEARCH_ERR;
+               goto doreturn;
+       default:
+               break;
+       }
+
+       /*
+        * if attr is objectClass, try to remap the value
+        */
+       if ( op->orc_ava->aa_desc == slap_schema.si_ad_objectClass ) {
+               asyncmeta_map( &mt->mt_rwmap.rwm_oc,
+                               &op->orc_ava->aa_value,
+                               &mapped_value, BACKLDAP_MAP );
+
+               if ( BER_BVISNULL( &mapped_value ) || BER_BVISEMPTY( &mapped_value ) ) {
+                       rs->sr_err = LDAP_OTHER;
+                       retcode = META_SEARCH_ERR;
+                       goto done;
+               }
+
+       /*
+        * else try to remap the attribute
+        */
+       } else {
+               asyncmeta_map( &mt->mt_rwmap.rwm_at,
+                       &op->orc_ava->aa_desc->ad_cname,
+                       &mapped_attr, BACKLDAP_MAP );
+               if ( BER_BVISNULL( &mapped_attr ) || BER_BVISEMPTY( &mapped_attr ) ) {
+                       rs->sr_err = LDAP_OTHER;
+                       retcode = META_SEARCH_ERR;
+                       goto done;
+               }
+
+               if ( op->orc_ava->aa_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName )
+               {
+                       dc.ctx = "compareAttrDN";
+
+                       switch ( asyncmeta_dn_massage( &dc, &op->orc_ava->aa_value, &mapped_value ) )
+                       {
+                       case LDAP_UNWILLING_TO_PERFORM:
+                               rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+                               retcode = META_SEARCH_ERR;
+                               goto done;
+
+                       default:
+                               break;
+                       }
+               }
+       }
+retry:;
+       ctrls = op->o_ctrls;
+       if ( asyncmeta_controls_add( op, rs, mc, candidate, &ctrls ) != LDAP_SUCCESS )
+       {
+               candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+               retcode = META_SEARCH_ERR;
+               goto done;
+       }
+
+       ber = ldap_build_compare_req( msc->msc_ld, mdn.bv_val, mapped_attr.bv_val, &mapped_value,
+                       ctrls, NULL, &msgid);
+       if (ber) {
+               candidates[ candidate ].sr_msgid = msgid;
+               rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_COMPARE,
+                                               mdn.bv_val, ber, msgid );
+               if (rc == msgid)
+                       rc = LDAP_SUCCESS;
+               else
+                       rc = LDAP_SERVER_DOWN;
+
+               switch ( rc ) {
+               case LDAP_SUCCESS:
+                       retcode = META_SEARCH_CANDIDATE;
+                       asyncmeta_set_msc_time(msc);
+                       break;
+
+               case LDAP_SERVER_DOWN:
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_clear_one_msc(NULL, mc, candidate);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       if ( nretries && asyncmeta_retry( op, rs, &mc, candidate, LDAP_BACK_DONTSEND ) ) {
+                               nretries = 0;
+                               /* if the identity changed, there might be need to re-authz */
+                               (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+                               goto retry;
+                       }
+
+               default:
+                       candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+                       retcode = META_SEARCH_ERR;
+               }
+       }
+done:
+       (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+       if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+               free( mdn.bv_val );
+       }
+
+       if ( op->orc_ava->aa_value.bv_val != mapped_value.bv_val ) {
+               free( mapped_value.bv_val );
+       }
+
+doreturn:;
+       Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_compare_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid );
+       return retcode;
+}
+
+int
+asyncmeta_back_compare( Operation *op, SlapReply *rs )
+{
+       a_metainfo_t    *mi = ( a_metainfo_t * )op->o_bd->be_private;
+       a_metatarget_t  *mt;
+       a_metaconn_t    *mc;
+       int             rc, candidate = -1;
+       OperationBuffer opbuf;
+       bm_context_t *bc;
+       SlapReply *candidates;
+       slap_callback *cb = op->o_callback;
+
+       Debug(LDAP_DEBUG_ARGS, "==> asyncmeta_back_compare: %s\n",
+             op->o_req_dn.bv_val, 0, 0 );
+
+       asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets );
+       if (bc == NULL) {
+               rs->sr_err = LDAP_OTHER;
+               asyncmeta_sender_error(op, rs, cb);
+               return rs->sr_err;
+       }
+
+       candidates = bc->candidates;
+       mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0);
+       if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+               asyncmeta_sender_error(op, rs, cb);
+               asyncmeta_clear_bm_context(bc);
+               return rs->sr_err;
+       }
+
+       mt = mi->mi_targets[ candidate ];
+       bc->timeout = mt->mt_timeout[ SLAP_OP_COMPARE ];
+       bc->retrying = LDAP_BACK_RETRYING;
+       bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying );
+       bc->stoptime = op->o_time + bc->timeout;
+
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+       rc = asyncmeta_add_message_queue(mc, bc);
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+       if (rc != LDAP_SUCCESS) {
+               rs->sr_err = LDAP_BUSY;
+               rs->sr_text = "Maximum pending ops limit exceeded";
+               asyncmeta_clear_bm_context(bc);
+               asyncmeta_sender_error(op, rs, cb);
+               goto finish;
+       }
+
+       rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate);
+       switch (rc)
+       {
+       case META_SEARCH_CANDIDATE:
+               /* target is already bound, just send the request */
+               Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare:  "
+                      "cnd=\"%ld\"\n", op->o_log_prefix, candidate , 0);
+
+               rc = asyncmeta_back_compare_start( op, rs, mc, bc, candidate);
+               if (rc == META_SEARCH_ERR) {
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_drop_bc(mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       asyncmeta_sender_error(op, rs, cb);
+                       asyncmeta_clear_bm_context(bc);
+                       goto finish;
+
+               }
+                       break;
+       case META_SEARCH_NOT_CANDIDATE:
+               Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare: NOT_CANDIDATE "
+                      "cnd=\"%ld\"\n", op->o_log_prefix, candidate , 0);
+               candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+               ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+               asyncmeta_drop_bc(mc, bc);
+               ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+               asyncmeta_sender_error(op, rs, cb);
+               asyncmeta_clear_bm_context(bc);
+               goto finish;
+
+       case META_SEARCH_NEED_BIND:
+       case META_SEARCH_CONNECTING:
+               Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare: NEED_BIND "
+                      "cnd=\"%ld\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+               rc = asyncmeta_dobind_init(op, rs, bc, mc, candidate);
+               if (rc == META_SEARCH_ERR) {
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_drop_bc(mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       asyncmeta_sender_error(op, rs, cb);
+                       asyncmeta_clear_bm_context(bc);
+                       goto finish;
+               }
+               break;
+       case META_SEARCH_BINDING:
+                       Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare: BINDING "
+                              "cnd=\"%ld\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+                       /* Todo add the context to the message queue but do not send the request
+                          the receiver must send this when we are done binding */
+                       /* question - how would do receiver know to which targets??? */
+                       break;
+
+       case META_SEARCH_ERR:
+                       Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare: ERR "
+                              "cnd=\"%ldd\"\n", op->o_log_prefix, candidate , 0);
+                       candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+                       candidates[ candidate ].sr_type = REP_RESULT;
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_drop_bc(mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       asyncmeta_sender_error(op, rs, cb);
+                       asyncmeta_clear_bm_context(bc);
+                       goto finish;
+               default:
+                       assert( 0 );
+                       break;
+               }
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+       asyncmeta_start_one_listener(mc, candidates, candidate);
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+finish:
+       return rs->sr_err;
+
+}
diff --git a/servers/slapd/back-asyncmeta/config.c b/servers/slapd/back-asyncmeta/config.c
new file mode 100644 (file)
index 0000000..1801a55
--- /dev/null
@@ -0,0 +1,3191 @@
+/* config.c - configuration parsing for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ctype.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "config.h"
+#include "lutil.h"
+#include "ldif.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+#ifdef LDAP_DEVEL
+#define SLAP_AUTH_DN   1
+#endif
+
+static ConfigDriver asyncmeta_back_cf_gen;
+static ConfigLDAPadd asyncmeta_ldadd;
+static ConfigCfAdd asyncmeta_cfadd;
+
+static int asyncmeta_map_config(
+       ConfigArgs *c,
+       struct ldapmap  *oc_map,
+       struct ldapmap  *at_map );
+
+/* Three sets of enums:
+ *     1) attrs that are only valid in the base config
+ *     2) attrs that are valid in base or target
+ *     3) attrs that are only valid in a target
+ */
+
+/* Base attrs */
+enum {
+       LDAP_BACK_CFG_DNCACHE_TTL = 1,
+       LDAP_BACK_CFG_IDLE_TIMEOUT,
+       LDAP_BACK_CFG_ONERR,
+       LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER,
+       LDAP_BACK_CFG_CONNPOOLMAX,
+       LDAP_BACK_CFG_MAX_TIMEOUT_OPS,
+       LDAP_BACK_CFG_MAX_PENDING_OPS,
+       LDAP_BACK_CFG_MAX_TARGET_CONNS,
+       LDAP_BACK_CFG_LAST_BASE,
+};
+
+/* Base or target */
+enum {
+       LDAP_BACK_CFG_BIND_TIMEOUT = LDAP_BACK_CFG_LAST_BASE,
+       LDAP_BACK_CFG_CANCEL,
+       LDAP_BACK_CFG_CHASE,
+       LDAP_BACK_CFG_CLIENT_PR,
+       LDAP_BACK_CFG_DEFAULT_T,
+       LDAP_BACK_CFG_NETWORK_TIMEOUT,
+       LDAP_BACK_CFG_NOREFS,
+       LDAP_BACK_CFG_NOUNDEFFILTER,
+       LDAP_BACK_CFG_NRETRIES,
+       LDAP_BACK_CFG_QUARANTINE,
+       LDAP_BACK_CFG_REBIND,
+       LDAP_BACK_CFG_TIMEOUT,
+       LDAP_BACK_CFG_VERSION,
+       LDAP_BACK_CFG_ST_REQUEST,
+       LDAP_BACK_CFG_T_F,
+       LDAP_BACK_CFG_TLS,
+       LDAP_BACK_CFG_LAST_BOTH
+};
+
+/* Target attrs */
+enum {
+       LDAP_BACK_CFG_URI = LDAP_BACK_CFG_LAST_BOTH,
+       LDAP_BACK_CFG_ACL_AUTHCDN,
+       LDAP_BACK_CFG_ACL_PASSWD,
+       LDAP_BACK_CFG_IDASSERT_AUTHZFROM,
+       LDAP_BACK_CFG_IDASSERT_BIND,
+       LDAP_BACK_CFG_REWRITE,
+       LDAP_BACK_CFG_SUFFIXM,
+       LDAP_BACK_CFG_MAP,
+       LDAP_BACK_CFG_SUBTREE_EX,
+       LDAP_BACK_CFG_SUBTREE_IN,
+       LDAP_BACK_CFG_KEEPALIVE,
+       LDAP_BACK_CFG_FILTER,
+       LDAP_BACK_CFG_LAST
+};
+
+static ConfigTable a_metacfg[] = {
+       { "uri", "uri", 2, 0, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_URI,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:0.14 "
+                       "NAME 'olcDbURI' "
+                       "DESC 'URI (list) for remote DSA' "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "tls", "what", 2, 0, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_TLS,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.1 "
+                       "NAME 'olcDbStartTLS' "
+                       "DESC 'StartTLS' "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "acl-authcDN", "DN", 2, 2, 0,
+               ARG_DN|ARG_MAGIC|LDAP_BACK_CFG_ACL_AUTHCDN,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.2 "
+                       "NAME 'olcDbACLAuthcDn' "
+                       "DESC 'Remote ACL administrative identity' "
+                       "OBSOLETE "
+                       "SYNTAX OMsDN "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       /* deprecated, will be removed; aliases "acl-authcDN" */
+       { "binddn", "DN", 2, 2, 0,
+               ARG_DN|ARG_MAGIC|LDAP_BACK_CFG_ACL_AUTHCDN,
+               asyncmeta_back_cf_gen, NULL, NULL, NULL },
+       { "acl-passwd", "cred", 2, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_ACL_PASSWD,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.3 "
+                       "NAME 'olcDbACLPasswd' "
+                       "DESC 'Remote ACL administrative identity credentials' "
+                       "OBSOLETE "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       /* deprecated, will be removed; aliases "acl-passwd" */
+       { "bindpw", "cred", 2, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_ACL_PASSWD,
+               asyncmeta_back_cf_gen, NULL, NULL, NULL },
+       { "idassert-bind", "args", 2, 0, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_BIND,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.7 "
+                       "NAME 'olcDbIDAssertBind' "
+                       "DESC 'Remote Identity Assertion administrative identity auth bind configuration' "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "idassert-authzFrom", "authzRule", 2, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_AUTHZFROM,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.9 "
+                       "NAME 'olcDbIDAssertAuthzFrom' "
+                       "DESC 'Remote Identity Assertion authz rules' "
+                       "EQUALITY caseIgnoreMatch "
+                       "SYNTAX OMsDirectoryString "
+                       "X-ORDERED 'VALUES' )",
+               NULL, NULL },
+       { "rebind-as-user", "true|FALSE", 1, 2, 0,
+               ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_REBIND,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.10 "
+                       "NAME 'olcDbRebindAsUser' "
+                       "DESC 'Rebind as user' "
+                       "SYNTAX OMsBoolean "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "chase-referrals", "true|FALSE", 2, 2, 0,
+               ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_CHASE,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.11 "
+                       "NAME 'olcDbChaseReferrals' "
+                       "DESC 'Chase referrals' "
+                       "SYNTAX OMsBoolean "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "t-f-support", "true|FALSE|discover", 2, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_T_F,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.12 "
+                       "NAME 'olcDbTFSupport' "
+                       "DESC 'Absolute filters support' "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "timeout", "timeout(list)", 2, 0, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_TIMEOUT,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.14 "
+                       "NAME 'olcDbTimeout' "
+                       "DESC 'Per-operation timeouts' "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "idle-timeout", "timeout", 2, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_IDLE_TIMEOUT,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.15 "
+                       "NAME 'olcDbIdleTimeout' "
+                       "DESC 'connection idle timeout' "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "network-timeout", "timeout", 2, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_NETWORK_TIMEOUT,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.17 "
+                       "NAME 'olcDbNetworkTimeout' "
+                       "DESC 'connection network timeout' "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "protocol-version", "version", 2, 2, 0,
+               ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_VERSION,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.18 "
+                       "NAME 'olcDbProtocolVersion' "
+                       "DESC 'protocol version' "
+                       "SYNTAX OMsInteger "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+
+       { "cancel", "ABANDON|ignore|exop", 2, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_CANCEL,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.20 "
+                       "NAME 'olcDbCancel' "
+                       "DESC 'abandon/ignore/exop operations when appropriate' "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "quarantine", "retrylist", 2, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_QUARANTINE,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.21 "
+                       "NAME 'olcDbQuarantine' "
+                       "DESC 'Quarantine database if connection fails and retry according to rule' "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+
+       { "conn-pool-max", "<n>", 2, 2, 0,
+               ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_CONNPOOLMAX,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.23 "
+                       "NAME 'olcDbConnectionPoolMax' "
+                       "DESC 'Max size of privileged connections pool' "
+                       "SYNTAX OMsInteger "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+       { "session-tracking-request", "true|FALSE", 2, 2, 0,
+               ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_ST_REQUEST,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.24 "
+                       "NAME 'olcDbSessionTrackingRequest' "
+                       "DESC 'Add session tracking control to proxied requests' "
+                       "SYNTAX OMsBoolean "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+       { "norefs", "true|FALSE", 2, 2, 0,
+               ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_NOREFS,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.25 "
+                       "NAME 'olcDbNoRefs' "
+                       "DESC 'Do not return search reference responses' "
+                       "SYNTAX OMsBoolean "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "noundeffilter", "true|FALSE", 2, 2, 0,
+               ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_NOUNDEFFILTER,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.26 "
+                       "NAME 'olcDbNoUndefFilter' "
+                       "DESC 'Do not propagate undefined search filters' "
+                       "SYNTAX OMsBoolean "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+
+       { "rewrite", "arglist", 2, 0, STRLENOF( "rewrite" ),
+               ARG_MAGIC|LDAP_BACK_CFG_REWRITE,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.101 "
+                       "NAME 'olcDbRewrite' "
+                       "DESC 'DN rewriting rules' "
+                       "EQUALITY caseIgnoreMatch "
+                       "SYNTAX OMsDirectoryString "
+                       "X-ORDERED 'VALUES' )",
+               NULL, NULL },
+       { "suffixmassage", "virtual> <real", 2, 3, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_SUFFIXM,
+               asyncmeta_back_cf_gen, NULL, NULL, NULL },
+
+       { "map", "attribute|objectClass> [*|<local>] *|<remote", 3, 4, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_MAP,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.102 "
+                       "NAME 'olcDbMap' "
+                       "DESC 'Map attribute and objectclass names' "
+                       "EQUALITY caseIgnoreMatch "
+                       "SYNTAX OMsDirectoryString "
+                       "X-ORDERED 'VALUES' )",
+               NULL, NULL },
+
+       { "subtree-exclude", "pattern", 2, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_SUBTREE_EX,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.103 "
+                       "NAME 'olcDbSubtreeExclude' "
+                       "DESC 'DN of subtree to exclude from target' "
+                       "EQUALITY caseIgnoreMatch "
+                       "SYNTAX OMsDirectoryString )",
+               NULL, NULL },
+       { "subtree-include", "pattern", 2, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_SUBTREE_IN,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.104 "
+                       "NAME 'olcDbSubtreeInclude' "
+                       "DESC 'DN of subtree to include in target' "
+                       "EQUALITY caseIgnoreMatch "
+                       "SYNTAX OMsDirectoryString )",
+               NULL, NULL },
+       { "default-target", "[none|<target ID>]", 1, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_DEFAULT_T,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.105 "
+                       "NAME 'olcDbDefaultTarget' "
+                       "DESC 'Specify the default target' "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "dncache-ttl", "ttl", 2, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_DNCACHE_TTL,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.106 "
+                       "NAME 'olcDbDnCacheTtl' "
+                       "DESC 'dncache ttl' "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "bind-timeout", "microseconds", 2, 2, 0,
+               ARG_MAGIC|ARG_ULONG|LDAP_BACK_CFG_BIND_TIMEOUT,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.107 "
+                       "NAME 'olcDbBindTimeout' "
+                       "DESC 'bind timeout' "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "onerr", "CONTINUE|report|stop", 2, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_ONERR,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.108 "
+                       "NAME 'olcDbOnErr' "
+                       "DESC 'error handling' "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "pseudoroot-bind-defer", "TRUE|false", 2, 2, 0,
+               ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.109 "
+                       "NAME 'olcDbPseudoRootBindDefer' "
+                       "DESC 'error handling' "
+                       "SYNTAX OMsBoolean "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "root-bind-defer", "TRUE|false", 2, 2, 0,
+               ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER,
+               asyncmeta_back_cf_gen, NULL, NULL, NULL },
+       { "nretries", "NEVER|forever|<number>", 2, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_NRETRIES,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.110 "
+                       "NAME 'olcDbNretries' "
+                       "DESC 'retry handling' "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+       { "client-pr", "accept-unsolicited|disable|<size>", 2, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_CLIENT_PR,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.111 "
+                       "NAME 'olcDbClientPr' "
+                       "DESC 'PagedResults handling' "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+
+       { "", "", 0, 0, 0, ARG_IGNORED,
+               NULL, "( OLcfgDbAt:3.116 NAME 'olcAsyncMetaSub' "
+                       "DESC 'Placeholder to name a Target entry' "
+                       "EQUALITY caseIgnoreMatch "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE X-ORDERED 'SIBLINGS' )", NULL, NULL },
+
+       { "keepalive", "keepalive", 2, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_KEEPALIVE,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.29 "
+                       "NAME 'olcDbKeepalive' "
+                       "DESC 'TCP keepalive' "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+               NULL, NULL },
+
+       { "filter", "pattern", 2, 2, 0,
+               ARG_MAGIC|LDAP_BACK_CFG_FILTER,
+               asyncmeta_back_cf_gen, "( OLcfgDbAt:3.112 "
+                       "NAME 'olcDbFilter' "
+                       "DESC 'Filter regex pattern to include in target' "
+                       "EQUALITY caseExactMatch "
+                       "SYNTAX OMsDirectoryString )",
+               NULL, NULL },
+
+       { "max-pending-ops", "<n>", 2, 2, 0,
+         ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_MAX_PENDING_OPS,
+         asyncmeta_back_cf_gen, "( OLcfgDbAt:3.113 "
+         "NAME 'olcDbMaxPendingOps' "
+         "DESC 'Maximum number of pending operations' "
+         "SYNTAX OMsInteger "
+         "SINGLE-VALUE )",
+         NULL, NULL },
+
+       { "max-target-conns", "<n>", 2, 2, 0,
+         ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_MAX_TARGET_CONNS,
+         asyncmeta_back_cf_gen, "( OLcfgDbAt:3.114 "
+         "NAME 'olcDbMaxTargetConns' "
+         "DESC 'Maximum number of open connections per target' "
+         "SYNTAX OMsInteger "
+         "SINGLE-VALUE )",
+         NULL, NULL },
+
+       { "max-timeout-ops", "<n>", 2, 2, 0,
+         ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_MAX_TIMEOUT_OPS,
+         asyncmeta_back_cf_gen, "( OLcfgDbAt:3.115 "
+         "NAME 'olcDbMaxTimeoutOps' "
+         "DESC 'Maximum number of consecutive timeout operations after which the connection is reset' "
+         "SYNTAX OMsInteger "
+         "SINGLE-VALUE )",
+         NULL, NULL },
+
+       { NULL, NULL, 0, 0, 0, ARG_IGNORED,
+               NULL, NULL, NULL, NULL }
+};
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+#define        ST_ATTR "$ olcDbSessionTrackingRequest "
+#else
+#define        ST_ATTR ""
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+#define COMMON_ATTRS   \
+                       "$ olcDbBindTimeout " \
+                       "$ olcDbCancel " \
+                       "$ olcDbChaseReferrals " \
+                       "$ olcDbClientPr " \
+                       "$ olcDbDefaultTarget " \
+                       "$ olcDbNetworkTimeout " \
+                       "$ olcDbNoRefs " \
+                       "$ olcDbNoUndefFilter " \
+                       "$ olcDbNretries " \
+                       "$ olcDbProtocolVersion " \
+                       "$ olcDbQuarantine " \
+                       "$ olcDbRebindAsUser " \
+                       ST_ATTR \
+                       "$ olcDbStartTLS " \
+                       "$ olcDbTFSupport "
+
+static ConfigOCs a_metaocs[] = {
+       { "( OLcfgDbOc:3.4 "
+               "NAME 'olcAsyncMetaConfig' "
+               "DESC 'Asyncmeta backend configuration' "
+               "SUP olcDatabaseConfig "
+               "MAY ( olcDbConnTtl "
+                       "$ olcDbDnCacheTtl "
+                       "$ olcDbIdleTimeout "
+                       "$ olcDbOnErr "
+                       "$ olcDbPseudoRootBindDefer "
+                       "$ olcDbSingleConn "
+                       "$ olcDbUseTemporaryConn "
+                       "$ olcDbConnectionPoolMax "
+                       "$ olcDbMaxTimeoutOps"
+                       "$ olcDbMaxPendingOps "
+                       "$ olcDbMaxTargetConns"
+                       /* defaults, may be overridden per-target */
+                       COMMON_ATTRS
+               ") )",
+                       Cft_Database, a_metacfg, NULL, asyncmeta_cfadd },
+       { "( OLcfgDbOc:3.5 "
+               "NAME 'olcAsyncMetaTargetConfig' "
+               "DESC 'Asyncmeta target configuration' "
+               "SUP olcConfig STRUCTURAL "
+               "MUST ( olcAsyncMetaSub $ olcDbURI ) "
+               "MAY ( olcDbACLAuthcDn "
+                       "$ olcDbACLPasswd "
+                       "$ olcDbIDAssertAuthzFrom "
+                       "$ olcDbIDAssertBind "
+                       "$ olcDbMap "
+                       "$ olcDbRewrite "
+                       "$ olcDbSubtreeExclude "
+                       "$ olcDbSubtreeInclude "
+                       "$ olcDbTimeout "
+                       "$ olcDbKeepalive "
+                       "$ olcDbFilter "
+
+                       /* defaults may be inherited */
+                       COMMON_ATTRS
+               ") )",
+                       Cft_Misc, a_metacfg, asyncmeta_ldadd },
+       { NULL, 0, NULL }
+};
+
+static int
+asyncmeta_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *c )
+{
+       if ( p->ce_type != Cft_Database || !p->ce_be ||
+               p->ce_be->be_cf_ocs != a_metaocs )
+               return LDAP_CONSTRAINT_VIOLATION;
+
+       c->be = p->ce_be;
+       return LDAP_SUCCESS;
+}
+
+static int
+asyncmeta_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *c )
+{
+       a_metainfo_t    *mi = ( a_metainfo_t * )c->be->be_private;
+       struct berval bv;
+       int i;
+
+       bv.bv_val = c->cr_msg;
+       for ( i=0; i<mi->mi_ntargets; i++ ) {
+               bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg),
+                       "olcAsyncMetaSub=" SLAP_X_ORDERED_FMT "uri", i );
+               c->ca_private = mi->mi_targets[i];
+               c->valx = i;
+               config_build_entry( op, rs, p->e_private, c,
+                       &bv, &a_metaocs[1], NULL );
+       }
+
+       return LDAP_SUCCESS;
+}
+
+static int
+asyncmeta_rwi_init( struct rewrite_info **rwm_rw )
+{
+       char                    *rargv[ 3 ];
+
+       *rwm_rw = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
+       if ( *rwm_rw == NULL ) {
+               return -1;
+       }
+       /*
+        * the filter rewrite as a string must be disabled
+        * by default; it can be re-enabled by adding rules;
+        * this creates an empty rewriteContext
+        */
+       rargv[ 0 ] = "rewriteContext";
+       rargv[ 1 ] = "searchFilter";
+       rargv[ 2 ] = NULL;
+       rewrite_parse( *rwm_rw, "<suffix massage>", 1, 2, rargv );
+
+       rargv[ 0 ] = "rewriteContext";
+       rargv[ 1 ] = "default";
+       rargv[ 2 ] = NULL;
+       rewrite_parse( *rwm_rw, "<suffix massage>", 1, 2, rargv );
+
+       return 0;
+}
+
+static int
+asyncmeta_back_new_target(
+       a_metatarget_t  **mtp )
+{
+       a_metatarget_t          *mt;
+
+       *mtp = NULL;
+
+       mt = ch_calloc( sizeof( a_metatarget_t ), 1 );
+
+       if ( asyncmeta_rwi_init( &mt->mt_rwmap.rwm_rw )) {
+               ch_free( mt );
+               return -1;
+       }
+
+       ldap_pvt_thread_mutex_init( &mt->mt_uri_mutex );
+
+       mt->mt_idassert_mode = LDAP_BACK_IDASSERT_LEGACY;
+       mt->mt_idassert_authmethod = LDAP_AUTH_NONE;
+       mt->mt_idassert_tls = SB_TLS_DEFAULT;
+       /* by default, use proxyAuthz control on each operation */
+       mt->mt_idassert_flags = LDAP_BACK_AUTH_PRESCRIPTIVE;
+
+       *mtp = mt;
+
+       return 0;
+}
+
+/* Validation for suffixmassage_config */
+static int
+asyncmeta_suffixm_config(
+       ConfigArgs *c,
+       int argc,
+       char **argv,
+       a_metatarget_t *mt
+)
+{
+       BackendDB       *tmp_bd;
+       struct berval   dn, nvnc, pvnc, nrnc, prnc;
+       int j, rc;
+
+       /*
+        * syntax:
+        *
+        *      suffixmassage <suffix> <massaged suffix>
+        *
+        * the <suffix> field must be defined as a valid suffix
+        * (or suffixAlias?) for the current database;
+        * the <massaged suffix> shouldn't have already been
+        * defined as a valid suffix or suffixAlias for the
+        * current server
+        */
+
+       ber_str2bv( argv[ 1 ], 0, 0, &dn );
+       if ( dnPrettyNormal( NULL, &dn, &pvnc, &nvnc, NULL ) != LDAP_SUCCESS ) {
+               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                       "suffix \"%s\" is invalid",
+                       argv[1] );
+               Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+               return 1;
+       }
+
+       for ( j = 0; !BER_BVISNULL( &c->be->be_nsuffix[ j ] ); j++ ) {
+               if ( dnIsSuffix( &nvnc, &c->be->be_nsuffix[ 0 ] ) ) {
+                       break;
+               }
+       }
+
+       if ( BER_BVISNULL( &c->be->be_nsuffix[ j ] ) ) {
+               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                       "suffix \"%s\" must be within the database naming context",
+                       argv[1] );
+               Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+               free( pvnc.bv_val );
+               free( nvnc.bv_val );
+               return 1;
+       }
+
+       ber_str2bv( argv[ 2 ], 0, 0, &dn );
+       if ( dnPrettyNormal( NULL, &dn, &prnc, &nrnc, NULL ) != LDAP_SUCCESS ) {
+               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                       "massaged suffix \"%s\" is invalid",
+                       argv[2] );
+               Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+               free( pvnc.bv_val );
+               free( nvnc.bv_val );
+               return 1;
+       }
+
+       tmp_bd = select_backend( &nrnc, 0 );
+       if ( tmp_bd != NULL && tmp_bd->be_private == c->be->be_private ) {
+               Debug( LDAP_DEBUG_ANY,
+       "%s: warning: <massaged suffix> \"%s\" resolves to this database, in "
+       "\"suffixMassage <suffix> <massaged suffix>\"\n",
+                       c->log, prnc.bv_val, 0 );
+       }
+
+       /*
+        * The suffix massaging is emulated by means of the
+        * rewrite capabilities
+        */
+       rc = asyncmeta_suffix_massage_config( mt->mt_rwmap.rwm_rw,
+                       &pvnc, &nvnc, &prnc, &nrnc );
+
+       free( pvnc.bv_val );
+       free( nvnc.bv_val );
+       free( prnc.bv_val );
+       free( nrnc.bv_val );
+
+       return rc;
+}
+
+static int
+slap_bv_x_ordered_unparse( BerVarray in, BerVarray *out )
+{
+       int             i;
+       BerVarray       bva = NULL;
+       char            ibuf[32], *ptr;
+       struct berval   idx;
+
+       assert( in != NULL );
+
+       for ( i = 0; !BER_BVISNULL( &in[i] ); i++ )
+               /* count'em */ ;
+
+       if ( i == 0 ) {
+               return 1;
+       }
+
+       idx.bv_val = ibuf;
+
+       bva = ch_malloc( ( i + 1 ) * sizeof(struct berval) );
+       BER_BVZERO( &bva[ 0 ] );
+
+       for ( i = 0; !BER_BVISNULL( &in[i] ); i++ ) {
+               idx.bv_len = snprintf( idx.bv_val, sizeof( ibuf ), SLAP_X_ORDERED_FMT, i );
+               if ( idx.bv_len >= sizeof( ibuf ) ) {
+                       ber_bvarray_free( bva );
+                       return 1;
+               }
+
+               bva[i].bv_len = idx.bv_len + in[i].bv_len;
+               bva[i].bv_val = ch_malloc( bva[i].bv_len + 1 );
+               ptr = lutil_strcopy( bva[i].bv_val, ibuf );
+               ptr = lutil_strcopy( ptr, in[i].bv_val );
+               *ptr = '\0';
+               BER_BVZERO( &bva[ i + 1 ] );
+       }
+
+       *out = bva;
+       return 0;
+}
+
+int
+asyncmeta_subtree_free( a_metasubtree_t *ms )
+{
+       switch ( ms->ms_type ) {
+       case META_ST_SUBTREE:
+       case META_ST_SUBORDINATE:
+               ber_memfree( ms->ms_dn.bv_val );
+               break;
+
+       case META_ST_REGEX:
+               regfree( &ms->ms_regex );
+               ber_memfree( ms->ms_regex_pattern.bv_val );
+               break;
+
+       default:
+               return -1;
+       }
+
+       ch_free( ms );
+       return 0;
+}
+
+int
+asyncmeta_subtree_destroy( a_metasubtree_t *ms )
+{
+       if ( ms->ms_next ) {
+               asyncmeta_subtree_destroy( ms->ms_next );
+       }
+
+       return asyncmeta_subtree_free( ms );
+}
+
+static void
+asyncmeta_filter_free( metafilter_t *mf )
+{
+       regfree( &mf->mf_regex );
+       ber_memfree( mf->mf_regex_pattern.bv_val );
+       ch_free( mf );
+}
+
+void
+asyncmeta_filter_destroy( metafilter_t *mf )
+{
+       if ( mf->mf_next )
+               asyncmeta_filter_destroy( mf->mf_next );
+       asyncmeta_filter_free( mf );
+}
+
+static struct berval st_styles[] = {
+       BER_BVC("subtree"),
+       BER_BVC("children"),
+       BER_BVC("regex")
+};
+
+static int
+asyncmeta_subtree_unparse(
+       ConfigArgs *c,
+       a_metatarget_t *mt )
+{
+       a_metasubtree_t *ms;
+       struct berval bv, *style;
+
+       if ( !mt->mt_subtree )
+               return 1;
+
+       /* can only be one of exclude or include */
+       if (( c->type == LDAP_BACK_CFG_SUBTREE_EX ) ^ mt->mt_subtree_exclude )
+               return 1;
+
+       bv.bv_val = c->cr_msg;
+       for ( ms=mt->mt_subtree; ms; ms=ms->ms_next ) {
+               if (ms->ms_type == META_ST_SUBTREE)
+                       style = &st_styles[0];
+               else if ( ms->ms_type == META_ST_SUBORDINATE )
+                       style = &st_styles[1];
+               else if ( ms->ms_type == META_ST_REGEX )
+                       style = &st_styles[2];
+               else {
+                       assert(0);
+                       continue;
+               }
+               bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg),
+                       "dn.%s:%s", style->bv_val, ms->ms_dn.bv_val );
+               value_add_one( &c->rvalue_vals, &bv );
+       }
+       return 0;
+}
+
+static int
+asyncmeta_subtree_config(
+       a_metatarget_t *mt,
+       ConfigArgs *c )
+{
+       meta_st_t       type = META_ST_SUBTREE;
+       char            *pattern;
+       struct berval   ndn = BER_BVNULL;
+       a_metasubtree_t *ms = NULL;
+
+       if ( c->type == LDAP_BACK_CFG_SUBTREE_EX ) {
+               if ( mt->mt_subtree && !mt->mt_subtree_exclude ) {
+                       snprintf( c->cr_msg, sizeof(c->cr_msg),
+                               "\"subtree-exclude\" incompatible with previous \"subtree-include\" directives" );
+                       return 1;
+               }
+
+               mt->mt_subtree_exclude = 1;
+
+       } else {
+               if ( mt->mt_subtree && mt->mt_subtree_exclude ) {
+                       snprintf( c->cr_msg, sizeof(c->cr_msg),
+                               "\"subtree-include\" incompatible with previous \"subtree-exclude\" directives" );
+                       return 1;
+               }
+       }
+
+       pattern = c->argv[1];
+       if ( strncasecmp( pattern, "dn", STRLENOF( "dn" ) ) == 0 ) {
+               char *style;
+
+               pattern = &pattern[STRLENOF( "dn")];
+
+               if ( pattern[0] == '.' ) {
+                       style = &pattern[1];
+
+                       if ( strncasecmp( style, "subtree", STRLENOF( "subtree" ) ) == 0 ) {
+                               type = META_ST_SUBTREE;
+                               pattern = &style[STRLENOF( "subtree" )];
+
+                       } else if ( strncasecmp( style, "children", STRLENOF( "children" ) ) == 0 ) {
+                               type = META_ST_SUBORDINATE;
+                               pattern = &style[STRLENOF( "children" )];
+
+                       } else if ( strncasecmp( style, "sub", STRLENOF( "sub" ) ) == 0 ) {
+                               type = META_ST_SUBTREE;
+                               pattern = &style[STRLENOF( "sub" )];
+
+                       } else if ( strncasecmp( style, "regex", STRLENOF( "regex" ) ) == 0 ) {
+                               type = META_ST_REGEX;
+                               pattern = &style[STRLENOF( "regex" )];
+
+                       } else {
+                               snprintf( c->cr_msg, sizeof(c->cr_msg), "unknown style in \"dn.<style>\"" );
+                               return 1;
+                       }
+               }
+
+               if ( pattern[0] != ':' ) {
+                       snprintf( c->cr_msg, sizeof(c->cr_msg), "missing colon after \"dn.<style>\"" );
+                       return 1;
+               }
+               pattern++;
+       }
+
+       switch ( type ) {
+       case META_ST_SUBTREE:
+       case META_ST_SUBORDINATE: {
+               struct berval dn;
+
+               ber_str2bv( pattern, 0, 0, &dn );
+               if ( dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL )
+                       != LDAP_SUCCESS )
+               {
+                       snprintf( c->cr_msg, sizeof(c->cr_msg), "DN=\"%s\" is invalid", pattern );
+                       return 1;
+               }
+
+               if ( !dnIsSuffix( &ndn, &mt->mt_nsuffix ) ) {
+                       snprintf( c->cr_msg, sizeof(c->cr_msg),
+                               "DN=\"%s\" is not a subtree of target \"%s\"",
+                               pattern, mt->mt_nsuffix.bv_val );
+                       ber_memfree( ndn.bv_val );
+                       return( 1 );
+               }
+               } break;
+
+       default:
+               /* silence warnings */
+               break;
+       }
+
+       ms = ch_calloc( sizeof( a_metasubtree_t ), 1 );
+       ms->ms_type = type;
+
+       switch ( ms->ms_type ) {
+       case META_ST_SUBTREE:
+       case META_ST_SUBORDINATE:
+               ms->ms_dn = ndn;
+               break;
+
+       case META_ST_REGEX: {
+               int rc;
+
+               rc = regcomp( &ms->ms_regex, pattern, REG_EXTENDED|REG_ICASE );
+               if ( rc != 0 ) {
+                       char regerr[ SLAP_TEXT_BUFLEN ];
+
+                       regerror( rc, &ms->ms_regex, regerr, sizeof(regerr) );
+
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "regular expression \"%s\" bad because of %s",
+                               pattern, regerr );
+                       ch_free( ms );
+                       return 1;
+               }
+               ber_str2bv( pattern, 0, 1, &ms->ms_regex_pattern );
+               } break;
+       }
+
+       if ( mt->mt_subtree == NULL ) {
+                mt->mt_subtree = ms;
+
+       } else {
+               a_metasubtree_t **msp;
+
+               for ( msp = &mt->mt_subtree; *msp; ) {
+                       switch ( ms->ms_type ) {
+                       case META_ST_SUBTREE:
+                               switch ( (*msp)->ms_type ) {
+                               case META_ST_SUBTREE:
+                                       if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) {
+                                               a_metasubtree_t *tmp = *msp;
+                                               Debug( LDAP_DEBUG_CONFIG,
+                                                       "%s: previous rule \"dn.subtree:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n",
+                                                       c->log, pattern, (*msp)->ms_dn.bv_val );
+                                               *msp = (*msp)->ms_next;
+                                               tmp->ms_next = NULL;
+                                               asyncmeta_subtree_destroy( tmp );
+                                               continue;
+
+                                       } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) ) {
+                                               Debug( LDAP_DEBUG_CONFIG,
+                                                       "%s: previous rule \"dn.subtree:%s\" contains rule \"dn.subtree:%s\" (ignored)\n",
+                                                       c->log, (*msp)->ms_dn.bv_val, pattern );
+                                               asyncmeta_subtree_destroy( ms );
+                                               ms = NULL;
+                                               return( 0 );
+                                       }
+                                       break;
+
+                               case META_ST_SUBORDINATE:
+                                       if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) {
+                                               a_metasubtree_t *tmp = *msp;
+                                               Debug( LDAP_DEBUG_CONFIG,
+                                                       "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n",
+                                                       c->log, pattern, (*msp)->ms_dn.bv_val );
+                                               *msp = (*msp)->ms_next;
+                                               tmp->ms_next = NULL;
+                                               asyncmeta_subtree_destroy( tmp );
+                                               continue;
+
+                                       } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) && ms->ms_dn.bv_len > (*msp)->ms_dn.bv_len ) {
+                                               Debug( LDAP_DEBUG_CONFIG,
+                                                       "%s: previous rule \"dn.children:%s\" contains rule \"dn.subtree:%s\" (ignored)\n",
+                                                       c->log, (*msp)->ms_dn.bv_val, pattern );
+                                               asyncmeta_subtree_destroy( ms );
+                                               ms = NULL;
+                                               return( 0 );
+                                       }
+                                       break;
+
+                               case META_ST_REGEX:
+                                       if ( regexec( &(*msp)->ms_regex, ms->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) {
+                                               Debug( LDAP_DEBUG_CONFIG,
+                                                       "%s: previous rule \"dn.regex:%s\" may contain rule \"dn.subtree:%s\"\n",
+                                                       c->log, (*msp)->ms_regex_pattern.bv_val, ms->ms_dn.bv_val );
+                                       }
+                                       break;
+                               }
+                               break;
+
+                       case META_ST_SUBORDINATE:
+                               switch ( (*msp)->ms_type ) {
+                               case META_ST_SUBTREE:
+                                       if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) {
+                                               a_metasubtree_t *tmp = *msp;
+                                               Debug( LDAP_DEBUG_CONFIG,
+                                                       "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n",
+                                                       c->log, pattern, (*msp)->ms_dn.bv_val );
+                                               *msp = (*msp)->ms_next;
+                                               tmp->ms_next = NULL;
+                                               asyncmeta_subtree_destroy( tmp );
+                                               continue;
+
+                                       } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) && ms->ms_dn.bv_len > (*msp)->ms_dn.bv_len ) {
+                                               Debug( LDAP_DEBUG_CONFIG,
+                                                       "%s: previous rule \"dn.children:%s\" contains rule \"dn.subtree:%s\" (ignored)\n",
+                                                       c->log, (*msp)->ms_dn.bv_val, pattern );
+                                               asyncmeta_subtree_destroy( ms );
+                                               ms = NULL;
+                                               return( 0 );
+                                       }
+                                       break;
+
+                               case META_ST_SUBORDINATE:
+                                       if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) {
+                                               a_metasubtree_t *tmp = *msp;
+                                               Debug( LDAP_DEBUG_CONFIG,
+                                                       "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.children:%s\" (replaced)\n",
+                                                       c->log, pattern, (*msp)->ms_dn.bv_val );
+                                               *msp = (*msp)->ms_next;
+                                               tmp->ms_next = NULL;
+                                               asyncmeta_subtree_destroy( tmp );
+                                               continue;
+
+                                       } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) ) {
+                                               Debug( LDAP_DEBUG_CONFIG,
+                                                       "%s: previous rule \"dn.children:%s\" contains rule \"dn.children:%s\" (ignored)\n",
+                                                       c->log, (*msp)->ms_dn.bv_val, pattern );
+                                               asyncmeta_subtree_destroy( ms );
+                                               ms = NULL;
+                                               return( 0 );
+                                       }
+                                       break;
+
+                               case META_ST_REGEX:
+                                       if ( regexec( &(*msp)->ms_regex, ms->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) {
+                                               Debug( LDAP_DEBUG_CONFIG,
+                                                       "%s: previous rule \"dn.regex:%s\" may contain rule \"dn.subtree:%s\"\n",
+                                                       c->log, (*msp)->ms_regex_pattern.bv_val, ms->ms_dn.bv_val );
+                                       }
+                                       break;
+                               }
+                               break;
+
+                       case META_ST_REGEX:
+                               switch ( (*msp)->ms_type ) {
+                               case META_ST_SUBTREE:
+                               case META_ST_SUBORDINATE:
+                                       if ( regexec( &ms->ms_regex, (*msp)->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) {
+                                               Debug( LDAP_DEBUG_CONFIG,
+                                                       "%s: previous rule \"dn.subtree:%s\" may be contained in rule \"dn.regex:%s\"\n",
+                                                       c->log, (*msp)->ms_dn.bv_val, ms->ms_regex_pattern.bv_val );
+                                       }
+                                       break;
+
+                               case META_ST_REGEX:
+                                       /* no check possible */
+                                       break;
+                               }
+                               break;
+                       }
+
+                       msp = &(*msp)->ms_next;
+               }
+
+               *msp = ms;
+       }
+
+       return 0;
+}
+
+static slap_verbmasks idassert_mode[] = {
+       { BER_BVC("self"),              LDAP_BACK_IDASSERT_SELF },
+       { BER_BVC("anonymous"),         LDAP_BACK_IDASSERT_ANONYMOUS },
+       { BER_BVC("none"),              LDAP_BACK_IDASSERT_NOASSERT },
+       { BER_BVC("legacy"),            LDAP_BACK_IDASSERT_LEGACY },
+       { BER_BVNULL,                   0 }
+};
+
+static slap_verbmasks tls_mode[] = {
+       { BER_BVC( "propagate" ),       LDAP_BACK_F_TLS_PROPAGATE_MASK },
+       { BER_BVC( "try-propagate" ),   LDAP_BACK_F_PROPAGATE_TLS },
+       { BER_BVC( "start" ),           LDAP_BACK_F_TLS_USE_MASK },
+       { BER_BVC( "try-start" ),       LDAP_BACK_F_USE_TLS },
+       { BER_BVC( "ldaps" ),           LDAP_BACK_F_TLS_LDAPS },
+       { BER_BVC( "none" ),            LDAP_BACK_F_NONE },
+       { BER_BVNULL,                   0 }
+};
+
+static slap_verbmasks t_f_mode[] = {
+       { BER_BVC( "yes" ),             LDAP_BACK_F_T_F },
+       { BER_BVC( "discover" ),        LDAP_BACK_F_T_F_DISCOVER },
+       { BER_BVC( "no" ),              LDAP_BACK_F_NONE },
+       { BER_BVNULL,                   0 }
+};
+
+static slap_verbmasks cancel_mode[] = {
+       { BER_BVC( "ignore" ),          LDAP_BACK_F_CANCEL_IGNORE },
+       { BER_BVC( "exop" ),            LDAP_BACK_F_CANCEL_EXOP },
+       { BER_BVC( "exop-discover" ),   LDAP_BACK_F_CANCEL_EXOP_DISCOVER },
+       { BER_BVC( "abandon" ),         LDAP_BACK_F_CANCEL_ABANDON },
+       { BER_BVNULL,                   0 }
+};
+
+static slap_verbmasks onerr_mode[] = {
+       { BER_BVC( "stop" ),            META_BACK_F_ONERR_STOP },
+       { BER_BVC( "report" ),  META_BACK_F_ONERR_REPORT },
+       { BER_BVC( "continue" ),                LDAP_BACK_F_NONE },
+       { BER_BVNULL,                   0 }
+};
+
+/* see enum in slap.h */
+static slap_cf_aux_table timeout_table[] = {
+       { BER_BVC("bind="),     SLAP_OP_BIND * sizeof( time_t ),        'u', 0, NULL },
+       /* unbind makes no sense */
+       { BER_BVC("add="),      SLAP_OP_ADD * sizeof( time_t ),         'u', 0, NULL },
+       { BER_BVC("delete="),   SLAP_OP_DELETE * sizeof( time_t ),      'u', 0, NULL },
+       { BER_BVC("modrdn="),   SLAP_OP_MODRDN * sizeof( time_t ),      'u', 0, NULL },
+       { BER_BVC("modify="),   SLAP_OP_MODIFY * sizeof( time_t ),      'u', 0, NULL },
+       { BER_BVC("compare="),  SLAP_OP_COMPARE * sizeof( time_t ),     'u', 0, NULL },
+       { BER_BVC("search="),   SLAP_OP_SEARCH * sizeof( time_t ),      'u', 0, NULL },
+       /* abandon makes little sense */
+#if 0  /* not implemented yet */
+       { BER_BVC("extended="), SLAP_OP_EXTENDED * sizeof( time_t ),    'u', 0, NULL },
+#endif
+       { BER_BVNULL, 0, 0, 0, NULL }
+};
+
+static int
+asyncmeta_cf_cleanup( ConfigArgs *c )
+{
+       a_metainfo_t    *mi = ( a_metainfo_t * )c->be->be_private;
+       a_metatarget_t  *mt = c->ca_private;
+
+       return asyncmeta_target_finish( mi, mt, c->log, c->cr_msg, sizeof( c->cr_msg ));
+}
+
+static int
+asyncmeta_back_cf_gen( ConfigArgs *c )
+{
+       a_metainfo_t    *mi = ( a_metainfo_t * )c->be->be_private;
+       a_metatarget_t  *mt;
+       a_metacommon_t  *mc;
+
+       int i, rc = 0;
+
+       assert( mi != NULL );
+
+       if ( c->op == SLAP_CONFIG_EMIT || c->op == LDAP_MOD_DELETE ) {
+               if ( !mi )
+                       return 1;
+
+               if ( c->table == Cft_Database ) {
+                       mt = NULL;
+                       mc = &mi->mi_mc;
+               } else {
+                       mt = c->ca_private;
+                       mc = &mt->mt_mc;
+               }
+       }
+
+       if ( c->op == SLAP_CONFIG_EMIT ) {
+               struct berval bv = BER_BVNULL;
+
+               switch( c->type ) {
+               /* Base attrs */
+
+               case LDAP_BACK_CFG_DNCACHE_TTL:
+                       if ( mi->mi_cache.ttl == META_DNCACHE_DISABLED ) {
+                               return 1;
+                       } else if ( mi->mi_cache.ttl == META_DNCACHE_FOREVER ) {
+                               BER_BVSTR( &bv, "forever" );
+                       } else {
+                               char    buf[ SLAP_TEXT_BUFLEN ];
+
+                               lutil_unparse_time( buf, sizeof( buf ), mi->mi_cache.ttl );
+                               ber_str2bv( buf, 0, 0, &bv );
+                       }
+                       value_add_one( &c->rvalue_vals, &bv );
+                       break;
+
+               case LDAP_BACK_CFG_IDLE_TIMEOUT:
+                       if ( mi->mi_idle_timeout == 0 ) {
+                               return 1;
+                       } else {
+                               char    buf[ SLAP_TEXT_BUFLEN ];
+
+                               lutil_unparse_time( buf, sizeof( buf ), mi->mi_idle_timeout );
+                               ber_str2bv( buf, 0, 0, &bv );
+                               value_add_one( &c->rvalue_vals, &bv );
+                       }
+                       break;
+
+               case LDAP_BACK_CFG_ONERR:
+                       enum_to_verb( onerr_mode, mi->mi_flags & META_BACK_F_ONERR_MASK, &bv );
+                       if ( BER_BVISNULL( &bv )) {
+                               rc = 1;
+                       } else {
+                               value_add_one( &c->rvalue_vals, &bv );
+                       }
+                       break;
+
+               case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER:
+                       c->value_int = META_BACK_DEFER_ROOTDN_BIND( mi );
+                       break;
+
+               case LDAP_BACK_CFG_CONNPOOLMAX:
+                       c->value_int = mi->mi_conn_priv_max;
+                       break;
+
+               /* common attrs */
+               case LDAP_BACK_CFG_BIND_TIMEOUT:
+                       if ( mc->mc_bind_timeout.tv_sec == 0 &&
+                               mc->mc_bind_timeout.tv_usec == 0 ) {
+                               return 1;
+                       } else {
+                               c->value_ulong = mc->mc_bind_timeout.tv_sec * 1000000UL +
+                                       mc->mc_bind_timeout.tv_usec;
+                       }
+                       break;
+
+               case LDAP_BACK_CFG_CANCEL: {
+                       slap_mask_t     mask = LDAP_BACK_F_CANCEL_MASK2;
+
+                       if ( mt && META_BACK_TGT_CANCEL_DISCOVER( mt ) ) {
+                               mask &= ~LDAP_BACK_F_CANCEL_EXOP;
+                       }
+                       enum_to_verb( cancel_mode, (mc->mc_flags & mask), &bv );
+                       if ( BER_BVISNULL( &bv ) ) {
+                               /* there's something wrong... */
+                               assert( 0 );
+                               rc = 1;
+
+                       } else {
+                               value_add_one( &c->rvalue_vals, &bv );
+                       }
+                       } break;
+
+               case LDAP_BACK_CFG_CHASE:
+                       c->value_int = META_BACK_CMN_CHASE_REFERRALS(mc);
+                       break;
+
+#ifdef SLAPD_META_CLIENT_PR
+               case LDAP_BACK_CFG_CLIENT_PR:
+                       if ( mc->mc_ps == META_CLIENT_PR_DISABLE ) {
+                               return 1;
+                       } else if ( mc->mc_ps == META_CLIENT_PR_ACCEPT_UNSOLICITED ) {
+                               BER_BVSTR( &bv, "accept-unsolicited" );
+                       } else {
+                               bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d", mc->mc_ps );
+                               bv.bv_val = c->cr_msg;
+                       }
+                       value_add_one( &c->rvalue_vals, &bv );
+                       break;
+#endif /* SLAPD_META_CLIENT_PR */
+
+               case LDAP_BACK_CFG_DEFAULT_T:
+                       if ( mt || mi->mi_defaulttarget == META_DEFAULT_TARGET_NONE )
+                               return 1;
+                       bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d", mi->mi_defaulttarget );
+                       bv.bv_val = c->cr_msg;
+                       value_add_one( &c->rvalue_vals, &bv );
+                       break;
+
+               case LDAP_BACK_CFG_NETWORK_TIMEOUT:
+                       if ( mc->mc_network_timeout == 0 ) {
+                               return 1;
+                       }
+                       bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%ld",
+                               mc->mc_network_timeout );
+                       bv.bv_val = c->cr_msg;
+                       value_add_one( &c->rvalue_vals, &bv );
+                       break;
+
+               case LDAP_BACK_CFG_NOREFS:
+                       c->value_int = META_BACK_CMN_NOREFS(mc);
+                       break;
+
+               case LDAP_BACK_CFG_NOUNDEFFILTER:
+                       c->value_int = META_BACK_CMN_NOUNDEFFILTER(mc);
+                       break;
+
+               case LDAP_BACK_CFG_NRETRIES:
+                       if ( mc->mc_nretries == META_RETRY_FOREVER ) {
+                               BER_BVSTR( &bv, "forever" );
+                       } else if ( mc->mc_nretries == META_RETRY_NEVER ) {
+                               BER_BVSTR( &bv, "never" );
+                       } else {
+                               bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d",
+                                       mc->mc_nretries );
+                               bv.bv_val = c->cr_msg;
+                       }
+                       value_add_one( &c->rvalue_vals, &bv );
+                       break;
+
+               case LDAP_BACK_CFG_QUARANTINE:
+                       if ( !META_BACK_CMN_QUARANTINE( mc )) {
+                               rc = 1;
+                               break;
+                       }
+                       rc = mi->mi_ldap_extra->retry_info_unparse( &mc->mc_quarantine, &bv );
+                       if ( rc == 0 ) {
+                               ber_bvarray_add( &c->rvalue_vals, &bv );
+                       }
+                       break;
+
+               case LDAP_BACK_CFG_REBIND:
+                       c->value_int = META_BACK_CMN_SAVECRED(mc);
+                       break;
+
+               case LDAP_BACK_CFG_TIMEOUT:
+                       for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+                               if ( mc->mc_timeout[ i ] != 0 ) {
+                                       break;
+                               }
+                       }
+
+                       if ( i == SLAP_OP_LAST ) {
+                               return 1;
+                       }
+
+                       BER_BVZERO( &bv );
+                       slap_cf_aux_table_unparse( mc->mc_timeout, &bv, timeout_table );
+
+                       if ( BER_BVISNULL( &bv ) ) {
+                               return 1;
+                       }
+
+                       for ( i = 0; isspace( (unsigned char) bv.bv_val[ i ] ); i++ )
+                               /* count spaces */ ;
+
+                       if ( i ) {
+                               bv.bv_len -= i;
+                               AC_MEMCPY( bv.bv_val, &bv.bv_val[ i ],
+                                       bv.bv_len + 1 );
+                       }
+
+                       ber_bvarray_add( &c->rvalue_vals, &bv );
+                       break;
+
+               case LDAP_BACK_CFG_VERSION:
+                       if ( mc->mc_version == 0 )
+                               return 1;
+                       c->value_int = mc->mc_version;
+                       break;
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+               case LDAP_BACK_CFG_ST_REQUEST:
+                       c->value_int = META_BACK_CMN_ST_REQUEST( mc );
+                       break;
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+               case LDAP_BACK_CFG_T_F:
+                       enum_to_verb( t_f_mode, (mc->mc_flags & LDAP_BACK_F_T_F_MASK2), &bv );
+                       if ( BER_BVISNULL( &bv ) ) {
+                               /* there's something wrong... */
+                               assert( 0 );
+                               rc = 1;
+
+                       } else {
+                               value_add_one( &c->rvalue_vals, &bv );
+                       }
+                       break;
+
+               case LDAP_BACK_CFG_TLS: {
+                       struct berval bc = BER_BVNULL, bv2;
+
+                       if (( mc->mc_flags & LDAP_BACK_F_TLS_MASK ) == LDAP_BACK_F_NONE ) {
+                               rc = 1;
+                               break;
+                       }
+                       enum_to_verb( tls_mode, ( mc->mc_flags & LDAP_BACK_F_TLS_MASK ), &bv );
+                       assert( !BER_BVISNULL( &bv ) );
+
+                       if ( mt ) {
+                               bindconf_tls_unparse( &mt->mt_tls, &bc );
+                       }
+
+                       if ( !BER_BVISEMPTY( &bc )) {
+                               bv2.bv_len = bv.bv_len + bc.bv_len + 1;
+                               bv2.bv_val = ch_malloc( bv2.bv_len + 1 );
+                               strcpy( bv2.bv_val, bv.bv_val );
+                               bv2.bv_val[bv.bv_len] = ' ';
+                               strcpy( &bv2.bv_val[bv.bv_len + 1], bc.bv_val );
+                               ber_memfree( bc.bv_val );
+                               ber_bvarray_add( &c->rvalue_vals, &bv2 );
+                       } else {
+                               value_add_one( &c->rvalue_vals, &bv );
+                       }
+                       } break;
+
+               /* target attrs */
+               case LDAP_BACK_CFG_URI: {
+                       char *p2, *p1 = strchr( mt->mt_uri, ' ' );
+                       bv.bv_len = strlen( mt->mt_uri ) + 3 + mt->mt_psuffix.bv_len;
+                       bv.bv_val = ch_malloc( bv.bv_len + 1 );
+                       p2 = bv.bv_val;
+                       *p2++ = '"';
+                       if ( p1 ) {
+                               p2 = lutil_strncopy( p2, mt->mt_uri, p1 - mt->mt_uri );
+                       } else {
+                               p2 = lutil_strcopy( p2, mt->mt_uri );
+                       }
+                       *p2++ = '/';
+                       p2 = lutil_strcopy( p2, mt->mt_psuffix.bv_val );
+                       *p2++ = '"';
+                       if ( p1 ) {
+                               strcpy( p2, p1 );
+                       }
+                       ber_bvarray_add( &c->rvalue_vals, &bv );
+                       } break;
+
+               case LDAP_BACK_CFG_ACL_AUTHCDN:
+               case LDAP_BACK_CFG_ACL_PASSWD:
+                       /* FIXME no point here, there is no code implementing
+                        * their features. Was this supposed to implement
+                        * acl-bind like back-ldap?
+                        */
+                       rc = 1;
+                       break;
+
+               case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: {
+                       BerVarray       *bvp;
+                       int             i;
+                       struct berval   bv = BER_BVNULL;
+                       char            buf[SLAP_TEXT_BUFLEN];
+
+                       bvp = &mt->mt_idassert_authz;
+                       if ( *bvp == NULL ) {
+                               if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL )
+                               {
+                                       BER_BVSTR( &bv, "*" );
+                                       value_add_one( &c->rvalue_vals, &bv );
+
+                               } else {
+                                       rc = 1;
+                               }
+                               break;
+                       }
+
+                       for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ ) {
+                               char *ptr;
+                               int len = snprintf( buf, sizeof( buf ), SLAP_X_ORDERED_FMT, i );
+                               bv.bv_len = ((*bvp)[ i ]).bv_len + len;
+                               bv.bv_val = ber_memrealloc( bv.bv_val, bv.bv_len + 1 );
+                               ptr = bv.bv_val;
+                               ptr = lutil_strcopy( ptr, buf );
+                               ptr = lutil_strncopy( ptr, ((*bvp)[ i ]).bv_val, ((*bvp)[ i ]).bv_len );
+                               value_add_one( &c->rvalue_vals, &bv );
+                       }
+                       if ( bv.bv_val ) {
+                               ber_memfree( bv.bv_val );
+                       }
+                       break;
+               }
+
+               case LDAP_BACK_CFG_IDASSERT_BIND: {
+                       int             i;
+                       struct berval   bc = BER_BVNULL;
+                       char            *ptr;
+
+                       if ( mt->mt_idassert_authmethod == LDAP_AUTH_NONE ) {
+                               return 1;
+                       } else {
+                               ber_len_t       len;
+
+                               switch ( mt->mt_idassert_mode ) {
+                               case LDAP_BACK_IDASSERT_OTHERID:
+                               case LDAP_BACK_IDASSERT_OTHERDN:
+                                       break;
+
+                               default: {
+                                       struct berval   mode = BER_BVNULL;
+
+                                       enum_to_verb( idassert_mode, mt->mt_idassert_mode, &mode );
+                                       if ( BER_BVISNULL( &mode ) ) {
+                                               /* there's something wrong... */
+                                               assert( 0 );
+                                               rc = 1;
+
+                                       } else {
+                                               bv.bv_len = STRLENOF( "mode=" ) + mode.bv_len;
+                                               bv.bv_val = ch_malloc( bv.bv_len + 1 );
+
+                                               ptr = lutil_strcopy( bv.bv_val, "mode=" );
+                                               ptr = lutil_strcopy( ptr, mode.bv_val );
+                                       }
+                                       break;
+                               }
+                               }
+
+                               if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) {
+                                       len = bv.bv_len + STRLENOF( "authz=native" );
+
+                                       if ( !BER_BVISEMPTY( &bv ) ) {
+                                               len += STRLENOF( " " );
+                                       }
+
+                                       bv.bv_val = ch_realloc( bv.bv_val, len + 1 );
+
+                                       ptr = &bv.bv_val[ bv.bv_len ];
+
+                                       if ( !BER_BVISEMPTY( &bv ) ) {
+                                               ptr = lutil_strcopy( ptr, " " );
+                                       }
+
+                                       (void)lutil_strcopy( ptr, "authz=native" );
+                               }
+
+                               len = bv.bv_len + STRLENOF( "flags=non-prescriptive,override,obsolete-encoding-workaround,proxy-authz-non-critical,dn-authzid" );
+                               /* flags */
+                               if ( !BER_BVISEMPTY( &bv ) ) {
+                                       len += STRLENOF( " " );
+                               }
+
+                               bv.bv_val = ch_realloc( bv.bv_val, len + 1 );
+
+                               ptr = &bv.bv_val[ bv.bv_len ];
+
+                               if ( !BER_BVISEMPTY( &bv ) ) {
+                                       ptr = lutil_strcopy( ptr, " " );
+                               }
+
+                               ptr = lutil_strcopy( ptr, "flags=" );
+
+                               if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+                                       ptr = lutil_strcopy( ptr, "prescriptive" );
+                               } else {
+                                       ptr = lutil_strcopy( ptr, "non-prescriptive" );
+                               }
+
+                               if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) {
+                                       ptr = lutil_strcopy( ptr, ",override" );
+                               }
+
+                               if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) {
+                                       ptr = lutil_strcopy( ptr, ",obsolete-proxy-authz" );
+
+                               } else if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) {
+                                       ptr = lutil_strcopy( ptr, ",obsolete-encoding-workaround" );
+                               }
+
+                               if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ) {
+                                       ptr = lutil_strcopy( ptr, ",proxy-authz-critical" );
+
+                               } else {
+                                       ptr = lutil_strcopy( ptr, ",proxy-authz-non-critical" );
+                               }
+
+#ifdef SLAP_AUTH_DN
+                               switch ( mt->mt_idassert_flags & LDAP_BACK_AUTH_DN_MASK ) {
+                               case LDAP_BACK_AUTH_DN_AUTHZID:
+                                       ptr = lutil_strcopy( ptr, ",dn-authzid" );
+                                       break;
+
+                               case LDAP_BACK_AUTH_DN_WHOAMI:
+                                       ptr = lutil_strcopy( ptr, ",dn-whoami" );
+                                       break;
+
+                               default:
+#if 0 /* implicit */
+                                       ptr = lutil_strcopy( ptr, ",dn-none" );
+#endif
+                                       break;
+                               }
+#endif
+
+                               bv.bv_len = ( ptr - bv.bv_val );
+                               /* end-of-flags */
+                       }
+
+                       bindconf_unparse( &mt->mt_idassert.si_bc, &bc );
+
+                       if ( !BER_BVISNULL( &bv ) ) {
+                               ber_len_t       len = bv.bv_len + bc.bv_len;
+
+                               bv.bv_val = ch_realloc( bv.bv_val, len + 1 );
+
+                               assert( bc.bv_val[ 0 ] == ' ' );
+
+                               ptr = lutil_strcopy( &bv.bv_val[ bv.bv_len ], bc.bv_val );
+                               free( bc.bv_val );
+                               bv.bv_len = ptr - bv.bv_val;
+
+                       } else {
+                               for ( i = 0; isspace( (unsigned char) bc.bv_val[ i ] ); i++ )
+                                       /* count spaces */ ;
+
+                               if ( i ) {
+                                       bc.bv_len -= i;
+                                       AC_MEMCPY( bc.bv_val, &bc.bv_val[ i ], bc.bv_len + 1 );
+                               }
+
+                               bv = bc;
+                       }
+
+                       ber_bvarray_add( &c->rvalue_vals, &bv );
+
+                       break;
+               }
+
+               case LDAP_BACK_CFG_SUFFIXM:     /* unused */
+               case LDAP_BACK_CFG_REWRITE:
+                       if ( mt->mt_rwmap.rwm_bva_rewrite == NULL ) {
+                               rc = 1;
+                       } else {
+                               rc = slap_bv_x_ordered_unparse( mt->mt_rwmap.rwm_bva_rewrite, &c->rvalue_vals );
+                       }
+                       break;
+
+               case LDAP_BACK_CFG_MAP:
+                       if ( mt->mt_rwmap.rwm_bva_map == NULL ) {
+                               rc = 1;
+                       } else {
+                               rc = slap_bv_x_ordered_unparse( mt->mt_rwmap.rwm_bva_map, &c->rvalue_vals );
+                       }
+                       break;
+
+               case LDAP_BACK_CFG_SUBTREE_EX:
+               case LDAP_BACK_CFG_SUBTREE_IN:
+                       rc = asyncmeta_subtree_unparse( c, mt );
+                       break;
+
+               case LDAP_BACK_CFG_FILTER:
+                       if ( mt->mt_filter == NULL ) {
+                               rc = 1;
+                       } else {
+                               metafilter_t *mf;
+                               for ( mf = mt->mt_filter; mf; mf = mf->mf_next )
+                                       value_add_one( &c->rvalue_vals, &mf->mf_regex_pattern );
+                       }
+                       break;
+               case LDAP_BACK_CFG_MAX_PENDING_OPS:
+                       c->value_int = mi->mi_max_pending_ops;
+                       break;
+
+               case LDAP_BACK_CFG_MAX_TARGET_CONNS:
+                       c->value_int = mi->mi_max_target_conns;
+                       break;
+               case LDAP_BACK_CFG_MAX_TIMEOUT_OPS:
+                       c->value_int = mi->mi_max_timeout_ops;
+                       break;
+
+               case LDAP_BACK_CFG_KEEPALIVE: {
+                               struct berval bv;
+                               char buf[AC_LINE_MAX];
+                               bv.bv_len = AC_LINE_MAX;
+                               bv.bv_val = &buf[0];
+                               slap_keepalive_parse(&bv, &mt->mt_tls.sb_keepalive, 0, 0, 1);
+                               value_add_one( &c->rvalue_vals, &bv );
+                               break;
+                       }
+
+               default:
+                       rc = 1;
+               }
+               return rc;
+       } else if ( c->op == LDAP_MOD_DELETE ) {
+               switch( c->type ) {
+               /* Base attrs */
+               case LDAP_BACK_CFG_DNCACHE_TTL:
+                       mi->mi_cache.ttl = META_DNCACHE_DISABLED;
+                       break;
+
+               case LDAP_BACK_CFG_IDLE_TIMEOUT:
+                       mi->mi_idle_timeout = 0;
+                       break;
+
+               case LDAP_BACK_CFG_ONERR:
+                       mi->mi_flags &= ~META_BACK_F_ONERR_MASK;
+                       break;
+
+               case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER:
+                       mi->mi_flags &= ~META_BACK_F_DEFER_ROOTDN_BIND;
+                       break;
+
+               case LDAP_BACK_CFG_CONNPOOLMAX:
+                       mi->mi_conn_priv_max = LDAP_BACK_CONN_PRIV_MIN;
+                       break;
+
+               /* common attrs */
+               case LDAP_BACK_CFG_BIND_TIMEOUT:
+                       mc->mc_bind_timeout.tv_sec = 0;
+                       mc->mc_bind_timeout.tv_usec = 0;
+                       break;
+
+               case LDAP_BACK_CFG_CANCEL:
+                       mc->mc_flags &= ~LDAP_BACK_F_CANCEL_MASK2;
+                       break;
+
+               case LDAP_BACK_CFG_CHASE:
+                       mc->mc_flags &= ~LDAP_BACK_F_CHASE_REFERRALS;
+                       break;
+
+#ifdef SLAPD_META_CLIENT_PR
+               case LDAP_BACK_CFG_CLIENT_PR:
+                       mc->mc_ps = META_CLIENT_PR_DISABLE;
+                       break;
+#endif /* SLAPD_META_CLIENT_PR */
+
+               case LDAP_BACK_CFG_DEFAULT_T:
+                       mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE;
+                       break;
+
+               case LDAP_BACK_CFG_NETWORK_TIMEOUT:
+                       mc->mc_network_timeout = 0;
+                       break;
+
+               case LDAP_BACK_CFG_NOREFS:
+                       mc->mc_flags &= ~LDAP_BACK_F_NOREFS;
+                       break;
+
+               case LDAP_BACK_CFG_NOUNDEFFILTER:
+                       mc->mc_flags &= ~LDAP_BACK_F_NOUNDEFFILTER;
+                       break;
+
+               case LDAP_BACK_CFG_NRETRIES:
+                       mc->mc_nretries = META_RETRY_DEFAULT;
+                       break;
+
+               case LDAP_BACK_CFG_QUARANTINE:
+                       if ( META_BACK_CMN_QUARANTINE( mc )) {
+                               mi->mi_ldap_extra->retry_info_destroy( &mc->mc_quarantine );
+                               mc->mc_flags &= ~LDAP_BACK_F_QUARANTINE;
+                               if ( mc == &mt->mt_mc ) {
+                                       ldap_pvt_thread_mutex_destroy( &mt->mt_quarantine_mutex );
+                                       mt->mt_isquarantined = 0;
+                               }
+                       }
+                       break;
+
+               case LDAP_BACK_CFG_REBIND:
+                       mc->mc_flags &= ~LDAP_BACK_F_SAVECRED;
+                       break;
+
+               case LDAP_BACK_CFG_TIMEOUT:
+                       for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+                               mc->mc_timeout[ i ] = 0;
+                       }
+                       break;
+
+               case LDAP_BACK_CFG_VERSION:
+                       mc->mc_version = 0;
+                       break;
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+               case LDAP_BACK_CFG_ST_REQUEST:
+                       mc->mc_flags &= ~LDAP_BACK_F_ST_REQUEST;
+                       break;
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+               case LDAP_BACK_CFG_T_F:
+                       mc->mc_flags &= ~LDAP_BACK_F_T_F_MASK2;
+                       break;
+
+               case LDAP_BACK_CFG_TLS:
+                       mc->mc_flags &= ~LDAP_BACK_F_TLS_MASK;
+                       if ( mt )
+                               bindconf_free( &mt->mt_tls );
+                       break;
+
+               /* target attrs */
+               case LDAP_BACK_CFG_URI:
+                       if ( mt->mt_uri ) {
+                               ch_free( mt->mt_uri );
+                               mt->mt_uri = NULL;
+                       }
+                       /* FIXME: should have a way to close all cached
+                        * connections associated with this target.
+                        */
+                       break;
+
+               case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: {
+                       BerVarray *bvp;
+
+                       bvp = &mt->mt_idassert_authz;
+                       if ( c->valx < 0 ) {
+                               if ( *bvp != NULL ) {
+                                       ber_bvarray_free( *bvp );
+                                       *bvp = NULL;
+                               }
+
+                       } else {
+                               if ( *bvp == NULL ) {
+                                       rc = 1;
+                                       break;
+                               }
+
+                               for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ )
+                                       ;
+
+                               if ( i >= c->valx ) {
+                                       rc = 1;
+                                       break;
+                               }
+                               ber_memfree( ((*bvp)[ c->valx ]).bv_val );
+                               for ( i = c->valx; !BER_BVISNULL( &((*bvp)[ i + 1 ]) ); i++ ) {
+                                       (*bvp)[ i ] = (*bvp)[ i + 1 ];
+                               }
+                               BER_BVZERO( &((*bvp)[ i ]) );
+                       }
+                       } break;
+
+               case LDAP_BACK_CFG_IDASSERT_BIND:
+                       bindconf_free( &mt->mt_idassert.si_bc );
+                       memset( &mt->mt_idassert, 0, sizeof( slap_idassert_t ) );
+                       break;
+
+               case LDAP_BACK_CFG_SUFFIXM:     /* unused */
+               case LDAP_BACK_CFG_REWRITE:
+                       if ( mt->mt_rwmap.rwm_bva_rewrite ) {
+                               ber_bvarray_free( mt->mt_rwmap.rwm_bva_rewrite );
+                               mt->mt_rwmap.rwm_bva_rewrite = NULL;
+                       }
+                       if ( mt->mt_rwmap.rwm_rw )
+                               rewrite_info_delete( &mt->mt_rwmap.rwm_rw );
+                       break;
+
+               case LDAP_BACK_CFG_MAP:
+                       if ( mt->mt_rwmap.rwm_bva_map ) {
+                               ber_bvarray_free( mt->mt_rwmap.rwm_bva_map );
+                               mt->mt_rwmap.rwm_bva_map = NULL;
+                       }
+                       asyncmeta_back_map_free( &mt->mt_rwmap.rwm_oc );
+                       asyncmeta_back_map_free( &mt->mt_rwmap.rwm_at );
+                       mt->mt_rwmap.rwm_oc.drop_missing = 0;
+                       mt->mt_rwmap.rwm_at.drop_missing = 0;
+                       break;
+
+               case LDAP_BACK_CFG_SUBTREE_EX:
+               case LDAP_BACK_CFG_SUBTREE_IN:
+                       /* can only be one of exclude or include */
+                       if (( c->type == LDAP_BACK_CFG_SUBTREE_EX ) ^ mt->mt_subtree_exclude ) {
+                               rc = 1;
+                               break;
+                       }
+                       if ( c->valx < 0 ) {
+                               asyncmeta_subtree_destroy( mt->mt_subtree );
+                               mt->mt_subtree = NULL;
+                       } else {
+                               a_metasubtree_t *ms, **mprev;
+                               for (i=0, mprev = &mt->mt_subtree, ms = *mprev; ms; ms = *mprev) {
+                                       if ( i == c->valx ) {
+                                               *mprev = ms->ms_next;
+                                               asyncmeta_subtree_free( ms );
+                                               break;
+                                       }
+                                       i++;
+                                       mprev = &ms->ms_next;
+                               }
+                               if ( i != c->valx )
+                                       rc = 1;
+                       }
+                       break;
+
+               case LDAP_BACK_CFG_FILTER:
+                       if ( c->valx < 0 ) {
+                               asyncmeta_filter_destroy( mt->mt_filter );
+                               mt->mt_filter = NULL;
+                       } else {
+                               metafilter_t *mf, **mprev;
+                               for (i=0, mprev = &mt->mt_filter, mf = *mprev; mf; mf = *mprev) {
+                                       if ( i == c->valx ) {
+                                               *mprev = mf->mf_next;
+                                               asyncmeta_filter_free( mf );
+                                               break;
+                                       }
+                                       i++;
+                                       mprev = &mf->mf_next;
+                               }
+                               if ( i != c->valx )
+                                       rc = 1;
+                       }
+                       break;
+               case LDAP_BACK_CFG_MAX_PENDING_OPS:
+                       mi->mi_max_pending_ops = 0;
+                       break;
+
+               case LDAP_BACK_CFG_MAX_TARGET_CONNS:
+                       mi->mi_max_target_conns = 0;
+                       break;
+
+               case LDAP_BACK_CFG_MAX_TIMEOUT_OPS:
+                       mi->mi_max_timeout_ops = 0;
+                       break;
+
+               case LDAP_BACK_CFG_KEEPALIVE:
+                       mt->mt_tls.sb_keepalive.sk_idle = 0;
+                       mt->mt_tls.sb_keepalive.sk_probes = 0;
+                       mt->mt_tls.sb_keepalive.sk_interval = 0;
+                       break;
+
+               default:
+                       rc = 1;
+                       break;
+               }
+
+               return rc;
+       }
+
+       if ( c->op == SLAP_CONFIG_ADD ) {
+               if ( c->type >= LDAP_BACK_CFG_LAST_BASE ) {
+                       /* exclude CFG_URI from this check */
+                       if ( c->type > LDAP_BACK_CFG_LAST_BOTH ) {
+                               if ( !mi->mi_ntargets ) {
+                                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                               "need \"uri\" directive first" );
+                                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                                       return 1;
+                               }
+                       }
+                       if ( mi->mi_ntargets ) {
+                               mt = mi->mi_targets[ mi->mi_ntargets-1 ];
+                               mc = &mt->mt_mc;
+                       } else {
+                               mt = NULL;
+                               mc = &mi->mi_mc;
+                       }
+               }
+       } else {
+               if ( c->table == Cft_Database ) {
+                       mt = NULL;
+                       mc = &mi->mi_mc;
+               } else {
+                       mt = c->ca_private;
+                       if ( mt )
+                               mc = &mt->mt_mc;
+                       else
+                               mc = NULL;
+               }
+       }
+
+       switch( c->type ) {
+       case LDAP_BACK_CFG_URI: {
+               LDAPURLDesc     *ludp;
+               struct berval   dn;
+               int             j;
+
+               char            **uris = NULL;
+
+               if ( c->be->be_nsuffix == NULL ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "the suffix must be defined before any target" );
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return 1;
+               }
+
+               i = mi->mi_ntargets++;
+
+               mi->mi_targets = ( a_metatarget_t ** )ch_realloc( mi->mi_targets,
+                       sizeof( a_metatarget_t * ) * mi->mi_ntargets );
+               if ( mi->mi_targets == NULL ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "out of memory while storing server name"
+                               " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+                               c->argv[0] );
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return 1;
+               }
+
+               if ( asyncmeta_back_new_target( &mi->mi_targets[ i ] ) != 0 ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "unable to init server"
+                               " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+                               c->argv[0] );
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return 1;
+               }
+
+               mt = mi->mi_targets[ i ];
+
+               mt->mt_rebind_f = mi->mi_rebind_f;
+               mt->mt_urllist_f = mi->mi_urllist_f;
+               mt->mt_urllist_p = mt;
+
+               if ( META_BACK_QUARANTINE( mi ) ) {
+                       ldap_pvt_thread_mutex_init( &mt->mt_quarantine_mutex );
+               }
+               mt->mt_mc = mi->mi_mc;
+
+               for ( j = 1; j < c->argc; j++ ) {
+                       char    **tmpuris = ldap_str2charray( c->argv[ j ], "\t" );
+
+                       if ( tmpuris == NULL ) {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                       "unable to parse URIs #%d"
+                                       " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+                                       j-1, c->argv[0] );
+                               Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                               return 1;
+                       }
+
+                       if ( j == 1 ) {
+                               uris = tmpuris;
+
+                       } else {
+                               ldap_charray_merge( &uris, tmpuris );
+                               ldap_charray_free( tmpuris );
+                       }
+               }
+
+               for ( j = 0; uris[ j ] != NULL; j++ ) {
+                       char *tmpuri = NULL;
+
+                       /*
+                        * uri MUST be legal!
+                        */
+                       if ( ldap_url_parselist_ext( &ludp, uris[ j ], "\t",
+                                       LDAP_PVT_URL_PARSE_NONE ) != LDAP_SUCCESS
+                               || ludp->lud_next != NULL )
+                       {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                       "unable to parse URI #%d"
+                                       " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+                                       j-1, c->argv[0] );
+                               Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                               ldap_charray_free( uris );
+                               return 1;
+                       }
+
+                       if ( j == 0 ) {
+
+                               /*
+                                * uri MUST have the <dn> part!
+                                */
+                               if ( ludp->lud_dn == NULL ) {
+                                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                               "missing <naming context> "
+                                               " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+                                               c->argv[0] );
+                                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                                       ldap_free_urllist( ludp );
+                                       ldap_charray_free( uris );
+                                       return 1;
+                               }
+
+                               /*
+                                * copies and stores uri and suffix
+                                */
+                               ber_str2bv( ludp->lud_dn, 0, 0, &dn );
+                               rc = dnPrettyNormal( NULL, &dn, &mt->mt_psuffix,
+                                       &mt->mt_nsuffix, NULL );
+                               if ( rc != LDAP_SUCCESS ) {
+                                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                               "target DN is invalid \"%s\"",
+                                               c->argv[1] );
+                                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                                       ldap_free_urllist( ludp );
+                                       ldap_charray_free( uris );
+                                       return( 1 );
+                               }
+
+                               ludp->lud_dn[ 0 ] = '\0';
+
+                               switch ( ludp->lud_scope ) {
+                               case LDAP_SCOPE_DEFAULT:
+                                       mt->mt_scope = LDAP_SCOPE_SUBTREE;
+                                       break;
+
+                               case LDAP_SCOPE_SUBTREE:
+                               case LDAP_SCOPE_SUBORDINATE:
+                                       mt->mt_scope = ludp->lud_scope;
+                                       break;
+
+                               default:
+                                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                               "invalid scope for target \"%s\"",
+                                               c->argv[1] );
+                                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                                       ldap_free_urllist( ludp );
+                                       ldap_charray_free( uris );
+                                       return( 1 );
+                               }
+
+                       } else {
+                               /* check all, to apply the scope check on the first one */
+                               if ( ludp->lud_dn != NULL && ludp->lud_dn[ 0 ] != '\0' ) {
+                                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                               "multiple URIs must have no DN part" );
+                                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                                       ldap_free_urllist( ludp );
+                                       ldap_charray_free( uris );
+                                       return( 1 );
+
+                               }
+                       }
+
+                       tmpuri = ldap_url_list2urls( ludp );
+                       ldap_free_urllist( ludp );
+                       if ( tmpuri == NULL ) {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg ), "no memory?" );
+                               Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                               ldap_charray_free( uris );
+                               return( 1 );
+                       }
+                       ldap_memfree( uris[ j ] );
+                       uris[ j ] = tmpuri;
+               }
+
+               mt->mt_uri = ldap_charray2str( uris, " " );
+               ldap_charray_free( uris );
+               if ( mt->mt_uri == NULL) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ), "no memory?" );
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return( 1 );
+               }
+
+               /*
+                * uri MUST be a branch of suffix!
+                */
+               for ( j = 0; !BER_BVISNULL( &c->be->be_nsuffix[ j ] ); j++ ) {
+                       if ( dnIsSuffix( &mt->mt_nsuffix, &c->be->be_nsuffix[ j ] ) ) {
+                               break;
+                       }
+               }
+
+               if ( BER_BVISNULL( &c->be->be_nsuffix[ j ] ) ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "<naming context> of URI must be within the naming context of this database." );
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return 1;
+               }
+               c->ca_private = mt;
+               c->cleanup = asyncmeta_cf_cleanup;
+       } break;
+       case LDAP_BACK_CFG_SUBTREE_EX:
+       case LDAP_BACK_CFG_SUBTREE_IN:
+       /* subtree-exclude */
+               if ( asyncmeta_subtree_config( mt, c )) {
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return 1;
+               }
+               break;
+
+       case LDAP_BACK_CFG_FILTER: {
+               metafilter_t *mf, **m2;
+               mf = ch_malloc( sizeof( metafilter_t ));
+               rc = regcomp( &mf->mf_regex, c->argv[1], REG_EXTENDED );
+               if ( rc ) {
+                       char regerr[ SLAP_TEXT_BUFLEN ];
+                       regerror( rc, &mf->mf_regex, regerr, sizeof(regerr) );
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "regular expression \"%s\" bad because of %s",
+                               c->argv[1], regerr );
+                       ch_free( mf );
+                       return 1;
+               }
+               ber_str2bv( c->argv[1], 0, 1, &mf->mf_regex_pattern );
+               for ( m2 = &mt->mt_filter; *m2; m2 = &(*m2)->mf_next )
+                       ;
+               *m2 = mf;
+       } break;
+       case LDAP_BACK_CFG_MAX_PENDING_OPS:
+               if (c->value_int < 0) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                 "max-pending-ops invalid value %d",
+                                 c->value_int);
+                       return 1;
+               }
+               mi->mi_max_pending_ops = c->value_int;
+               break;
+       case LDAP_BACK_CFG_MAX_TARGET_CONNS:
+       {
+               int msc_num, i;
+               if (c->value_int < 0) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                 "max-target-conns invalid value %d",
+                                 c->value_int);
+                       return 1;
+               }
+               mi->mi_max_target_conns = c->value_int;
+       }
+               break;
+       case LDAP_BACK_CFG_MAX_TIMEOUT_OPS:
+               if (c->value_int < 0) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                 "max-timeout-ops invalid value %d",
+                                 c->value_int);
+                       return 1;
+               }
+               mi->mi_max_timeout_ops = c->value_int;
+               break;
+
+       case LDAP_BACK_CFG_DEFAULT_T:
+       /* default target directive */
+               i = mi->mi_ntargets - 1;
+
+               if ( c->argc == 1 ) {
+                       if ( i < 0 ) {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                       "\"%s\" alone must be inside a \"uri\" directive",
+                                       c->argv[0] );
+                               Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                               return 1;
+                       }
+                       mi->mi_defaulttarget = i;
+
+               } else {
+                       if ( strcasecmp( c->argv[ 1 ], "none" ) == 0 ) {
+                               if ( i >= 0 ) {
+                                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                               "\"%s none\" should go before uri definitions",
+                                               c->argv[0] );
+                                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                               }
+                               mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE;
+
+                       } else {
+
+                               if ( lutil_atoi( &mi->mi_defaulttarget, c->argv[ 1 ] ) != 0
+                                       || mi->mi_defaulttarget < 0
+                                       || mi->mi_defaulttarget >= i - 1 )
+                               {
+                                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                               "illegal target number %d",
+                                               mi->mi_defaulttarget );
+                                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                                       return 1;
+                               }
+                       }
+               }
+               break;
+
+       case LDAP_BACK_CFG_DNCACHE_TTL:
+       /* ttl of dn cache */
+               if ( strcasecmp( c->argv[ 1 ], "forever" ) == 0 ) {
+                       mi->mi_cache.ttl = META_DNCACHE_FOREVER;
+
+               } else if ( strcasecmp( c->argv[ 1 ], "disabled" ) == 0 ) {
+                       mi->mi_cache.ttl = META_DNCACHE_DISABLED;
+
+               } else {
+                       unsigned long   t;
+
+                       if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                       "unable to parse dncache ttl \"%s\"",
+                                       c->argv[ 1 ] );
+                               Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                               return 1;
+                       }
+                       mi->mi_cache.ttl = (time_t)t;
+               }
+               break;
+
+       case LDAP_BACK_CFG_NETWORK_TIMEOUT: {
+       /* network timeout when connecting to ldap servers */
+               unsigned long t;
+
+               if ( lutil_parse_time( c->argv[ 1 ], &t ) ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "unable to parse network timeout \"%s\"",
+                               c->argv[ 1 ] );
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return 1;
+               }
+               mc->mc_network_timeout = (time_t)t;
+               } break;
+
+       case LDAP_BACK_CFG_IDLE_TIMEOUT: {
+       /* idle timeout when connecting to ldap servers */
+               unsigned long   t;
+
+               if ( lutil_parse_time( c->argv[ 1 ], &t ) ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "unable to parse idle timeout \"%s\"",
+                               c->argv[ 1 ] );
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return 1;
+
+               }
+               mi->mi_idle_timeout = (time_t)t;
+               } break;
+
+       case LDAP_BACK_CFG_BIND_TIMEOUT:
+       /* bind timeout when connecting to ldap servers */
+               mc->mc_bind_timeout.tv_sec = c->value_ulong/1000000;
+               mc->mc_bind_timeout.tv_usec = c->value_ulong%1000000;
+               break;
+
+       case LDAP_BACK_CFG_ACL_AUTHCDN:
+       /* name to use for meta_back_group */
+               if ( strcasecmp( c->argv[ 0 ], "binddn" ) == 0 ) {
+                       Debug( LDAP_DEBUG_ANY, "%s: "
+                               "\"binddn\" statement is deprecated; "
+                               "use \"acl-authcDN\" instead\n",
+                               c->log, 0, 0 );
+                       /* FIXME: some day we'll need to throw an error */
+               }
+
+               ber_memfree_x( c->value_dn.bv_val, NULL );
+               mt->mt_binddn = c->value_ndn;
+               BER_BVZERO( &c->value_dn );
+               BER_BVZERO( &c->value_ndn );
+               break;
+
+       case LDAP_BACK_CFG_ACL_PASSWD:
+       /* password to use for meta_back_group */
+               if ( strcasecmp( c->argv[ 0 ], "bindpw" ) == 0 ) {
+                       Debug( LDAP_DEBUG_ANY, "%s "
+                               "\"bindpw\" statement is deprecated; "
+                               "use \"acl-passwd\" instead\n",
+                               c->log, 0, 0 );
+                       /* FIXME: some day we'll need to throw an error */
+               }
+
+               ber_str2bv( c->argv[ 1 ], 0L, 1, &mt->mt_bindpw );
+               break;
+
+       case LDAP_BACK_CFG_REBIND:
+       /* save bind creds for referral rebinds? */
+               if ( c->argc == 1 || c->value_int ) {
+                       mc->mc_flags |= LDAP_BACK_F_SAVECRED;
+               } else {
+                       mc->mc_flags &= ~LDAP_BACK_F_SAVECRED;
+               }
+               break;
+
+       case LDAP_BACK_CFG_CHASE:
+               if ( c->argc == 1 || c->value_int ) {
+                       mc->mc_flags |= LDAP_BACK_F_CHASE_REFERRALS;
+               } else {
+                       mc->mc_flags &= ~LDAP_BACK_F_CHASE_REFERRALS;
+               }
+               break;
+
+       case LDAP_BACK_CFG_TLS:
+               i = verb_to_mask( c->argv[1], tls_mode );
+               if ( BER_BVISNULL( &tls_mode[i].word ) ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "%s unknown argument \"%s\"",
+                               c->argv[0], c->argv[1] );
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return 1;
+               }
+               mc->mc_flags &= ~LDAP_BACK_F_TLS_MASK;
+               mc->mc_flags |= tls_mode[i].mask;
+
+               if ( c->argc > 2 ) {
+                       if ( c->op == SLAP_CONFIG_ADD && mi->mi_ntargets == 0 ) {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                       "need \"uri\" directive first" );
+                               Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                               return 1;
+                       }
+
+                       for ( i = 2; i < c->argc; i++ ) {
+                               if ( bindconf_tls_parse( c->argv[i], &mt->mt_tls ))
+                                       return 1;
+                       }
+                       bindconf_tls_defaults( &mt->mt_tls );
+               }
+               break;
+
+       case LDAP_BACK_CFG_T_F:
+               i = verb_to_mask( c->argv[1], t_f_mode );
+               if ( BER_BVISNULL( &t_f_mode[i].word ) ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "%s unknown argument \"%s\"",
+                               c->argv[0], c->argv[1] );
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return 1;
+               }
+               mc->mc_flags &= ~LDAP_BACK_F_T_F_MASK2;
+               mc->mc_flags |= t_f_mode[i].mask;
+               break;
+
+       case LDAP_BACK_CFG_ONERR:
+       /* onerr? */
+               i = verb_to_mask( c->argv[1], onerr_mode );
+               if ( BER_BVISNULL( &onerr_mode[i].word ) ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "%s unknown argument \"%s\"",
+                               c->argv[0], c->argv[1] );
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return 1;
+               }
+               mi->mi_flags &= ~META_BACK_F_ONERR_MASK;
+               mi->mi_flags |= onerr_mode[i].mask;
+               break;
+
+       case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER:
+       /* bind-defer? */
+               if ( c->argc == 1 || c->value_int ) {
+                       mi->mi_flags |= META_BACK_F_DEFER_ROOTDN_BIND;
+               } else {
+                       mi->mi_flags &= ~META_BACK_F_DEFER_ROOTDN_BIND;
+               }
+               break;
+
+       case LDAP_BACK_CFG_CONNPOOLMAX:
+       /* privileged connections pool max size ? */
+               if ( mi->mi_ntargets > 0 ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "\"%s\" must appear before target definitions",
+                               c->argv[0] );
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return( 1 );
+               }
+
+               if ( c->value_int < LDAP_BACK_CONN_PRIV_MIN
+                       || c->value_int > LDAP_BACK_CONN_PRIV_MAX )
+               {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "invalid max size " "of privileged "
+                               "connections pool \"%s\" "
+                               "in \"conn-pool-max <n> "
+                               "(must be between %d and %d)\"",
+                               c->argv[ 1 ],
+                               LDAP_BACK_CONN_PRIV_MIN,
+                               LDAP_BACK_CONN_PRIV_MAX );
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return 1;
+               }
+               mi->mi_conn_priv_max = c->value_int;
+               break;
+
+       case LDAP_BACK_CFG_CANCEL:
+               i = verb_to_mask( c->argv[1], cancel_mode );
+               if ( BER_BVISNULL( &cancel_mode[i].word ) ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "%s unknown argument \"%s\"",
+                               c->argv[0], c->argv[1] );
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return 1;
+               }
+               mc->mc_flags &= ~LDAP_BACK_F_CANCEL_MASK2;
+               mc->mc_flags |= cancel_mode[i].mask;
+               break;
+
+       case LDAP_BACK_CFG_TIMEOUT:
+               for ( i = 1; i < c->argc; i++ ) {
+                       if ( isdigit( (unsigned char) c->argv[ i ][ 0 ] ) ) {
+                               int             j;
+                               unsigned        u;
+
+                               if ( lutil_atoux( &u, c->argv[ i ], 0 ) != 0 ) {
+                                       snprintf( c->cr_msg, sizeof( c->cr_msg),
+                                               "unable to parse timeout \"%s\"",
+                                               c->argv[ i ] );
+                                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                                       return 1;
+                               }
+
+                               for ( j = 0; j < SLAP_OP_LAST; j++ ) {
+                                       mc->mc_timeout[ j ] = u;
+                               }
+
+                               continue;
+                       }
+
+                       if ( slap_cf_aux_table_parse( c->argv[ i ], mc->mc_timeout, timeout_table, "slapd-meta timeout" ) ) {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg),
+                                       "unable to parse timeout \"%s\"",
+                                       c->argv[ i ] );
+                               Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                               return 1;
+                       }
+               }
+               break;
+
+       case LDAP_BACK_CFG_IDASSERT_BIND:
+       /* idassert-bind */
+               rc = mi->mi_ldap_extra->idassert_parse( c, &mt->mt_idassert );
+               break;
+
+       case LDAP_BACK_CFG_IDASSERT_AUTHZFROM:
+       /* idassert-authzFrom */
+               rc = mi->mi_ldap_extra->idassert_authzfrom_parse( c, &mt->mt_idassert );
+               break;
+
+       case LDAP_BACK_CFG_QUARANTINE:
+       /* quarantine */
+               if ( META_BACK_CMN_QUARANTINE( mc ) )
+               {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "quarantine already defined" );
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return 1;
+               }
+
+               if ( mt ) {
+                       mc->mc_quarantine.ri_interval = NULL;
+                       mc->mc_quarantine.ri_num = NULL;
+                       if ( !META_BACK_QUARANTINE( mi ) ) {
+                               ldap_pvt_thread_mutex_init( &mt->mt_quarantine_mutex );
+                       }
+               }
+
+               if ( mi->mi_ldap_extra->retry_info_parse( c->argv[ 1 ], &mc->mc_quarantine, c->cr_msg, sizeof( c->cr_msg ) ) ) {
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return 1;
+               }
+
+               mc->mc_flags |= LDAP_BACK_F_QUARANTINE;
+               break;
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+       case LDAP_BACK_CFG_ST_REQUEST:
+       /* session tracking request */
+               if ( c->value_int ) {
+                       mc->mc_flags |= LDAP_BACK_F_ST_REQUEST;
+               } else {
+                       mc->mc_flags &= ~LDAP_BACK_F_ST_REQUEST;
+               }
+               break;
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+       case LDAP_BACK_CFG_SUFFIXM:     /* FALLTHRU */
+       case LDAP_BACK_CFG_REWRITE: {
+       /* rewrite stuff ... */
+               ConfigArgs ca = { 0 };
+               char *line, **argv;
+               struct rewrite_info *rwi;
+               int cnt = 0, argc, ix = c->valx;
+
+               if ( mt->mt_rwmap.rwm_bva_rewrite ) {
+                       for ( ; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_rewrite[ cnt ] ); cnt++ )
+                               /* count */ ;
+               }
+
+               if ( ix >= cnt || ix < 0 ) {
+                       ix = cnt;
+               } else {
+                       rwi = mt->mt_rwmap.rwm_rw;
+
+                       mt->mt_rwmap.rwm_rw = NULL;
+                       rc = asyncmeta_rwi_init( &mt->mt_rwmap.rwm_rw );
+
+                       /* re-parse all rewrite rules, up to the one
+                        * that needs to be added */
+                       ca.be = c->be;
+                       ca.fname = c->fname;
+                       ca.lineno = c->lineno;
+                       for ( i = 0; i < ix; i++ ) {
+                               ca.line = mt->mt_rwmap.rwm_bva_rewrite[ i ].bv_val;
+                               ca.argc = 0;
+                               config_fp_parse_line( &ca );
+
+                               if ( !strcasecmp( ca.argv[0], "suffixmassage" )) {
+                                       rc = asyncmeta_suffixm_config( &ca, ca.argc, ca.argv, mt );
+                               } else {
+                                       rc = rewrite_parse( mt->mt_rwmap.rwm_rw,
+                                               c->fname, c->lineno, ca.argc, ca.argv );
+                               }
+                               assert( rc == 0 );
+                               ch_free( ca.argv );
+                               ch_free( ca.tline );
+                       }
+               }
+               argc = c->argc;
+               argv = c->argv;
+               if ( c->op != SLAP_CONFIG_ADD ) {
+                       argc--;
+                       argv++;
+               }
+               /* add the new rule */
+               if ( !strcasecmp( argv[0], "suffixmassage" )) {
+                       rc = asyncmeta_suffixm_config( c, argc, argv, mt );
+               } else {
+                       rc = rewrite_parse( mt->mt_rwmap.rwm_rw,
+                                               c->fname, c->lineno, argc, argv );
+               }
+               if ( rc ) {
+                       if ( ix < cnt ) {
+                               rewrite_info_delete( &mt->mt_rwmap.rwm_rw );
+                               mt->mt_rwmap.rwm_rw = rwi;
+                       }
+                       return 1;
+               }
+               if ( ix < cnt ) {
+                       for ( ; i < cnt; i++ ) {
+                               ca.line = mt->mt_rwmap.rwm_bva_rewrite[ i ].bv_val;
+                               ca.argc = 0;
+                               config_fp_parse_line( &ca );
+
+                               if ( !strcasecmp( ca.argv[0], "suffixmassage" )) {
+                                       rc = asyncmeta_suffixm_config( &ca, ca.argc, ca.argv, mt );
+                               } else {
+                                       rc = rewrite_parse( mt->mt_rwmap.rwm_rw,
+                                               c->fname, c->lineno, ca.argc, argv );
+                               }
+                               assert( rc == 0 );
+                               ch_free( ca.argv );
+                               ch_free( ca.tline );
+                       }
+               }
+
+               /* save the rule info */
+               line = ldap_charray2str( argv, "\" \"" );
+               if ( line != NULL ) {
+                       struct berval bv;
+                       int len = strlen( argv[ 0 ] );
+
+                       ber_str2bv( line, 0, 0, &bv );
+                       AC_MEMCPY( &bv.bv_val[ len ], &bv.bv_val[ len + 1 ],
+                               bv.bv_len - ( len + 1 ));
+                       bv.bv_val[ bv.bv_len - 1] = '"';
+                       ber_bvarray_add( &mt->mt_rwmap.rwm_bva_rewrite, &bv );
+                       /* move it to the right slot */
+                       if ( ix < cnt ) {
+                               for ( i=cnt; i>ix; i-- )
+                                       mt->mt_rwmap.rwm_bva_rewrite[i+1] = mt->mt_rwmap.rwm_bva_rewrite[i];
+                               mt->mt_rwmap.rwm_bva_rewrite[i] = bv;
+
+                               /* destroy old rules */
+                               rewrite_info_delete( &rwi );
+                       }
+               }
+               } break;
+
+       case LDAP_BACK_CFG_MAP: {
+       /* objectclass/attribute mapping */
+               ConfigArgs ca = { 0 };
+               char *argv[5];
+               struct ldapmap rwm_oc;
+               struct ldapmap rwm_at;
+               int cnt = 0, ix = c->valx;
+
+               if ( mt->mt_rwmap.rwm_bva_map ) {
+                       for ( ; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_map[ cnt ] ); cnt++ )
+                               /* count */ ;
+               }
+
+               if ( ix >= cnt || ix < 0 ) {
+                       ix = cnt;
+               } else {
+                       rwm_oc = mt->mt_rwmap.rwm_oc;
+                       rwm_at = mt->mt_rwmap.rwm_at;
+
+                       memset( &mt->mt_rwmap.rwm_oc, 0, sizeof( mt->mt_rwmap.rwm_oc ) );
+                       memset( &mt->mt_rwmap.rwm_at, 0, sizeof( mt->mt_rwmap.rwm_at ) );
+
+                       /* re-parse all mappings, up to the one
+                        * that needs to be added */
+                       argv[0] = c->argv[0];
+                       ca.fname = c->fname;
+                       ca.lineno = c->lineno;
+                       for ( i = 0; i < ix; i++ ) {
+                               ca.line = mt->mt_rwmap.rwm_bva_map[ i ].bv_val;
+                               ca.argc = 0;
+                               config_fp_parse_line( &ca );
+
+                               argv[1] = ca.argv[0];
+                               argv[2] = ca.argv[1];
+                               argv[3] = ca.argv[2];
+                               argv[4] = ca.argv[3];
+                               ch_free( ca.argv );
+                               ca.argv = argv;
+                               ca.argc++;
+                               rc = asyncmeta_map_config( &ca, &mt->mt_rwmap.rwm_oc,
+                                       &mt->mt_rwmap.rwm_at );
+
+                               ch_free( ca.tline );
+                               ca.tline = NULL;
+                               ca.argv = NULL;
+
+                               /* in case of failure, restore
+                                * the existing mapping */
+                               if ( rc ) {
+                                       goto map_fail;
+                               }
+                       }
+               }
+               /* add the new mapping */
+               rc = asyncmeta_map_config( c, &mt->mt_rwmap.rwm_oc,
+                                       &mt->mt_rwmap.rwm_at );
+               if ( rc ) {
+                       goto map_fail;
+               }
+
+               if ( ix < cnt ) {
+                       for ( ; i<cnt ; cnt++ ) {
+                               ca.line = mt->mt_rwmap.rwm_bva_map[ i ].bv_val;
+                               ca.argc = 0;
+                               config_fp_parse_line( &ca );
+
+                               argv[1] = ca.argv[0];
+                               argv[2] = ca.argv[1];
+                               argv[3] = ca.argv[2];
+                               argv[4] = ca.argv[3];
+
+                               ch_free( ca.argv );
+                               ca.argv = argv;
+                               ca.argc++;
+                               rc = asyncmeta_map_config( &ca, &mt->mt_rwmap.rwm_oc,
+                                       &mt->mt_rwmap.rwm_at );
+
+                               ch_free( ca.tline );
+                               ca.tline = NULL;
+                               ca.argv = NULL;
+
+                               /* in case of failure, restore
+                                * the existing mapping */
+                               if ( rc ) {
+                                       goto map_fail;
+                               }
+                       }
+               }
+
+               /* save the map info */
+               argv[0] = ldap_charray2str( &c->argv[ 1 ], " " );
+               if ( argv[0] != NULL ) {
+                       struct berval bv;
+                       ber_str2bv( argv[0], 0, 0, &bv );
+                       ber_bvarray_add( &mt->mt_rwmap.rwm_bva_map, &bv );
+                       /* move it to the right slot */
+                       if ( ix < cnt ) {
+                               for ( i=cnt; i>ix; i-- )
+                                       mt->mt_rwmap.rwm_bva_map[i+1] = mt->mt_rwmap.rwm_bva_map[i];
+                               mt->mt_rwmap.rwm_bva_map[i] = bv;
+
+                               /* destroy old mapping */
+                               asyncmeta_back_map_free( &rwm_oc );
+                               asyncmeta_back_map_free( &rwm_at );
+                       }
+               }
+               break;
+
+map_fail:;
+               if ( ix < cnt ) {
+                       asyncmeta_back_map_free( &mt->mt_rwmap.rwm_oc );
+                       asyncmeta_back_map_free( &mt->mt_rwmap.rwm_at );
+                       mt->mt_rwmap.rwm_oc = rwm_oc;
+                       mt->mt_rwmap.rwm_at = rwm_at;
+               }
+               } break;
+
+       case LDAP_BACK_CFG_NRETRIES: {
+               int             nretries = META_RETRY_UNDEFINED;
+
+               if ( strcasecmp( c->argv[ 1 ], "forever" ) == 0 ) {
+                       nretries = META_RETRY_FOREVER;
+
+               } else if ( strcasecmp( c->argv[ 1 ], "never" ) == 0 ) {
+                       nretries = META_RETRY_NEVER;
+
+               } else {
+                       if ( lutil_atoi( &nretries, c->argv[ 1 ] ) != 0 ) {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                       "unable to parse nretries {never|forever|<retries>}: \"%s\"",
+                                       c->argv[ 1 ] );
+                               Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                               return 1;
+                       }
+               }
+
+               mc->mc_nretries = nretries;
+               } break;
+
+       case LDAP_BACK_CFG_VERSION:
+               if ( c->value_int != 0 && ( c->value_int < LDAP_VERSION_MIN || c->value_int > LDAP_VERSION_MAX ) ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "unsupported protocol version \"%s\"",
+                               c->argv[ 1 ] );
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return 1;
+               }
+               mc->mc_version = c->value_int;
+               break;
+
+       case LDAP_BACK_CFG_NOREFS:
+       /* do not return search references */
+               if ( c->value_int ) {
+                       mc->mc_flags |= LDAP_BACK_F_NOREFS;
+               } else {
+                       mc->mc_flags &= ~LDAP_BACK_F_NOREFS;
+               }
+               break;
+
+       case LDAP_BACK_CFG_NOUNDEFFILTER:
+       /* do not propagate undefined search filters */
+               if ( c->value_int ) {
+                       mc->mc_flags |= LDAP_BACK_F_NOUNDEFFILTER;
+               } else {
+                       mc->mc_flags &= ~LDAP_BACK_F_NOUNDEFFILTER;
+               }
+               break;
+
+#ifdef SLAPD_META_CLIENT_PR
+       case LDAP_BACK_CFG_CLIENT_PR:
+               if ( strcasecmp( c->argv[ 1 ], "accept-unsolicited" ) == 0 ) {
+                       mc->mc_ps = META_CLIENT_PR_ACCEPT_UNSOLICITED;
+
+               } else if ( strcasecmp( c->argv[ 1 ], "disable" ) == 0 ) {
+                       mc->mc_ps = META_CLIENT_PR_DISABLE;
+
+               } else if ( lutil_atoi( &mc->mc_ps, c->argv[ 1 ] ) || mc->mc_ps < -1 ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "unable to parse client-pr {accept-unsolicited|disable|<size>}: \"%s\"",
+                               c->argv[ 1 ] );
+                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return( 1 );
+               }
+               break;
+#endif /* SLAPD_META_CLIENT_PR */
+
+       case LDAP_BACK_CFG_KEEPALIVE:
+               slap_keepalive_parse( ber_bvstrdup(c->argv[1]),
+                                &mt->mt_tls.sb_keepalive, 0, 0, 0);
+               break;
+
+       /* anything else */
+       default:
+               return SLAP_CONF_UNKNOWN;
+       }
+
+       return rc;
+}
+
+int
+asyncmeta_back_init_cf( BackendInfo *bi )
+{
+       int                     rc;
+       AttributeDescription    *ad = NULL;
+       const char              *text;
+
+       /* Make sure we don't exceed the bits reserved for userland */
+       config_check_userland( LDAP_BACK_CFG_LAST );
+
+       bi->bi_cf_ocs = a_metaocs;
+
+       rc = config_register_schema( a_metacfg, a_metaocs );
+       if ( rc ) {
+               return rc;
+       }
+
+       /* setup olcDbAclPasswd and olcDbIDAssertPasswd
+        * to be base64-encoded when written in LDIF form;
+        * basically, we don't care if it fails */
+       rc = slap_str2ad( "olcDbACLPasswd", &ad, &text );
+       if ( rc ) {
+               Debug( LDAP_DEBUG_ANY, "config_back_initialize: "
+                       "warning, unable to get \"olcDbACLPasswd\" "
+                       "attribute description: %d: %s\n",
+                       rc, text, 0 );
+       } else {
+               (void)ldif_must_b64_encode_register( ad->ad_cname.bv_val,
+                       ad->ad_type->sat_oid );
+       }
+
+       ad = NULL;
+       rc = slap_str2ad( "olcDbIDAssertPasswd", &ad, &text );
+       if ( rc ) {
+               Debug( LDAP_DEBUG_ANY, "config_back_initialize: "
+                       "warning, unable to get \"olcDbIDAssertPasswd\" "
+                       "attribute description: %d: %s\n",
+                       rc, text, 0 );
+       } else {
+               (void)ldif_must_b64_encode_register( ad->ad_cname.bv_val,
+                       ad->ad_type->sat_oid );
+       }
+
+       return 0;
+}
+
+static int
+asyncmeta_map_config(
+               ConfigArgs *c,
+               struct ldapmap  *oc_map,
+               struct ldapmap  *at_map )
+{
+       struct ldapmap          *map;
+       struct ldapmapping      *mapping;
+       char                    *src, *dst;
+       int                     is_oc = 0;
+
+       if ( strcasecmp( c->argv[ 1 ], "objectclass" ) == 0 ) {
+               map = oc_map;
+               is_oc = 1;
+
+       } else if ( strcasecmp( c->argv[ 1 ], "attribute" ) == 0 ) {
+               map = at_map;
+
+       } else {
+               snprintf( c->cr_msg, sizeof(c->cr_msg),
+                       "%s unknown argument \"%s\"",
+                       c->argv[0], c->argv[1] );
+               Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+               return 1;
+       }
+
+       if ( !is_oc && map->map == NULL ) {
+               /* only init if required */
+               asyncmeta_map_init( map, &mapping );
+       }
+
+       if ( strcmp( c->argv[ 2 ], "*" ) == 0 ) {
+               if ( c->argc < 4 || strcmp( c->argv[ 3 ], "*" ) == 0 ) {
+                       map->drop_missing = ( c->argc < 4 );
+                       goto success_return;
+               }
+               src = dst = c->argv[ 3 ];
+
+       } else if ( c->argc < 4 ) {
+               src = "";
+               dst = c->argv[ 2 ];
+
+       } else {
+               src = c->argv[ 2 ];
+               dst = ( strcmp( c->argv[ 3 ], "*" ) == 0 ? src : c->argv[ 3 ] );
+       }
+
+       if ( ( map == at_map )
+               && ( strcasecmp( src, "objectclass" ) == 0
+                       || strcasecmp( dst, "objectclass" ) == 0 ) )
+       {
+               snprintf( c->cr_msg, sizeof(c->cr_msg),
+                       "objectclass attribute cannot be mapped" );
+               Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+               return 1;
+       }
+
+       mapping = (struct ldapmapping *)ch_calloc( 2,
+               sizeof(struct ldapmapping) );
+       if ( mapping == NULL ) {
+               snprintf( c->cr_msg, sizeof(c->cr_msg),
+                       "out of memory" );
+               Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+               return 1;
+       }
+       ber_str2bv( src, 0, 1, &mapping[ 0 ].src );
+       ber_str2bv( dst, 0, 1, &mapping[ 0 ].dst );
+       mapping[ 1 ].src = mapping[ 0 ].dst;
+       mapping[ 1 ].dst = mapping[ 0 ].src;
+
+       /*
+        * schema check
+        */
+       if ( is_oc ) {
+               if ( src[ 0 ] != '\0' ) {
+                       if ( oc_bvfind( &mapping[ 0 ].src ) == NULL ) {
+                               Debug( LDAP_DEBUG_ANY,
+       "warning, source objectClass '%s' should be defined in schema\n",
+                                       c->log, src, 0 );
+
+                               /*
+                                * FIXME: this should become an err
+                                */
+                               goto error_return;
+                       }
+               }
+
+               if ( oc_bvfind( &mapping[ 0 ].dst ) == NULL ) {
+                       Debug( LDAP_DEBUG_ANY,
+       "warning, destination objectClass '%s' is not defined in schema\n",
+                               c->log, dst, 0 );
+               }
+       } else {
+               int                     rc;
+               const char              *text = NULL;
+               AttributeDescription    *ad = NULL;
+
+               if ( src[ 0 ] != '\0' ) {
+                       rc = slap_bv2ad( &mapping[ 0 ].src, &ad, &text );
+                       if ( rc != LDAP_SUCCESS ) {
+                               Debug( LDAP_DEBUG_ANY,
+       "warning, source attributeType '%s' should be defined in schema\n",
+                                       c->log, src, 0 );
+
+                               /*
+                                * FIXME: this should become an err
+                                */
+                               /*
+                                * we create a fake "proxied" ad
+                                * and add it here.
+                                */
+
+                               rc = slap_bv2undef_ad( &mapping[ 0 ].src,
+                                               &ad, &text, SLAP_AD_PROXIED );
+                               if ( rc != LDAP_SUCCESS ) {
+                                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                               "source attributeType \"%s\": %d (%s)",
+                                               src, rc, text ? text : "" );
+                                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                                       goto error_return;
+                               }
+                       }
+
+                       ad = NULL;
+               }
+
+               rc = slap_bv2ad( &mapping[ 0 ].dst, &ad, &text );
+               if ( rc != LDAP_SUCCESS ) {
+                       Debug( LDAP_DEBUG_ANY,
+       "warning, destination attributeType '%s' is not defined in schema\n",
+                               c->log, dst, 0 );
+
+                       /*
+                        * we create a fake "proxied" ad
+                        * and add it here.
+                        */
+
+                       rc = slap_bv2undef_ad( &mapping[ 0 ].dst,
+                                       &ad, &text, SLAP_AD_PROXIED );
+                       if ( rc != LDAP_SUCCESS ) {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                       "destination attributeType \"%s\": %d (%s)\n",
+                                       dst, rc, text ? text : "" );
+                               Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                               return 1;
+                       }
+               }
+       }
+
+       if ( (src[ 0 ] != '\0' && avl_find( map->map, (caddr_t)&mapping[ 0 ], asyncmeta_mapping_cmp ) != NULL)
+                       || avl_find( map->remap, (caddr_t)&mapping[ 1 ], asyncmeta_mapping_cmp ) != NULL)
+       {
+               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                       "duplicate mapping found." );
+               Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+               goto error_return;
+       }
+
+       if ( src[ 0 ] != '\0' ) {
+               avl_insert( &map->map, (caddr_t)&mapping[ 0 ],
+                                       asyncmeta_mapping_cmp, asyncmeta_mapping_dup );
+       }
+       avl_insert( &map->remap, (caddr_t)&mapping[ 1 ],
+                               asyncmeta_mapping_cmp, asyncmeta_mapping_dup );
+
+success_return:;
+       return 0;
+
+error_return:;
+       if ( mapping ) {
+               ch_free( mapping[ 0 ].src.bv_val );
+               ch_free( mapping[ 0 ].dst.bv_val );
+               ch_free( mapping );
+       }
+
+       return 1;
+}
+
+
+static char *
+suffix_massage_regexize( const char *s )
+{
+       char *res, *ptr;
+       const char *p, *r;
+       int i;
+
+       if ( s[ 0 ] == '\0' ) {
+               return ch_strdup( "^(.+)$" );
+       }
+
+       for ( i = 0, p = s;
+                       ( r = strchr( p, ',' ) ) != NULL;
+                       p = r + 1, i++ )
+               ;
+
+       res = ch_calloc( sizeof( char ),
+                       strlen( s )
+                       + STRLENOF( "((.+),)?" )
+                       + STRLENOF( "[ ]?" ) * i
+                       + STRLENOF( "$" ) + 1 );
+
+       ptr = lutil_strcopy( res, "((.+),)?" );
+       for ( i = 0, p = s;
+                       ( r = strchr( p, ',' ) ) != NULL;
+                       p = r + 1 , i++ ) {
+               ptr = lutil_strncopy( ptr, p, r - p + 1 );
+               ptr = lutil_strcopy( ptr, "[ ]?" );
+
+               if ( r[ 1 ] == ' ' ) {
+                       r++;
+               }
+       }
+       ptr = lutil_strcopy( ptr, p );
+       ptr[ 0 ] = '$';
+       ptr++;
+       ptr[ 0 ] = '\0';
+
+       return res;
+}
+
+static char *
+suffix_massage_patternize( const char *s, const char *p )
+{
+       ber_len_t       len;
+       char            *res, *ptr;
+
+       len = strlen( p );
+
+       if ( s[ 0 ] == '\0' ) {
+               len++;
+       }
+
+       res = ch_calloc( sizeof( char ), len + STRLENOF( "%1" ) + 1 );
+       if ( res == NULL ) {
+               return NULL;
+       }
+
+       ptr = lutil_strcopy( res, ( p[ 0 ] == '\0' ? "%2" : "%1" ) );
+       if ( s[ 0 ] == '\0' ) {
+               ptr[ 0 ] = ',';
+               ptr++;
+       }
+       lutil_strcopy( ptr, p );
+
+       return res;
+}
+
+int
+asyncmeta_suffix_massage_config(
+               struct rewrite_info *info,
+               struct berval *pvnc,
+               struct berval *nvnc,
+               struct berval *prnc,
+               struct berval *nrnc
+)
+{
+       char *rargv[ 5 ];
+       int line = 0;
+
+       rargv[ 0 ] = "rewriteEngine";
+       rargv[ 1 ] = "on";
+       rargv[ 2 ] = NULL;
+       rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+       rargv[ 0 ] = "rewriteContext";
+       rargv[ 1 ] = "default";
+       rargv[ 2 ] = NULL;
+       rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+       rargv[ 0 ] = "rewriteRule";
+       rargv[ 1 ] = suffix_massage_regexize( pvnc->bv_val );
+       rargv[ 2 ] = suffix_massage_patternize( pvnc->bv_val, prnc->bv_val );
+       rargv[ 3 ] = ":";
+       rargv[ 4 ] = NULL;
+       rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+       ch_free( rargv[ 1 ] );
+       ch_free( rargv[ 2 ] );
+
+       if ( BER_BVISEMPTY( pvnc ) ) {
+               rargv[ 0 ] = "rewriteRule";
+               rargv[ 1 ] = "^$";
+               rargv[ 2 ] = prnc->bv_val;
+               rargv[ 3 ] = ":";
+               rargv[ 4 ] = NULL;
+               rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+       }
+
+       rargv[ 0 ] = "rewriteContext";
+       rargv[ 1 ] = "searchEntryDN";
+       rargv[ 2 ] = NULL;
+       rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+       rargv[ 0 ] = "rewriteRule";
+       rargv[ 1 ] = suffix_massage_regexize( prnc->bv_val );
+       rargv[ 2 ] = suffix_massage_patternize( prnc->bv_val, pvnc->bv_val );
+       rargv[ 3 ] = ":";
+       rargv[ 4 ] = NULL;
+       rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+       ch_free( rargv[ 1 ] );
+       ch_free( rargv[ 2 ] );
+
+       if ( BER_BVISEMPTY( prnc ) ) {
+               rargv[ 0 ] = "rewriteRule";
+               rargv[ 1 ] = "^$";
+               rargv[ 2 ] = pvnc->bv_val;
+               rargv[ 3 ] = ":";
+               rargv[ 4 ] = NULL;
+               rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+       }
+
+       /* backward compatibility */
+       rargv[ 0 ] = "rewriteContext";
+       rargv[ 1 ] = "searchResult";
+       rargv[ 2 ] = "alias";
+       rargv[ 3 ] = "searchEntryDN";
+       rargv[ 4 ] = NULL;
+       rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+
+       rargv[ 0 ] = "rewriteContext";
+       rargv[ 1 ] = "matchedDN";
+       rargv[ 2 ] = "alias";
+       rargv[ 3 ] = "searchEntryDN";
+       rargv[ 4 ] = NULL;
+       rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+
+       rargv[ 0 ] = "rewriteContext";
+       rargv[ 1 ] = "searchAttrDN";
+       rargv[ 2 ] = "alias";
+       rargv[ 3 ] = "searchEntryDN";
+       rargv[ 4 ] = NULL;
+       rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+
+       /* NOTE: this corresponds to #undef'ining RWM_REFERRAL_REWRITE;
+        * see servers/slapd/overlays/rwm.h for details */
+        rargv[ 0 ] = "rewriteContext";
+       rargv[ 1 ] = "referralAttrDN";
+       rargv[ 2 ] = NULL;
+       rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+       rargv[ 0 ] = "rewriteContext";
+       rargv[ 1 ] = "referralDN";
+       rargv[ 2 ] = NULL;
+       rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+       return 0;
+}
diff --git a/servers/slapd/back-asyncmeta/conn.c b/servers/slapd/back-asyncmeta/conn.c
new file mode 100644 (file)
index 0000000..a8268a1
--- /dev/null
@@ -0,0 +1,1327 @@
+/* conn.c - handles connections to remote targets */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
++ * This work was developed by Symas Corporation
++ * based on back-meta module for inclusion in OpenLDAP Software.
++ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+
+
+#define AVL_INTERNAL
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+
+/*
+ * Debug stuff (got it from libavl)
+ */
+#if META_BACK_PRINT_CONNTREE > 0
+
+static void
+asyncmeta_back_ravl_print( Avlnode *root, int depth )
+{
+       int             i;
+
+       if ( root == 0 ) {
+               return;
+       }
+
+       asyncmeta_back_ravl_print( root->avl_right, depth + 1 );
+
+       for ( i = 0; i < depth; i++ ) {
+               fprintf( stderr, "-" );
+       }
+       fputc( ' ', stderr );
+
+       asyncmeta_back_print( (a_metaconn_t *)root->avl_data,
+               avl_bf2str( root->avl_bf ) );
+
+       asyncmeta_back_ravl_print( root->avl_left, depth + 1 );
+}
+
+/* NOTE: duplicate from back-ldap/bind.c */
+static char* priv2str[] = {
+       "privileged",
+       "privileged/TLS",
+       "anonymous",
+       "anonymous/TLS",
+       "bind",
+       "bind/TLS",
+       NULL
+};
+
+#endif /* META_BACK_PRINT_CONNTREE */
+/*
+ * End of debug stuff
+ */
+
+/*
+ * asyncmeta_conn_alloc
+ *
+ * Allocates a connection structure, making room for all the referenced targets
+ */
+static a_metaconn_t *
+asyncmeta_conn_alloc(
+       a_metainfo_t    *mi)
+{
+       a_metaconn_t    *mc;
+       int             ntargets = mi->mi_ntargets;
+
+       assert( ntargets > 0 );
+
+       /* malloc all in one */
+       mc = ( a_metaconn_t * )ch_calloc( 1, sizeof( a_metaconn_t ) + ntargets * sizeof( a_metasingleconn_t ));
+       if ( mc == NULL ) {
+               return NULL;
+       }
+
+       mc->mc_info = mi;
+       ldap_pvt_thread_mutex_init( &mc->mc_om_mutex);
+       mc->mc_authz_target = META_BOUND_NONE;
+       mc->mc_conns = (a_metasingleconn_t *)(mc+1);
+       return mc;
+}
+
+/*
+ * asyncmeta_init_one_conn
+ *
+ * Initializes one connection
+ */
+int
+asyncmeta_init_one_conn(
+       Operation               *op,
+       SlapReply               *rs,
+       a_metaconn_t            *mc,
+       int                     candidate,
+       int                     ispriv,
+       ldap_back_send_t        sendok,
+       int                     dolock)
+{
+       a_metainfo_t            *mi = mc->mc_info;
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+       a_metasingleconn_t      *msc = NULL;
+       int                     version;
+       a_dncookie              dc;
+       int                     isauthz = ( candidate == mc->mc_authz_target );
+       int                     do_return = 0;
+       int                     nretries = 2;
+
+#ifdef HAVE_TLS
+       int                     is_ldaps = 0;
+       int                     do_start_tls = 0;
+#endif /* HAVE_TLS */
+
+       /* if the server is quarantined, and
+        * - the current interval did not expire yet, or
+        * - no more retries should occur,
+        * don't return the connection */
+       if ( mt->mt_isquarantined ) {
+               slap_retry_info_t       *ri = &mt->mt_quarantine;
+               int                     dont_retry = 0;
+
+               if ( mt->mt_quarantine.ri_interval ) {
+                       ldap_pvt_thread_mutex_lock( &mt->mt_quarantine_mutex );
+                       dont_retry = ( mt->mt_isquarantined > LDAP_BACK_FQ_NO );
+                       if ( dont_retry ) {
+                               dont_retry = ( ri->ri_num[ ri->ri_idx ] == SLAP_RETRYNUM_TAIL
+                                       || slap_get_time() < ri->ri_last + ri->ri_interval[ ri->ri_idx ] );
+                               if ( !dont_retry ) {
+                                       if ( LogTest( LDAP_DEBUG_ANY ) ) {
+                                               char    buf[ SLAP_TEXT_BUFLEN ];
+
+                                               snprintf( buf, sizeof( buf ),
+                                                       "asyncmeta_init_one_conn[%d]: quarantine "
+                                                       "retry block #%d try #%d",
+                                                       candidate, ri->ri_idx, ri->ri_count );
+                                               Debug( LDAP_DEBUG_ANY, "%s %s.\n",
+                                                       op->o_log_prefix, buf, 0 );
+                                       }
+
+                                       mt->mt_isquarantined = LDAP_BACK_FQ_RETRYING;
+                               }
+
+                       }
+                       ldap_pvt_thread_mutex_unlock( &mt->mt_quarantine_mutex );
+               }
+
+               if ( dont_retry ) {
+                       rs->sr_err = LDAP_UNAVAILABLE;
+                       if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) {
+                               rs->sr_text = "Target is quarantined";
+                                       send_ldap_result( op, rs );
+                       }
+                       return rs->sr_err;
+               }
+       }
+               msc = &mc->mc_conns[candidate];
+retry_lock:;
+       /*
+        * Already init'ed
+        */
+       if ( LDAP_BACK_CONN_ISBOUND( msc )
+               || LDAP_BACK_CONN_ISANON( msc ) )
+       {
+               assert( msc->msc_ld != NULL );
+               rs->sr_err = LDAP_SUCCESS;
+               do_return = 1;
+
+       } else if ( META_BACK_CONN_CREATING( msc )
+               || LDAP_BACK_CONN_BINDING( msc ) )
+       {
+               /* sounds more appropriate */
+               if (nretries >= 0) {
+                       nretries--;
+                       ldap_pvt_thread_yield();
+                       goto retry_lock;
+               }
+               rs->sr_err = LDAP_UNAVAILABLE;
+               do_return = 1;
+
+       } else if ( META_BACK_CONN_INITED( msc ) ) {
+               assert( msc->msc_ld != NULL );
+               rs->sr_err = LDAP_SUCCESS;
+               do_return = 1;
+
+       } else {
+               /*
+                * creating...
+                */
+               META_BACK_CONN_CREATING_SET( msc );
+       }
+
+       if ( do_return ) {
+               if ( rs->sr_err != LDAP_SUCCESS
+                       && op->o_conn
+                       && ( sendok & LDAP_BACK_SENDERR ) )
+               {
+                       send_ldap_result( op, rs );
+               }
+
+               return rs->sr_err;
+       }
+
+       assert( msc->msc_ld == NULL );
+
+       /*
+        * Attempts to initialize the connection to the target ds
+        */
+       ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex );
+
+       rs->sr_err = ldap_initialize( &msc->msc_ld, mt->mt_uri );
+#ifdef HAVE_TLS
+       is_ldaps = ldap_is_ldaps_url( mt->mt_uri );
+#endif /* HAVE_TLS */
+       ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex );
+       if ( rs->sr_err != LDAP_SUCCESS ) {
+               goto error_return;
+       }
+       msc->msc_ldr = ldap_dup(msc->msc_ld);
+       if (!msc->msc_ldr) {
+               ldap_ld_free(msc->msc_ld, 0, NULL, NULL);
+               rs->sr_err = LDAP_NO_MEMORY;
+               goto error_return;
+       }
+
+       /*
+        * Set LDAP version. This will always succeed: If the client
+        * bound with a particular version, then so can we.
+        */
+       if ( mt->mt_version != 0 ) {
+               version = mt->mt_version;
+
+       } else if ( op->o_conn->c_protocol != 0 ) {
+               version = op->o_conn->c_protocol;
+
+       } else {
+               version = LDAP_VERSION3;
+       }
+       ldap_set_option( msc->msc_ld, LDAP_OPT_PROTOCOL_VERSION, &version );
+       ldap_set_urllist_proc( msc->msc_ld, mt->mt_urllist_f, mt->mt_urllist_p );
+
+       /* automatically chase referrals ("chase-referrals [{yes|no}]" statement) */
+       ldap_set_option( msc->msc_ld, LDAP_OPT_REFERRALS,
+               META_BACK_TGT_CHASE_REFERRALS( mt ) ? LDAP_OPT_ON : LDAP_OPT_OFF );
+
+       slap_client_keepalive(msc->msc_ld, &mt->mt_tls.sb_keepalive);
+
+#ifdef HAVE_TLS
+       {
+               slap_bindconf *sb = NULL;
+
+               if ( ispriv ) {
+                       sb = &mt->mt_idassert.si_bc;
+               } else {
+                       sb = &mt->mt_tls;
+               }
+
+               if ( sb->sb_tls_do_init ) {
+                       bindconf_tls_set( sb, msc->msc_ld );
+               } else if ( sb->sb_tls_ctx ) {
+                       ldap_set_option( msc->msc_ld, LDAP_OPT_X_TLS_CTX, sb->sb_tls_ctx );
+               }
+
+               if ( !is_ldaps ) {
+                       if ( sb == &mt->mt_idassert.si_bc && sb->sb_tls_ctx ) {
+                               do_start_tls = 1;
+
+                       } else if ( META_BACK_TGT_USE_TLS( mt )
+                               || ( op->o_conn->c_is_tls && META_BACK_TGT_PROPAGATE_TLS( mt ) ) )
+                       {
+                               do_start_tls = 1;
+                       }
+               }
+       }
+
+       /* start TLS ("tls [try-]{start|propagate}" statement) */
+       if ( do_start_tls ) {
+#ifdef SLAP_STARTTLS_ASYNCHRONOUS
+               /*
+                * use asynchronous StartTLS; in case, chase referral
+                * FIXME: OpenLDAP does not return referral on StartTLS yet
+                */
+               int             msgid;
+
+               rs->sr_err = ldap_start_tls( msc->msc_ld, NULL, NULL, &msgid );
+               if ( rs->sr_err == LDAP_SUCCESS ) {
+                       LDAPMessage     *res = NULL;
+                       int             rc, nretries = mt->mt_nretries;
+                       struct timeval  tv;
+
+                       LDAP_BACK_TV_SET( &tv );
+
+retry:;
+                       rc = ldap_result( msc->msc_ld, msgid, LDAP_MSG_ALL, &tv, &res );
+                       switch ( rc ) {
+                       case -1:
+                               rs->sr_err = LDAP_OTHER;
+                               break;
+
+                       case 0:
+                               if ( nretries != 0 ) {
+                                       if ( nretries > 0 ) {
+                                               nretries--;
+                                       }
+                                       LDAP_BACK_TV_SET( &tv );
+                                       goto retry;
+                               }
+                               rs->sr_err = LDAP_OTHER;
+                               break;
+
+                       default:
+                               /* only touch when activity actually took place... */
+                               if ( mi->mi_idle_timeout != 0 && msc->msc_time < op->o_time ) {
+                                       msc->msc_time = op->o_time;
+                               }
+                               break;
+                       }
+
+                       if ( rc == LDAP_RES_EXTENDED ) {
+                               struct berval   *data = NULL;
+
+                               /* NOTE: right now, data is unused, so don't get it */
+                               rs->sr_err = ldap_parse_extended_result( msc->msc_ld,
+                                       res, NULL, NULL /* &data */ , 0 );
+                               if ( rs->sr_err == LDAP_SUCCESS ) {
+                                       int             err;
+
+                                       /* FIXME: matched? referrals? response controls? */
+                                       rs->sr_err = ldap_parse_result( msc->msc_ld,
+                                               res, &err, NULL, NULL, NULL, NULL, 1 );
+                                       res = NULL;
+
+                                       if ( rs->sr_err == LDAP_SUCCESS ) {
+                                               rs->sr_err = err;
+                                       }
+                                       rs->sr_err = slap_map_api2result( rs );
+
+                                       /* FIXME: in case a referral
+                                        * is returned, should we try
+                                        * using it instead of the
+                                        * configured URI? */
+                                       if ( rs->sr_err == LDAP_SUCCESS ) {
+                                               ldap_install_tls( msc->msc_ld );
+
+                                       } else if ( rs->sr_err == LDAP_REFERRAL ) {
+                                               /* FIXME: LDAP_OPERATIONS_ERROR? */
+                                               rs->sr_err = LDAP_OTHER;
+                                               rs->sr_text = "Unwilling to chase referral "
+                                                       "returned by Start TLS exop";
+                                       }
+
+                                       if ( data ) {
+                                               ber_bvfree( data );
+                                       }
+                               }
+
+                       } else {
+                               rs->sr_err = LDAP_OTHER;
+                       }
+
+                       if ( res != NULL ) {
+                               ldap_msgfree( res );
+                       }
+               }
+#else /* ! SLAP_STARTTLS_ASYNCHRONOUS */
+               /*
+                * use synchronous StartTLS
+                */
+               rs->sr_err = ldap_start_tls_s( msc->msc_ld, NULL, NULL );
+#endif /* ! SLAP_STARTTLS_ASYNCHRONOUS */
+
+               /* if StartTLS is requested, only attempt it if the URL
+                * is not "ldaps://"; this may occur not only in case
+                * of misconfiguration, but also when used in the chain
+                * overlay, where the "uri" can be parsed out of a referral */
+               if ( rs->sr_err == LDAP_SERVER_DOWN
+                       || ( rs->sr_err != LDAP_SUCCESS
+                               && META_BACK_TGT_TLS_CRITICAL( mt ) ) )
+               {
+
+#ifdef DEBUG_205
+                       Debug( LDAP_DEBUG_ANY,
+                               "### %s asyncmeta_init_one_conn(TLS) "
+                               "ldap_unbind_ext[%d] ld=%p\n",
+                               op->o_log_prefix, candidate,
+                               (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+                       goto error_return;
+               }
+       }
+#endif /* HAVE_TLS */
+
+       /*
+        * Set the network timeout if set
+        */
+       if ( mt->mt_network_timeout != 0 ) {
+               struct timeval  network_timeout;
+
+               network_timeout.tv_usec = 0;
+               network_timeout.tv_sec = mt->mt_network_timeout;
+
+               ldap_set_option( msc->msc_ld, LDAP_OPT_NETWORK_TIMEOUT,
+                               (void *)&network_timeout );
+       }
+
+       /*
+        * If the connection DN is not null, an attempt to rewrite it is made
+        */
+
+       if ( ispriv ) {
+               if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) ) {
+                       ber_bvreplace( &msc->msc_bound_ndn, &mt->mt_idassert_authcDN );
+                       if ( !BER_BVISNULL( &mt->mt_idassert_passwd ) ) {
+                               if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+                                       memset( msc->msc_cred.bv_val, 0,
+                                               msc->msc_cred.bv_len );
+                               }
+                               ber_bvreplace( &msc->msc_cred, &mt->mt_idassert_passwd );
+                       }
+                       LDAP_BACK_CONN_ISIDASSERT_SET( msc );
+
+               } else {
+                       ber_bvreplace( &msc->msc_bound_ndn, &slap_empty_bv );
+               }
+
+       } else {
+               if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+                       memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len );
+                       ber_memfree_x( msc->msc_cred.bv_val, NULL );
+                       BER_BVZERO( &msc->msc_cred );
+               }
+               if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+                       ber_memfree_x( msc->msc_bound_ndn.bv_val, NULL );
+                       BER_BVZERO( &msc->msc_bound_ndn );
+               }
+               if ( !BER_BVISEMPTY( &op->o_ndn )
+                       && SLAP_IS_AUTHZ_BACKEND( op )
+                       && isauthz )
+               {
+                       dc.target = mt;
+                       dc.conn = op->o_conn;
+                       dc.rs = rs;
+                       dc.ctx = "bindDN";
+
+                       /*
+                        * Rewrite the bind dn if needed
+                        */
+                       if ( asyncmeta_dn_massage( &dc, &op->o_conn->c_dn,
+                                               &msc->msc_bound_ndn ) )
+                       {
+
+#ifdef DEBUG_205
+                               Debug( LDAP_DEBUG_ANY,
+                                       "### %s asyncmeta_init_one_conn(rewrite) "
+                                       "ldap_unbind_ext[%d] ld=%p\n",
+                                       op->o_log_prefix, candidate,
+                                       (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+                               goto error_return;
+                       }
+
+                       /* copy the DN if needed */
+                       if ( msc->msc_bound_ndn.bv_val == op->o_conn->c_dn.bv_val ) {
+                               ber_dupbv( &msc->msc_bound_ndn, &op->o_conn->c_dn );
+                       }
+
+                       assert( !BER_BVISNULL( &msc->msc_bound_ndn ) );
+
+               } else {
+                       ber_dupbv( &msc->msc_bound_ndn, (struct berval *)&slap_empty_bv );
+               }
+       }
+       assert( !BER_BVISNULL( &msc->msc_bound_ndn ) );
+
+error_return:;
+
+       if (msc != NULL) {
+               META_BACK_CONN_CREATING_CLEAR( msc );
+       }
+       if ( rs->sr_err == LDAP_SUCCESS && msc != NULL) {
+               /*
+                * Sets a cookie for the rewrite session
+                */
+               ( void )rewrite_session_init( mt->mt_rwmap.rwm_rw, op->o_conn );
+               META_BACK_CONN_INITED_SET( msc );
+       }
+
+       if ( rs->sr_err != LDAP_SUCCESS ) {
+               rs->sr_err = slap_map_api2result( rs );
+               if ( sendok & LDAP_BACK_SENDERR ) {
+                       send_ldap_result( op, rs );
+               }
+       }
+       return rs->sr_err;
+}
+
+/*
+ * asyncmeta_retry
+ *
+ * Retries one connection
+ */
+int
+asyncmeta_retry(
+       Operation               *op,
+       SlapReply               *rs,
+       a_metaconn_t            **mcp,
+       int                     candidate,
+       ldap_back_send_t        sendok )
+{
+       a_metaconn_t            *mc = *mcp;
+       a_metainfo_t            *mi = mc->mc_info;
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+       int                     rc = LDAP_UNAVAILABLE,
+                               binding,
+                               quarantine = 1;
+
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+       struct berval save_cred;
+
+       if ( LogTest( LDAP_DEBUG_ANY ) ) {
+               char    buf[ SLAP_TEXT_BUFLEN ];
+
+               /* this lock is required; however,
+                * it's invoked only when logging is on */
+                       ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex );
+                       snprintf( buf, sizeof( buf ),
+                                 "retrying URI=\"%s\" DN=\"%s\"",
+                                 mt->mt_uri,
+                                 BER_BVISNULL( &msc->msc_bound_ndn ) ?
+                                 "" : msc->msc_bound_ndn.bv_val );
+                       ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex );
+
+                       Debug( LDAP_DEBUG_ANY,
+                              "%s asyncmeta_retry[%d]: %s.\n",
+                              op->o_log_prefix, candidate, buf );
+       }
+
+       ( void )rewrite_session_delete( mt->mt_rwmap.rwm_rw, op->o_conn );
+       /* mc here must be the regular mc, reset and ready for init */
+       rc = asyncmeta_init_one_conn( op, rs, mc, candidate,
+                                     LDAP_BACK_CONN_ISPRIV( mc ), sendok, 0 );
+
+       if ( rc != LDAP_SUCCESS ) {
+               if ( sendok & LDAP_BACK_SENDERR ) {
+                       /* init_one_conn has set the result */
+                       send_ldap_result( op, rs );
+               }
+       }
+
+       if ( rc == LDAP_SUCCESS ) {
+               quarantine = 0;
+               LDAP_BACK_CONN_BINDING_SET( msc ); binding = 1;
+               /* todo this must be dobind_init */
+               rc = asyncmeta_back_single_dobind( op, rs, mcp, candidate,
+                                                  sendok, mt->mt_nretries, 0 );
+
+               Debug( LDAP_DEBUG_ANY,
+                      "%s asyncmeta_retry[%d]: "
+                      "asyncmeta_single_dobind=%d\n",
+                      op->o_log_prefix, candidate, rc );
+               if ( rc == LDAP_SUCCESS ) {
+                       if ( !BER_BVISNULL( &msc->msc_bound_ndn ) &&
+                            !BER_BVISEMPTY( &msc->msc_bound_ndn ) )
+                       {
+                               LDAP_BACK_CONN_ISBOUND_SET( msc );
+
+                       } else {
+                               LDAP_BACK_CONN_ISANON_SET( msc );
+                       }
+
+                       /* when bound, dispose of the "binding" flag */
+                       if ( binding ) {
+                               LDAP_BACK_CONN_BINDING_CLEAR( msc );
+                       }
+               }
+       }
+
+
+       if ( rc != LDAP_SUCCESS ) {
+               if (mc->mc_active < 1) {
+                       asyncmeta_clear_one_msc(NULL, mc, candidate);
+               }
+               if ( sendok & LDAP_BACK_SENDERR ) {
+                       rs->sr_err = rc;
+                       rs->sr_text = "Unable to retry";
+                       send_ldap_result( op, rs );
+               }
+       }
+
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+       if ( quarantine && META_BACK_TGT_QUARANTINE( mt ) ) {
+               asyncmeta_quarantine( op, mi, rs, candidate );
+       }
+
+       return rc == LDAP_SUCCESS ? 1 : 0;
+}
+
+/*
+ * callback for unique candidate selection
+ */
+static int
+asyncmeta_conn_cb( Operation *op, SlapReply *rs )
+{
+       assert( op->o_tag == LDAP_REQ_SEARCH );
+
+       switch ( rs->sr_type ) {
+       case REP_SEARCH:
+               ((long *)op->o_callback->sc_private)[0] = (long)op->o_private;
+               break;
+
+       case REP_SEARCHREF:
+       case REP_RESULT:
+               break;
+
+       default:
+               return rs->sr_err;
+       }
+
+       return 0;
+}
+
+
+static int
+asyncmeta_get_candidate(
+       Operation       *op,
+       SlapReply       *rs,
+       struct berval   *ndn )
+{
+       a_metainfo_t    *mi = ( a_metainfo_t * )op->o_bd->be_private;
+       long            candidate;
+
+       /*
+        * tries to get a unique candidate
+        * (takes care of default target)
+        */
+       candidate = asyncmeta_select_unique_candidate( mi, ndn );
+
+       /*
+        * if any is found, inits the connection
+        */
+       if ( candidate == META_TARGET_NONE ) {
+               rs->sr_err = LDAP_NO_SUCH_OBJECT;
+               rs->sr_text = "No suitable candidate target found";
+
+       } else if ( candidate == META_TARGET_MULTIPLE ) {
+               Operation       op2 = *op;
+               SlapReply       rs2 = { REP_RESULT };
+               slap_callback   cb2 = { 0 };
+               int             rc;
+
+               /* try to get a unique match for the request ndn
+                * among the multiple candidates available */
+               op2.o_tag = LDAP_REQ_SEARCH;
+               op2.o_req_dn = *ndn;
+               op2.o_req_ndn = *ndn;
+               op2.ors_scope = LDAP_SCOPE_BASE;
+               op2.ors_deref = LDAP_DEREF_NEVER;
+               op2.ors_attrs = slap_anlist_no_attrs;
+               op2.ors_attrsonly = 0;
+               op2.ors_limit = NULL;
+               op2.ors_slimit = 1;
+               op2.ors_tlimit = SLAP_NO_LIMIT;
+
+               op2.ors_filter = (Filter *)slap_filter_objectClass_pres;
+               op2.ors_filterstr = *slap_filterstr_objectClass_pres;
+
+               op2.o_callback = &cb2;
+               cb2.sc_response = asyncmeta_conn_cb;
+               cb2.sc_private = (void *)&candidate;
+
+               rc = op->o_bd->be_search( &op2, &rs2 );
+
+               switch ( rs2.sr_err ) {
+               case LDAP_SUCCESS:
+               default:
+                       rs->sr_err = rs2.sr_err;
+                       break;
+
+               case LDAP_SIZELIMIT_EXCEEDED:
+                       /* if multiple candidates can serve the operation,
+                        * and a default target is defined, and it is
+                        * a candidate, try using it (FIXME: YMMV) */
+                       if ( mi->mi_defaulttarget != META_DEFAULT_TARGET_NONE
+                               && asyncmeta_is_candidate( mi->mi_targets[ mi->mi_defaulttarget ],
+                                               ndn, op->o_tag == LDAP_REQ_SEARCH ? op->ors_scope : LDAP_SCOPE_BASE ) )
+                       {
+                               candidate = mi->mi_defaulttarget;
+                               rs->sr_err = LDAP_SUCCESS;
+                               rs->sr_text = NULL;
+
+                       } else {
+                               rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+                               rs->sr_text = "Unable to select unique candidate target";
+                       }
+                       break;
+               }
+
+       } else {
+               rs->sr_err = LDAP_SUCCESS;
+       }
+
+       return candidate;
+}
+
+static void    *asyncmeta_candidates_dummy;
+
+static void
+asyncmeta_candidates_keyfree(
+       void            *key,
+       void            *data )
+{
+       a_metacandidates_t      *mc = (a_metacandidates_t *)data;
+
+       ber_memfree_x( mc->mc_candidates, NULL );
+       ber_memfree_x( data, NULL );
+}
+
+
+/*
+ * asyncmeta_getconn
+ *
+ * Prepares the connection structure
+ *
+ * RATIONALE:
+ *
+ * - determine what DN is being requested:
+ *
+ *     op      requires candidate      checks
+ *
+ *     add     unique                  parent of o_req_ndn
+ *     bind    unique^*[/all]          o_req_ndn [no check]
+ *     compare unique^+                o_req_ndn
+ *     delete  unique                  o_req_ndn
+ *     modify  unique                  o_req_ndn
+ *     search  any                     o_req_ndn
+ *     modrdn  unique[, unique]        o_req_ndn[, orr_nnewSup]
+ *
+ * - for ops that require the candidate to be unique, in case of multiple
+ *   occurrences an internal search with sizeLimit=1 is performed
+ *   if a unique candidate can actually be determined.  If none is found,
+ *   the operation aborts; if multiple are found, the default target
+ *   is used if defined and candidate; otherwise the operation aborts.
+ *
+ * *^note: actually, the bind operation is handled much like a search;
+ *   i.e. the bind is broadcast to all candidate targets.
+ *
+ * +^note: actually, the compare operation is handled much like a search;
+ *   i.e. the compare is broadcast to all candidate targets, while checking
+ *   that exactly none (noSuchObject) or one (TRUE/FALSE/UNDEFINED) is
+ *   returned.
+ */
+a_metaconn_t *
+asyncmeta_getconn(
+       Operation               *op,
+       SlapReply               *rs,
+       SlapReply               *candidates,
+       int                     *candidate,
+       ldap_back_send_t        sendok,
+       int                     alloc_new)
+{
+       a_metainfo_t    *mi = ( a_metainfo_t * )op->o_bd->be_private;
+       a_metaconn_t    *mc = NULL,
+                       mc_curr = {{ 0 }};
+       int             cached = META_TARGET_NONE,
+                       i = META_TARGET_NONE,
+                       err = LDAP_SUCCESS,
+                       new_conn = 0,
+                       ncandidates = 0;
+
+
+       meta_op_type    op_type = META_OP_REQUIRE_SINGLE;
+       enum            {
+               META_DNTYPE_ENTRY,
+               META_DNTYPE_PARENT,
+               META_DNTYPE_NEWPARENT
+       }               dn_type = META_DNTYPE_ENTRY;
+       struct berval   ndn = op->o_req_ndn,
+                       pndn;
+
+       if (alloc_new > 0) {
+               mc = asyncmeta_conn_alloc(mi);
+               new_conn = 0;
+       } else {
+               mc = asyncmeta_get_next_mc(mi);
+       }
+
+       ldap_pvt_thread_mutex_lock(&mc->mc_om_mutex);
+       /* Internal searches are privileged and shared. So is root. */
+       if ( ( !BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_ALWAYS( mi ) )
+               || ( BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_ANON( mi ) )
+               || op->o_do_not_cache || be_isroot( op ) )
+       {
+               LDAP_BACK_CONN_ISPRIV_SET( &mc_curr );
+               LDAP_BACK_PCONN_ROOTDN_SET( &mc_curr, op );
+
+       } else if ( BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_NOANON( mi ) )
+       {
+               LDAP_BACK_CONN_ISANON_SET( &mc_curr );
+               LDAP_BACK_PCONN_ANON_SET( &mc_curr, op );
+
+       } else {
+               /* Explicit binds must not be shared */
+               if ( !BER_BVISEMPTY( &op->o_ndn )
+                       || op->o_tag == LDAP_REQ_BIND
+                       || SLAP_IS_AUTHZ_BACKEND( op ) )
+               {
+                       //mc_curr.mc_conn = op->o_conn;
+
+               } else {
+                       LDAP_BACK_CONN_ISANON_SET( &mc_curr );
+                       LDAP_BACK_PCONN_ANON_SET( &mc_curr, op );
+               }
+       }
+
+       switch ( op->o_tag ) {
+       case LDAP_REQ_ADD:
+               /* if we go to selection, the entry must not exist,
+                * and we must be able to resolve the parent */
+               dn_type = META_DNTYPE_PARENT;
+               dnParent( &ndn, &pndn );
+               break;
+
+       case LDAP_REQ_MODRDN:
+               /* if nnewSuperior is not NULL, it must resolve
+                * to the same candidate as the req_ndn */
+               if ( op->orr_nnewSup ) {
+                       dn_type = META_DNTYPE_NEWPARENT;
+               }
+               break;
+
+       case LDAP_REQ_BIND:
+               /* if bound as rootdn, the backend must bind to all targets
+                * with the administrative identity
+                * (unless pseoudoroot-bind-defer is TRUE) */
+               if ( op->orb_method == LDAP_AUTH_SIMPLE && be_isroot_pw( op ) ) {
+                       op_type = META_OP_REQUIRE_ALL;
+               }
+               break;
+
+       case LDAP_REQ_COMPARE:
+       case LDAP_REQ_DELETE:
+       case LDAP_REQ_MODIFY:
+               /* just a unique candidate */
+               break;
+
+       case LDAP_REQ_SEARCH:
+               /* allow multiple candidates for the searchBase */
+               op_type = META_OP_ALLOW_MULTIPLE;
+               break;
+
+       default:
+               /* right now, just break (exop?) */
+               break;
+       }
+
+       /*
+        * require all connections ...
+        */
+       if ( op_type == META_OP_REQUIRE_ALL ) {
+               if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) {
+                       LDAP_BACK_CONN_ISPRIV_SET( mc );
+
+               } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) {
+                       LDAP_BACK_CONN_ISANON_SET( mc );
+               }
+
+               for ( i = 0; i < mi->mi_ntargets; i++ ) {
+                       /*
+                        * The target is activated; if needed, it is
+                        * also init'd
+                        */
+                       candidates[ i ].sr_err = asyncmeta_init_one_conn( op,
+                               rs, mc, i, LDAP_BACK_CONN_ISPRIV( &mc_curr ),
+                               LDAP_BACK_DONTSEND, !new_conn );
+                       if ( candidates[ i ].sr_err == LDAP_SUCCESS ) {
+                               if ( new_conn && ( sendok & LDAP_BACK_BINDING ) ) {
+                                       LDAP_BACK_CONN_BINDING_SET( &mc->mc_conns[ i ] );
+                               }
+                               META_CANDIDATE_SET( &candidates[ i ] );
+                               ncandidates++;
+
+                       } else {
+
+                               /*
+                                * FIXME: in case one target cannot
+                                * be init'd, should the other ones
+                                * be tried?
+                                */
+                               META_CANDIDATE_RESET( &candidates[ i ] );
+                               err = candidates[ i ].sr_err;
+                               continue;
+                       }
+               }
+
+               if ( ncandidates == 0 ) {
+                       rs->sr_err = LDAP_NO_SUCH_OBJECT;
+                       rs->sr_text = "Unable to select valid candidates";
+
+                       if ( sendok & LDAP_BACK_SENDERR ) {
+                               if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) {
+                                       rs->sr_matched = op->o_bd->be_suffix[ 0 ].bv_val;
+                               }
+                               send_ldap_result( op, rs );
+                               rs->sr_matched = NULL;
+                       }
+                       ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+                       if ( alloc_new > 0) {
+                               asyncmeta_back_conn_free( mc );
+                       }
+                       return NULL;
+               }
+
+               goto done;
+       }
+
+       /*
+        * looks in cache, if any
+        */
+       if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED ) {
+               cached = i = asyncmeta_dncache_get_target( &mi->mi_cache, &op->o_req_ndn );
+       }
+
+       if ( op_type == META_OP_REQUIRE_SINGLE ) {
+               a_metatarget_t          *mt = NULL;
+               a_metasingleconn_t      *msc = NULL;
+
+               int                     j;
+
+               for ( j = 0; j < mi->mi_ntargets; j++ ) {
+                       META_CANDIDATE_RESET( &candidates[ j ] );
+               }
+
+               /*
+                * tries to get a unique candidate
+                * (takes care of default target)
+                */
+               if ( i == META_TARGET_NONE ) {
+                       i = asyncmeta_get_candidate( op, rs, &ndn );
+
+                       if ( rs->sr_err == LDAP_NO_SUCH_OBJECT && dn_type == META_DNTYPE_PARENT ) {
+                               i = asyncmeta_get_candidate( op, rs, &pndn );
+                       }
+
+                       if ( i < 0 || rs->sr_err != LDAP_SUCCESS ) {
+                               if ( sendok & LDAP_BACK_SENDERR ) {
+                                       if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) {
+                                               rs->sr_matched = op->o_bd->be_suffix[ 0 ].bv_val;
+                                       }
+                                       send_ldap_result( op, rs );
+                                       rs->sr_matched = NULL;
+                               }
+                               ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+                               if ( mc != NULL && alloc_new ) {
+                                       asyncmeta_back_conn_free( mc );
+                               }
+                               return NULL;
+                       }
+               }
+
+               if ( dn_type == META_DNTYPE_NEWPARENT && asyncmeta_get_candidate( op, rs, op->orr_nnewSup ) != i )
+               {
+                       rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+                       rs->sr_text = "Cross-target rename not supported";
+                       if ( sendok & LDAP_BACK_SENDERR ) {
+                               send_ldap_result( op, rs );
+                       }
+                       ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+                       if ( mc != NULL && alloc_new > 0 ) {
+                               asyncmeta_back_conn_free( mc );
+                       }
+                       return NULL;
+               }
+
+               Debug( LDAP_DEBUG_TRACE,
+                      "==>asyncmeta__getconn: got target=%d for ndn=\"%s\" from cache\n",
+                      i, op->o_req_ndn.bv_val, 0 );
+                       if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) {
+                               LDAP_BACK_CONN_ISPRIV_SET( mc );
+
+                       } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) {
+                               LDAP_BACK_CONN_ISANON_SET( mc );
+                       }
+
+               /*
+                * Clear all other candidates
+                */
+                       ( void )asyncmeta_clear_unused_candidates( op, i , mc, candidates);
+
+               mt = mi->mi_targets[ i ];
+               msc = &mc->mc_conns[ i ];
+
+               /*
+                * The target is activated; if needed, it is
+                * also init'd. In case of error, asyncmeta_init_one_conn
+                * sends the appropriate result.
+                */
+               err = asyncmeta_init_one_conn( op, rs, mc, i,
+                       LDAP_BACK_CONN_ISPRIV( &mc_curr ), sendok, !new_conn );
+               if ( err != LDAP_SUCCESS ) {
+                       /*
+                        * FIXME: in case one target cannot
+                        * be init'd, should the other ones
+                        * be tried?
+                        */
+                       META_CANDIDATE_RESET( &candidates[ i ] );
+                       ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+                       if ( mc != NULL && alloc_new > 0 ) {
+                               asyncmeta_back_conn_free( mc );
+                       }
+                       return NULL;
+               }
+
+               candidates[ i ].sr_err = LDAP_SUCCESS;
+               META_CANDIDATE_SET( &candidates[ i ] );
+               ncandidates++;
+
+               if ( candidate ) {
+                       *candidate = i;
+               }
+
+       /*
+        * if no unique candidate ...
+        */
+       } else {
+       if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) {
+               LDAP_BACK_CONN_ISPRIV_SET( mc );
+
+       } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) {
+               LDAP_BACK_CONN_ISANON_SET( mc );
+       }
+
+               for ( i = 0; i < mi->mi_ntargets; i++ ) {
+                       a_metatarget_t          *mt = mi->mi_targets[ i ];
+
+                       META_CANDIDATE_RESET( &candidates[ i ] );
+
+                       if ( i == cached
+                               || asyncmeta_is_candidate( mt, &op->o_req_ndn,
+                                       op->o_tag == LDAP_REQ_SEARCH ? op->ors_scope : LDAP_SCOPE_SUBTREE ) )
+                       {
+
+                               /*
+                                * The target is activated; if needed, it is
+                                * also init'd
+                                */
+                               int lerr = asyncmeta_init_one_conn( op, rs, mc, i,
+                                       LDAP_BACK_CONN_ISPRIV( &mc_curr ),
+                                       LDAP_BACK_DONTSEND, !new_conn );
+                               candidates[ i ].sr_err = lerr;
+                               if ( lerr == LDAP_SUCCESS ) {
+                                       META_CANDIDATE_SET( &candidates[ i ] );
+                                       ncandidates++;
+
+                                       Debug( LDAP_DEBUG_TRACE, "%s: asyncmeta_getconn[%d]\n",
+                                               op->o_log_prefix, i, 0 );
+
+                               } else if ( lerr == LDAP_UNAVAILABLE && !META_BACK_ONERR_STOP( mi ) ) {
+                                       META_CANDIDATE_SET( &candidates[ i ] );
+
+                                       Debug( LDAP_DEBUG_TRACE, "%s: asyncmeta_getconn[%d] %s\n",
+                                               op->o_log_prefix, i,
+                                               mt->mt_isquarantined != LDAP_BACK_FQ_NO ? "quarantined" : "unavailable" );
+
+                               } else {
+
+                                       /*
+                                        * FIXME: in case one target cannot
+                                        * be init'd, should the other ones
+                                        * be tried?
+                                        */
+                                       /* leave the target candidate, but record the error for later use */
+                                       err = lerr;
+
+                                       if ( lerr == LDAP_UNAVAILABLE && mt->mt_isquarantined != LDAP_BACK_FQ_NO ) {
+                                               Debug( LDAP_DEBUG_TRACE, "%s: asyncmeta_getconn[%d] quarantined err=%d\n",
+                                                       op->o_log_prefix, i, lerr );
+
+                                       } else {
+                                               Debug( LDAP_DEBUG_ANY, "%s: asyncmeta_getconn[%d] failed err=%d\n",
+                                                       op->o_log_prefix, i, lerr );
+                                       }
+
+                                       if ( META_BACK_ONERR_STOP( mi ) ) {
+                                               if ( sendok & LDAP_BACK_SENDERR ) {
+                                                       send_ldap_result( op, rs );
+                                               }
+                                               ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+                                               if ( alloc_new > 0 ) {
+                                                       asyncmeta_back_conn_free( mc );
+
+                                               }
+                                               return NULL;
+                                       }
+
+                                       continue;
+                               }
+
+                       }
+               }
+
+               if ( ncandidates == 0 ) {
+                       if ( rs->sr_err == LDAP_SUCCESS ) {
+                               rs->sr_err = LDAP_NO_SUCH_OBJECT;
+                               rs->sr_text = "Unable to select valid candidates";
+                       }
+
+                       if ( sendok & LDAP_BACK_SENDERR ) {
+                               if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) {
+                                       rs->sr_matched = op->o_bd->be_suffix[ 0 ].bv_val;
+                               }
+                               send_ldap_result( op, rs );
+                               rs->sr_matched = NULL;
+                       }
+                       if ( alloc_new > 0 ) {
+                               asyncmeta_back_conn_free( mc );
+
+                       }
+                       ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+                       return NULL;
+               }
+       }
+
+done:;
+       /* clear out meta_back_init_one_conn non-fatal errors */
+       rs->sr_err = LDAP_SUCCESS;
+       rs->sr_text = NULL;
+
+       if ( new_conn ) {
+               if ( !LDAP_BACK_PCONN_ISPRIV( mc ) ) {
+                       /*
+                        * Err could be -1 in case a duplicate metaconn is inserted
+                        */
+                       switch ( err ) {
+                       case 0:
+                               break;
+                       default:
+                               LDAP_BACK_CONN_CACHED_CLEAR( mc );
+                               if ( LogTest( LDAP_DEBUG_ANY ) ) {
+                                       char buf[STRLENOF("4294967295U") + 1] = { 0 };
+                                       mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) );
+
+                                       Debug( LDAP_DEBUG_ANY,
+                                               "%s asyncmeta_getconn: candidates=%d conn=%s insert failed\n",
+                                               op->o_log_prefix, ncandidates, buf );
+                               }
+
+                               asyncmeta_back_conn_free( mc );
+
+                               rs->sr_err = LDAP_OTHER;
+                               rs->sr_text = "Proxy bind collision";
+                               if ( sendok & LDAP_BACK_SENDERR ) {
+                                       send_ldap_result( op, rs );
+                               }
+                               return NULL;
+                       }
+               }
+
+               if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+                       char buf[STRLENOF("4294967295U") + 1] = { 0 };
+                       mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) );
+
+                       Debug( LDAP_DEBUG_TRACE,
+                               "%s asyncmeta_getconn: candidates=%d conn=%s inserted\n",
+                               op->o_log_prefix, ncandidates, buf );
+               }
+
+       } else {
+               if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+                       char buf[STRLENOF("4294967295U") + 1] = { 0 };
+                       mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) );
+
+                       Debug( LDAP_DEBUG_TRACE,
+                               "%s asyncmeta_getconn: candidates=%d conn=%s fetched\n",
+                               op->o_log_prefix, ncandidates, buf );
+               }
+       }
+       ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+       return mc;
+}
+
+void
+asyncmeta_quarantine(
+       Operation       *op,
+       a_metainfo_t    *mi,
+       SlapReply       *rs,
+       int             candidate )
+{
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+
+       slap_retry_info_t       *ri = &mt->mt_quarantine;
+
+       ldap_pvt_thread_mutex_lock( &mt->mt_quarantine_mutex );
+
+       if ( rs->sr_err == LDAP_UNAVAILABLE ) {
+               time_t  new_last = slap_get_time();
+
+               switch ( mt->mt_isquarantined ) {
+               case LDAP_BACK_FQ_NO:
+                       if ( ri->ri_last == new_last ) {
+                               goto done;
+                       }
+
+                       Debug( LDAP_DEBUG_ANY,
+                               "%s asyncmeta_quarantine[%d]: enter.\n",
+                               op->o_log_prefix, candidate, 0 );
+
+                       ri->ri_idx = 0;
+                       ri->ri_count = 0;
+                       break;
+
+               case LDAP_BACK_FQ_RETRYING:
+                       if ( LogTest( LDAP_DEBUG_ANY ) ) {
+                               char    buf[ SLAP_TEXT_BUFLEN ];
+
+                               snprintf( buf, sizeof( buf ),
+                                       "asyncmeta_quarantine[%d]: block #%d try #%d failed",
+                                       candidate, ri->ri_idx, ri->ri_count );
+                               Debug( LDAP_DEBUG_ANY, "%s %s.\n",
+                                       op->o_log_prefix, buf, 0 );
+                       }
+
+                       ++ri->ri_count;
+                       if ( ri->ri_num[ ri->ri_idx ] != SLAP_RETRYNUM_FOREVER
+                               && ri->ri_count == ri->ri_num[ ri->ri_idx ] )
+                       {
+                               ri->ri_count = 0;
+                               ++ri->ri_idx;
+                       }
+                       break;
+
+               default:
+                       break;
+               }
+
+               mt->mt_isquarantined = LDAP_BACK_FQ_YES;
+               ri->ri_last = new_last;
+
+       } else if ( mt->mt_isquarantined == LDAP_BACK_FQ_RETRYING ) {
+               Debug( LDAP_DEBUG_ANY,
+                       "%s asyncmeta_quarantine[%d]: exit.\n",
+                       op->o_log_prefix, candidate, 0 );
+
+               if ( mi->mi_quarantine_f ) {
+                       (void)mi->mi_quarantine_f( mi, candidate,
+                               mi->mi_quarantine_p );
+               }
+
+               ri->ri_count = 0;
+               ri->ri_idx = 0;
+               mt->mt_isquarantined = LDAP_BACK_FQ_NO;
+               mt->mt_timeout_ops = 0;
+       }
+
+done:;
+       ldap_pvt_thread_mutex_unlock( &mt->mt_quarantine_mutex );
+}
+
+a_metaconn_t *
+asyncmeta_get_next_mc( a_metainfo_t *mi )
+{
+       a_metaconn_t *mc = NULL;
+       ldap_pvt_thread_mutex_lock( &mi->mi_mc_mutex );
+       if (mi->mi_next_conn >= mi->mi_num_conns-1) {
+               mi->mi_next_conn = 0;
+       } else {
+               mi->mi_next_conn++;
+       }
+
+       mc = &mi->mi_conns[mi->mi_next_conn];
+       ldap_pvt_thread_mutex_unlock( &mi->mi_mc_mutex );
+       return mc;
+}
+
+int asyncmeta_start_listeners(a_metaconn_t *mc, SlapReply *candidates)
+{
+       int i;
+       for (i = 0; i < mc->mc_info->mi_ntargets; i++) {
+               asyncmeta_start_one_listener(mc, candidates, i);
+       }
+       return LDAP_SUCCESS;
+}
+
+int asyncmeta_start_one_listener(a_metaconn_t *mc, SlapReply *candidates, int candidate)
+{
+       a_metasingleconn_t *msc;
+       ber_socket_t s;
+       int i;
+
+       msc = &mc->mc_conns[candidate];
+       if (msc->msc_ld == NULL || !META_IS_CANDIDATE( &candidates[ candidate ] )) {
+               return LDAP_SUCCESS;
+       }
+       if ( msc->conn == NULL) {
+               ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+               if (s < 0) {
+                       /* Todo a meaningful log pls */
+                       return LDAP_OTHER;
+               }
+               msc->conn = connection_client_setup( s, asyncmeta_op_handle_result, mc );
+       }
+       connection_client_enable( msc->conn );
+       return LDAP_SUCCESS;
+}
diff --git a/servers/slapd/back-asyncmeta/delete.c b/servers/slapd/back-asyncmeta/delete.c
new file mode 100644 (file)
index 0000000..5a0bbe0
--- /dev/null
@@ -0,0 +1,240 @@
+/* delete.c - delete request handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
++ * This work was developed by Symas Corporation
++ * based on back-meta module for inclusion in OpenLDAP Software.
++ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+
+meta_search_candidate_t
+asyncmeta_back_delete_start(Operation *op,
+                           SlapReply *rs,
+                           a_metaconn_t *mc,
+                           bm_context_t *bc,
+                           int candidate)
+{
+       a_metainfo_t    *mi = mc->mc_info;
+       a_metatarget_t  *mt = mi->mi_targets[ candidate ];
+       struct berval   mdn = BER_BVNULL;
+       a_dncookie      dc;
+       int rc = 0, nretries = 1;
+       LDAPControl     **ctrls = NULL;
+       meta_search_candidate_t retcode = META_SEARCH_CANDIDATE;
+       BerElement *ber = NULL;
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+       SlapReply               *candidates = bc->candidates;
+       ber_int_t       msgid;
+
+       dc.target = mt;
+       dc.conn = op->o_conn;
+       dc.rs = rs;
+       dc.ctx = "deleteDN";
+
+       if ( asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn ) ) {
+               rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+               retcode = META_SEARCH_ERR;
+               goto doreturn;
+       }
+
+retry:;
+       ctrls = op->o_ctrls;
+       if ( asyncmeta_controls_add( op, rs, mc, candidate, &ctrls ) != LDAP_SUCCESS )
+       {
+               candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+               retcode = META_SEARCH_ERR;
+               goto done;
+       }
+
+       ber = ldap_build_delete_req( msc->msc_ld, mdn.bv_val, ctrls, NULL, &msgid);
+       if (ber) {
+               candidates[ candidate ].sr_msgid = msgid;
+               rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_DELETE,
+                                               mdn.bv_val, ber, msgid );
+               if (rc == msgid)
+                       rc = LDAP_SUCCESS;
+               else
+                       rc = LDAP_SERVER_DOWN;
+
+               switch ( rc ) {
+               case LDAP_SUCCESS:
+                       retcode = META_SEARCH_CANDIDATE;
+                       asyncmeta_set_msc_time(msc);
+                       break;
+
+               case LDAP_SERVER_DOWN:
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_clear_one_msc(NULL, mc, candidate);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       if ( nretries && asyncmeta_retry( op, rs, &mc, candidate, LDAP_BACK_DONTSEND ) ) {
+                               nretries = 0;
+                               /* if the identity changed, there might be need to re-authz */
+                               (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+                               goto retry;
+                       }
+
+               default:
+                       candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+                       retcode = META_SEARCH_ERR;
+               }
+       }
+done:
+       (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+       if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+               free( mdn.bv_val );
+               BER_BVZERO( &mdn );
+       }
+
+doreturn:;
+       Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_delete_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid );
+       return retcode;
+}
+
+int
+asyncmeta_back_delete( Operation *op, SlapReply *rs )
+{
+       a_metainfo_t    *mi = ( a_metainfo_t * )op->o_bd->be_private;
+       a_metatarget_t  *mt;
+       a_metaconn_t    *mc;
+       int             rc, candidate = -1;
+       OperationBuffer opbuf;
+       bm_context_t *bc;
+       SlapReply *candidates;
+       slap_callback *cb = op->o_callback;
+
+       Debug(LDAP_DEBUG_ARGS, "==> asyncmeta_back_delete: %s\n",
+             op->o_req_dn.bv_val, 0, 0 );
+
+       asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets );
+       if (bc == NULL) {
+               rs->sr_err = LDAP_OTHER;
+               asyncmeta_sender_error(op, rs, cb);
+               return rs->sr_err;
+       }
+
+       candidates = bc->candidates;
+       mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0);
+       if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+               asyncmeta_sender_error(op, rs, cb);
+               asyncmeta_clear_bm_context(bc);
+               return rs->sr_err;
+       }
+
+       mt = mi->mi_targets[ candidate ];
+       bc->timeout = mt->mt_timeout[ SLAP_OP_DELETE ];
+       bc->retrying = LDAP_BACK_RETRYING;
+       bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying );
+       bc->stoptime = op->o_time + bc->timeout;
+
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+       rc = asyncmeta_add_message_queue(mc, bc);
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+       if (rc != LDAP_SUCCESS) {
+               rs->sr_err = LDAP_BUSY;
+               rs->sr_text = "Maximum pending ops limit exceeded";
+               asyncmeta_clear_bm_context(bc);
+               asyncmeta_sender_error(op, rs, cb);
+               goto finish;
+       }
+
+       rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate);
+       switch (rc)
+       {
+       case META_SEARCH_CANDIDATE:
+               /* target is already bound, just send the request */
+               Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_delete:  "
+                      "cnd=\"%ld\"\n", op->o_log_prefix, candidate , 0);
+
+               rc = asyncmeta_back_delete_start( op, rs, mc, bc, candidate);
+               if (rc == META_SEARCH_ERR) {
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_drop_bc(mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       asyncmeta_sender_error(op, rs, cb);
+                       asyncmeta_clear_bm_context(bc);
+                       goto finish;
+
+               }
+                       break;
+       case META_SEARCH_NOT_CANDIDATE:
+               Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_delete: NOT_CANDIDATE "
+                      "cnd=\"%ld\"\n", op->o_log_prefix, candidate , 0);
+               candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+               ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+               asyncmeta_drop_bc(mc, bc);
+               ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+               asyncmeta_sender_error(op, rs, cb);
+               asyncmeta_clear_bm_context(bc);
+               goto finish;
+
+       case META_SEARCH_NEED_BIND:
+       case META_SEARCH_CONNECTING:
+               Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_delete: NEED_BIND "
+                      "cnd=\"%ld\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+               rc = asyncmeta_dobind_init(op, rs, bc, mc, candidate);
+               if (rc == META_SEARCH_ERR) {
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_drop_bc(mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       asyncmeta_sender_error(op, rs, cb);
+                       asyncmeta_clear_bm_context(bc);
+                       goto finish;
+               }
+               break;
+       case META_SEARCH_BINDING:
+                       Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_delete: BINDING "
+                              "cnd=\"%ld\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+                       /* Todo add the context to the message queue but do not send the request
+                          the receiver must send this when we are done binding */
+                       /* question - how would do receiver know to which targets??? */
+                       break;
+
+       case META_SEARCH_ERR:
+                       Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_delete: ERR "
+                              "cnd=\"%ldd\"\n", op->o_log_prefix, candidate , 0);
+                       candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+                       candidates[ candidate ].sr_type = REP_RESULT;
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_drop_bc(mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       asyncmeta_sender_error(op, rs, cb);
+                       asyncmeta_clear_bm_context(bc);
+                       goto finish;
+               default:
+                       assert( 0 );
+                       break;
+               }
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+       asyncmeta_start_one_listener(mc, candidates, candidate);
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+finish:
+       return rs->sr_err;
+}
diff --git a/servers/slapd/back-asyncmeta/dncache.c b/servers/slapd/back-asyncmeta/dncache.c
new file mode 100644 (file)
index 0000000..26247ca
--- /dev/null
@@ -0,0 +1,228 @@
+/* dncache.c - dn caching for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+/*
+ * The dncache, at present, maps an entry to the target that holds it.
+ */
+
+typedef struct metadncacheentry_t {
+       struct berval   dn;
+       int             target;
+
+       time_t          lastupdated;
+} metadncacheentry_t;
+
+/*
+ * asyncmeta_dncache_cmp
+ *
+ * compares two struct metadncacheentry; used by avl stuff
+ * FIXME: modify avl stuff to delete an entry based on cmp
+ * (e.g. when ttl expired?)
+ */
+int
+asyncmeta_dncache_cmp(
+       const void      *c1,
+       const void      *c2 )
+{
+       metadncacheentry_t      *cc1 = ( metadncacheentry_t * )c1;
+       metadncacheentry_t      *cc2 = ( metadncacheentry_t * )c2;
+
+       /*
+        * case sensitive, because the dn MUST be normalized
+        */
+       return ber_bvcmp( &cc1->dn, &cc2->dn);
+}
+
+/*
+ * asyncmeta_dncache_dup
+ *
+ * returns -1 in case a duplicate struct metadncacheentry has been inserted;
+ * used by avl stuff
+ */
+int
+asyncmeta_dncache_dup(
+       void            *c1,
+       void            *c2 )
+{
+       metadncacheentry_t      *cc1 = ( metadncacheentry_t * )c1;
+       metadncacheentry_t      *cc2 = ( metadncacheentry_t * )c2;
+
+       /*
+        * case sensitive, because the dn MUST be normalized
+        */
+       return ( ber_bvcmp( &cc1->dn, &cc2->dn ) == 0 ) ? -1 : 0;
+}
+
+/*
+ * asyncmeta_dncache_get_target
+ *
+ * returns the target a dn belongs to, or -1 in case the dn is not
+ * in the cache
+ */
+int
+asyncmeta_dncache_get_target(
+       a_metadncache_t *cache,
+       struct berval   *ndn )
+{
+       metadncacheentry_t      tmp_entry,
+                               *entry;
+       int                     target = META_TARGET_NONE;
+
+       assert( cache != NULL );
+       assert( ndn != NULL );
+
+       tmp_entry.dn = *ndn;
+       ldap_pvt_thread_mutex_lock( &cache->mutex );
+       entry = ( metadncacheentry_t * )avl_find( cache->tree,
+                       ( caddr_t )&tmp_entry, asyncmeta_dncache_cmp );
+
+       if ( entry != NULL ) {
+
+               /*
+                * if cache->ttl < 0, cache never expires;
+                * if cache->ttl = 0 no cache is used; shouldn't get here
+                * else, cache is used with ttl
+                */
+               if ( cache->ttl < 0 ) {
+                       target = entry->target;
+
+               } else {
+                       if ( entry->lastupdated+cache->ttl > slap_get_time() ) {
+                               target = entry->target;
+                       }
+               }
+       }
+       ldap_pvt_thread_mutex_unlock( &cache->mutex );
+
+       return target;
+}
+
+/*
+ * asyncmeta_dncache_update_entry
+ *
+ * updates target and lastupdated of a struct metadncacheentry if exists,
+ * otherwise it gets created; returns -1 in case of error
+ */
+int
+asyncmeta_dncache_update_entry(
+       a_metadncache_t *cache,
+       struct berval   *ndn,
+       int             target )
+{
+       metadncacheentry_t      *entry,
+                               tmp_entry;
+       time_t                  curr_time = 0L;
+       int                     err = 0;
+
+       assert( cache != NULL );
+       assert( ndn != NULL );
+
+       /*
+        * if cache->ttl < 0, cache never expires;
+        * if cache->ttl = 0 no cache is used; shouldn't get here
+        * else, cache is used with ttl
+        */
+       if ( cache->ttl > 0 ) {
+               curr_time = slap_get_time();
+       }
+
+       tmp_entry.dn = *ndn;
+
+       ldap_pvt_thread_mutex_lock( &cache->mutex );
+       entry = ( metadncacheentry_t * )avl_find( cache->tree,
+                       ( caddr_t )&tmp_entry, asyncmeta_dncache_cmp );
+
+       if ( entry != NULL ) {
+               entry->target = target;
+               entry->lastupdated = curr_time;
+
+       } else {
+               entry = ch_malloc( sizeof( metadncacheentry_t ) + ndn->bv_len + 1 );
+               if ( entry == NULL ) {
+                       err = -1;
+                       goto error_return;
+               }
+
+               entry->dn.bv_len = ndn->bv_len;
+               entry->dn.bv_val = (char *)&entry[ 1 ];
+               AC_MEMCPY( entry->dn.bv_val, ndn->bv_val, ndn->bv_len );
+               entry->dn.bv_val[ ndn->bv_len ] = '\0';
+
+               entry->target = target;
+               entry->lastupdated = curr_time;
+
+               err = avl_insert( &cache->tree, ( caddr_t )entry,
+                               asyncmeta_dncache_cmp, asyncmeta_dncache_dup );
+       }
+
+error_return:;
+       ldap_pvt_thread_mutex_unlock( &cache->mutex );
+
+       return err;
+}
+
+int
+asyncmeta_dncache_delete_entry(
+       a_metadncache_t *cache,
+       struct berval   *ndn )
+{
+       metadncacheentry_t      *entry,
+                               tmp_entry;
+
+       assert( cache != NULL );
+       assert( ndn != NULL );
+
+       tmp_entry.dn = *ndn;
+
+       ldap_pvt_thread_mutex_lock( &cache->mutex );
+       entry = avl_delete( &cache->tree, ( caddr_t )&tmp_entry,
+                       asyncmeta_dncache_cmp );
+       ldap_pvt_thread_mutex_unlock( &cache->mutex );
+
+       if ( entry != NULL ) {
+               asyncmeta_dncache_free( ( void * )entry );
+       }
+
+       return 0;
+}
+
+/*
+ * meta_dncache_free
+ *
+ * frees an entry
+ *
+ */
+void
+asyncmeta_dncache_free(
+       void            *e )
+{
+       free( e );
+}
diff --git a/servers/slapd/back-asyncmeta/init.c b/servers/slapd/back-asyncmeta/init.c
new file mode 100644 (file)
index 0000000..567cf49
--- /dev/null
@@ -0,0 +1,509 @@
+/* init.c - initialization of a back-asyncmeta database */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "config.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+int
+asyncmeta_back_open(
+       BackendInfo     *bi )
+{
+       /* FIXME: need to remove the pagedResults, and likely more... */
+       bi->bi_controls = slap_known_controls;
+
+       return 0;
+}
+
+int
+asyncmeta_back_initialize(
+       BackendInfo     *bi )
+{
+       bi->bi_flags =
+#if 0
+       /* this is not (yet) set essentially because back-meta does not
+        * directly support extended operations... */
+#ifdef LDAP_DYNAMIC_OBJECTS
+               /* this is set because all the support a proxy has to provide
+                * is the capability to forward the refresh exop, and to
+                * pass thru entries that contain the dynamicObject class
+                * and the entryTtl attribute */
+               SLAP_BFLAG_DYNAMIC |
+#endif /* LDAP_DYNAMIC_OBJECTS */
+#endif
+
+               /* back-meta recognizes RFC4525 increment;
+                * let the remote server complain, if needed (ITS#5912) */
+               SLAP_BFLAG_INCREMENT;
+
+       bi->bi_open = asyncmeta_back_open;
+       bi->bi_config = 0;
+       bi->bi_close = 0;
+       bi->bi_destroy = 0;
+
+       bi->bi_db_init = asyncmeta_back_db_init;
+       bi->bi_db_config = config_generic_wrapper;
+       bi->bi_db_open = asyncmeta_back_db_open;
+       bi->bi_db_close = asyncmeta_back_db_close;
+       bi->bi_db_destroy = asyncmeta_back_db_destroy;
+
+       bi->bi_op_bind = asyncmeta_back_bind;
+       bi->bi_op_unbind = 0;
+       bi->bi_op_search = asyncmeta_back_search;
+       bi->bi_op_compare = asyncmeta_back_compare;
+       bi->bi_op_modify = asyncmeta_back_modify;
+       bi->bi_op_modrdn = asyncmeta_back_modrdn;
+       bi->bi_op_add = asyncmeta_back_add;
+       bi->bi_op_delete = asyncmeta_back_delete;
+       bi->bi_op_abandon = 0;
+
+       bi->bi_extended = 0;
+
+       bi->bi_chk_referrals = 0;
+
+       bi->bi_connection_init = 0;
+       bi->bi_connection_destroy = asyncmeta_back_conn_destroy;
+
+       return asyncmeta_back_init_cf( bi );
+}
+
+int
+asyncmeta_back_db_init(
+       Backend         *be,
+       ConfigReply     *cr)
+{
+       a_metainfo_t    *mi;
+       int             i;
+       BackendInfo     *bi;
+
+       bi = backend_info( "ldap" );
+       if ( !bi || !bi->bi_extra ) {
+               Debug( LDAP_DEBUG_ANY,
+                       "asyncmeta_back_db_init: needs back-ldap\n",
+                       0, 0, 0 );
+               return 1;
+       }
+
+       mi = ch_calloc( 1, sizeof( a_metainfo_t ) );
+       if ( mi == NULL ) {
+               return -1;
+       }
+
+       /* set default flags */
+       mi->mi_flags =
+               META_BACK_F_DEFER_ROOTDN_BIND
+               | META_BACK_F_PROXYAUTHZ_ALWAYS
+               | META_BACK_F_PROXYAUTHZ_ANON
+               | META_BACK_F_PROXYAUTHZ_NOANON;
+
+       /*
+        * At present the default is no default target;
+        * this may change
+        */
+       mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE;
+       mi->mi_bind_timeout.tv_sec = 0;
+       mi->mi_bind_timeout.tv_usec = META_BIND_TIMEOUT;
+
+       mi->mi_rebind_f = asyncmeta_back_default_rebind;
+       mi->mi_urllist_f = asyncmeta_back_default_urllist;
+
+       ldap_pvt_thread_mutex_init( &mi->mi_cache.mutex );
+
+       /* safe default */
+       mi->mi_nretries = META_RETRY_DEFAULT;
+       mi->mi_version = LDAP_VERSION3;
+
+       for ( i = LDAP_BACK_PCONN_FIRST; i < LDAP_BACK_PCONN_LAST; i++ ) {
+               mi->mi_conn_priv[ i ].mic_num = 0;
+               LDAP_TAILQ_INIT( &mi->mi_conn_priv[ i ].mic_priv );
+       }
+       mi->mi_conn_priv_max = LDAP_BACK_CONN_PRIV_DEFAULT;
+
+       mi->mi_ldap_extra = (ldap_extra_t *)bi->bi_extra;
+       ldap_pvt_thread_mutex_init( &mi->mi_mc_mutex);
+
+       be->be_private = mi;
+       be->be_cf_ocs = be->bd_info->bi_cf_ocs;
+
+       return 0;
+}
+
+int
+asyncmeta_target_finish(
+       a_metainfo_t *mi,
+       a_metatarget_t *mt,
+       const char *log,
+       char *msg,
+       size_t msize
+)
+{
+       slap_bindconf   sb = { BER_BVNULL };
+       struct berval mapped;
+       int rc;
+       int msc_num, i;
+
+       ber_str2bv( mt->mt_uri, 0, 0, &sb.sb_uri );
+       sb.sb_version = mt->mt_version;
+       sb.sb_method = LDAP_AUTH_SIMPLE;
+       BER_BVSTR( &sb.sb_binddn, "" );
+
+       if ( META_BACK_TGT_T_F_DISCOVER( mt ) ) {
+               rc = slap_discover_feature( &sb,
+                               slap_schema.si_ad_supportedFeatures->ad_cname.bv_val,
+                               LDAP_FEATURE_ABSOLUTE_FILTERS );
+               if ( rc == LDAP_COMPARE_TRUE ) {
+                       mt->mt_flags |= LDAP_BACK_F_T_F;
+               }
+       }
+
+       if ( META_BACK_TGT_CANCEL_DISCOVER( mt ) ) {
+               rc = slap_discover_feature( &sb,
+                               slap_schema.si_ad_supportedExtension->ad_cname.bv_val,
+                               LDAP_EXOP_CANCEL );
+               if ( rc == LDAP_COMPARE_TRUE ) {
+                       mt->mt_flags |= LDAP_BACK_F_CANCEL_EXOP;
+               }
+       }
+
+       if ( !( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE )
+               || mt->mt_idassert_authz != NULL )
+       {
+               mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_ALWAYS;
+       }
+
+       if ( ( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL )
+               && !( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) )
+       {
+               snprintf( msg, msize,
+                       "%s: inconsistent idassert configuration "
+                       "(likely authz=\"*\" used with \"non-prescriptive\" flag)",
+                       log );
+               Debug( LDAP_DEBUG_ANY, "%s (target %s)\n",
+                       msg, mt->mt_uri, 0 );
+               return 1;
+       }
+
+       if ( !( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) )
+       {
+               mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_ANON;
+       }
+
+       if ( ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) )
+       {
+               mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_NOANON;
+       }
+
+       BER_BVZERO( &mapped );
+       asyncmeta_map( &mt->mt_rwmap.rwm_at,
+               &slap_schema.si_ad_entryDN->ad_cname, &mapped,
+               BACKLDAP_REMAP );
+       if ( BER_BVISNULL( &mapped ) || mapped.bv_val[0] == '\0' ) {
+               mt->mt_rep_flags |= REP_NO_ENTRYDN;
+       }
+
+       BER_BVZERO( &mapped );
+       asyncmeta_map( &mt->mt_rwmap.rwm_at,
+               &slap_schema.si_ad_subschemaSubentry->ad_cname, &mapped,
+               BACKLDAP_REMAP );
+       if ( BER_BVISNULL( &mapped ) || mapped.bv_val[0] == '\0' ) {
+               mt->mt_rep_flags |= REP_NO_SUBSCHEMA;
+       }
+       return 0;
+}
+
+int
+asyncmeta_back_db_open(
+       Backend         *be,
+       ConfigReply     *cr )
+{
+       a_metainfo_t    *mi = (a_metainfo_t *)be->be_private;
+
+       char msg[SLAP_TEXT_BUFLEN];
+
+       int             i, rc;
+
+       if ( mi->mi_ntargets == 0 ) {
+               /* Dynamically added, nothing to check here until
+                * some targets get added
+                */
+               if ( slapMode & SLAP_SERVER_RUNNING )
+                       return 0;
+
+               Debug( LDAP_DEBUG_ANY,
+                       "asyncmeta_back_db_open: no targets defined\n",
+                       0, 0, 0 );
+               return 1;
+       }
+       mi->mi_num_conns = 0;
+       for ( i = 0; i < mi->mi_ntargets; i++ ) {
+               a_metatarget_t  *mt = mi->mi_targets[ i ];
+               if ( asyncmeta_target_finish( mi, mt,
+                       "asyncmeta_back_db_open", msg, sizeof( msg )))
+                       return 1;
+       }
+       mi->mi_num_conns = (mi->mi_max_target_conns == 0) ? META_BACK_CFG_MAX_TARGET_CONNS : mi->mi_max_target_conns;
+       assert(mi->mi_num_conns > 0);
+       mi->mi_conns = ch_calloc( mi->mi_num_conns, sizeof( a_metaconn_t ));
+       for (i = 0; i < mi->mi_num_conns; i++) {
+               a_metaconn_t *mc = &mi->mi_conns[i];
+               ldap_pvt_thread_mutex_init( &mc->mc_om_mutex);
+               mc->mc_authz_target = META_BOUND_NONE;
+               mc->mc_conns = ch_calloc( mi->mi_ntargets, sizeof( a_metasingleconn_t ));
+               mc->mc_info = mi;
+       }
+
+       ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+       mi->mi_task = ldap_pvt_runqueue_insert( &slapd_rq, 0,
+               asyncmeta_timeout_loop, mi, "asyncmeta_timeout_loop", be->be_suffix[0].bv_val );
+       ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+       return 0;
+}
+
+/*
+ * asyncmeta_back_conn_free()
+ *
+ * actually frees a connection; the reference count must be 0,
+ * and it must not (or no longer) be in the cache.
+ */
+void
+asyncmeta_back_conn_free(
+       void            *v_mc )
+{
+       a_metaconn_t            *mc = v_mc;
+
+       assert( mc != NULL );
+       ldap_pvt_thread_mutex_destroy( &mc->mc_om_mutex );
+       free( mc );
+}
+
+static void
+mapping_free(
+       void            *v_mapping )
+{
+       struct ldapmapping *mapping = v_mapping;
+       ch_free( mapping->src.bv_val );
+       ch_free( mapping->dst.bv_val );
+       ch_free( mapping );
+}
+
+static void
+mapping_dst_free(
+       void            *v_mapping )
+{
+       struct ldapmapping *mapping = v_mapping;
+
+       if ( BER_BVISEMPTY( &mapping->dst ) ) {
+               mapping_free( &mapping[ -1 ] );
+       }
+}
+
+void
+asyncmeta_back_map_free( struct ldapmap *lm )
+{
+       avl_free( lm->remap, mapping_dst_free );
+       avl_free( lm->map, mapping_free );
+       lm->remap = NULL;
+       lm->map = NULL;
+}
+
+static void
+asyncmeta_back_stop_miconns( a_metainfo_t *mi )
+{
+
+       /*Todo do any other mc cleanup here if necessary*/
+}
+
+static void
+asyncmeta_back_clear_miconns( a_metainfo_t *mi )
+{
+       int i, j;
+       a_metaconn_t *mc;
+       for (i = 0; i < mi->mi_num_conns; i++) {
+               mc = &mi->mi_conns[i];
+               /* todo clear the message queue */
+               for (j = 0; j < mi->mi_ntargets; j ++) {
+                       asyncmeta_clear_one_msc(NULL, mc, j);
+               }
+               free(mc->mc_conns);
+               ldap_pvt_thread_mutex_destroy( &mc->mc_om_mutex );
+       }
+       free(mi->mi_conns);
+}
+
+static void
+asyncmeta_target_free(
+       a_metatarget_t  *mt )
+{
+       if ( mt->mt_uri ) {
+               free( mt->mt_uri );
+               ldap_pvt_thread_mutex_destroy( &mt->mt_uri_mutex );
+       }
+       if ( mt->mt_subtree ) {
+               asyncmeta_subtree_destroy( mt->mt_subtree );
+               mt->mt_subtree = NULL;
+       }
+       if ( mt->mt_filter ) {
+               asyncmeta_filter_destroy( mt->mt_filter );
+               mt->mt_filter = NULL;
+       }
+       if ( !BER_BVISNULL( &mt->mt_psuffix ) ) {
+               free( mt->mt_psuffix.bv_val );
+       }
+       if ( !BER_BVISNULL( &mt->mt_nsuffix ) ) {
+               free( mt->mt_nsuffix.bv_val );
+       }
+       if ( !BER_BVISNULL( &mt->mt_binddn ) ) {
+               free( mt->mt_binddn.bv_val );
+       }
+       if ( !BER_BVISNULL( &mt->mt_bindpw ) ) {
+               free( mt->mt_bindpw.bv_val );
+       }
+       if ( !BER_BVISNULL( &mt->mt_idassert_authcID ) ) {
+               ch_free( mt->mt_idassert_authcID.bv_val );
+       }
+       if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) ) {
+               ch_free( mt->mt_idassert_authcDN.bv_val );
+       }
+       if ( !BER_BVISNULL( &mt->mt_idassert_passwd ) ) {
+               ch_free( mt->mt_idassert_passwd.bv_val );
+       }
+       if ( !BER_BVISNULL( &mt->mt_idassert_authzID ) ) {
+               ch_free( mt->mt_idassert_authzID.bv_val );
+       }
+       if ( !BER_BVISNULL( &mt->mt_idassert_sasl_mech ) ) {
+               ch_free( mt->mt_idassert_sasl_mech.bv_val );
+       }
+       if ( !BER_BVISNULL( &mt->mt_idassert_sasl_realm ) ) {
+               ch_free( mt->mt_idassert_sasl_realm.bv_val );
+       }
+       if ( mt->mt_idassert_authz != NULL ) {
+               ber_bvarray_free( mt->mt_idassert_authz );
+       }
+       if ( mt->mt_rwmap.rwm_rw ) {
+               rewrite_info_delete( &mt->mt_rwmap.rwm_rw );
+               if ( mt->mt_rwmap.rwm_bva_rewrite )
+                       ber_bvarray_free( mt->mt_rwmap.rwm_bva_rewrite );
+       }
+       asyncmeta_back_map_free( &mt->mt_rwmap.rwm_oc );
+       asyncmeta_back_map_free( &mt->mt_rwmap.rwm_at );
+       ber_bvarray_free( mt->mt_rwmap.rwm_bva_map );
+       free( mt );
+}
+
+int
+asyncmeta_back_db_close(
+       Backend         *be,
+       ConfigReply     *cr )
+{
+       a_metainfo_t    *mi;
+
+       if ( be->be_private ) {
+               int i;
+
+               mi = ( a_metainfo_t * )be->be_private;
+               if ( mi->mi_task != NULL ) {
+                       ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+                       if ( ldap_pvt_runqueue_isrunning( &slapd_rq, mi->mi_task )) {
+                               ldap_pvt_runqueue_stoptask( &slapd_rq,  mi->mi_task);
+                       }
+                       ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+                       mi->mi_task = NULL;
+               }
+               ldap_pvt_thread_mutex_lock( &mi->mi_mc_mutex );
+               asyncmeta_back_stop_miconns( mi );
+               ldap_pvt_thread_mutex_unlock( &mi->mi_mc_mutex );
+       }
+}
+
+int
+asyncmeta_back_db_destroy(
+       Backend         *be,
+       ConfigReply     *cr )
+{
+       a_metainfo_t    *mi;
+
+       if ( be->be_private ) {
+               int i;
+
+               mi = ( a_metainfo_t * )be->be_private;
+               /*
+                * Destroy the per-target stuff (assuming there's at
+                * least one ...)
+                */
+               if ( mi->mi_targets != NULL ) {
+                       for ( i = 0; i < mi->mi_ntargets; i++ ) {
+                               a_metatarget_t  *mt = mi->mi_targets[ i ];
+
+                               if ( META_BACK_TGT_QUARANTINE( mt ) ) {
+                                       if ( mt->mt_quarantine.ri_num != mi->mi_quarantine.ri_num )
+                                       {
+                                               mi->mi_ldap_extra->retry_info_destroy( &mt->mt_quarantine );
+                                       }
+
+                                       ldap_pvt_thread_mutex_destroy( &mt->mt_quarantine_mutex );
+                               }
+
+                               asyncmeta_target_free( mt );
+                       }
+
+                       free( mi->mi_targets );
+               }
+
+               ldap_pvt_thread_mutex_lock( &mi->mi_cache.mutex );
+               if ( mi->mi_cache.tree ) {
+                       avl_free( mi->mi_cache.tree, asyncmeta_dncache_free );
+               }
+
+               ldap_pvt_thread_mutex_unlock( &mi->mi_cache.mutex );
+               ldap_pvt_thread_mutex_destroy( &mi->mi_cache.mutex );
+
+               if ( mi->mi_candidates != NULL ) {
+                       ber_memfree_x( mi->mi_candidates, NULL );
+               }
+
+               if ( META_BACK_QUARANTINE( mi ) ) {
+                       mi->mi_ldap_extra->retry_info_destroy( &mi->mi_quarantine );
+               }
+       }
+       ldap_pvt_thread_mutex_lock( &mi->mi_mc_mutex );
+       asyncmeta_back_clear_miconns(mi);
+       ldap_pvt_thread_mutex_unlock( &mi->mi_mc_mutex );
+       ldap_pvt_thread_mutex_destroy( &mi->mi_mc_mutex );
+
+       free( be->be_private );
+       return 0;
+}
+
+#if SLAPD_ASYNCMETA == SLAPD_MOD_DYNAMIC
+
+/* conditionally define the init_module() function */
+SLAP_BACKEND_INIT_MODULE( asyncmeta )
+
+#endif /* SLAPD_ASYNCMETA == SLAPD_MOD_DYNAMIC */
diff --git a/servers/slapd/back-asyncmeta/map.c b/servers/slapd/back-asyncmeta/map.c
new file mode 100644 (file)
index 0000000..8353b58
--- /dev/null
@@ -0,0 +1,874 @@
+/* map.c - ldap backend mapping routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+/* This is an altered version */
+/*
+ * Copyright 1999, Howard Chu, All rights reserved. <hyc@highlandsun.com>
+ *
+ * Permission is granted to anyone to use this software for any purpose
+ * on any computer system, and to alter it and redistribute it, subject
+ * to the following restrictions:
+ *
+ * 1. The author is not responsible for the consequences of use of this
+ *    software, no matter how awful, even if they arise from flaws in it.
+ *
+ * 2. The origin of this software must not be misrepresented, either by
+ *    explicit claim or by omission.  Since few users ever read sources,
+ *    credits should appear in the documentation.
+ *
+ * 3. Altered versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.  Since few users
+ *    ever read sources, credits should appear in the documentation.
+ *
+ * 4. This notice may not be removed or altered.
+ *
+ *
+ *
+ * Copyright 2016, Symas Corporation
+ *
+ * This is based on the back-meta/map.c version by Pierangelo Masarati.
+ * The previously reported conditions apply to the modified code as well.
+ * Changes in the original code are highlighted where required.
+ * Credits for the original code go to the author, Howard Chu.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+int
+asyncmeta_mapping_cmp ( const void *c1, const void *c2 )
+{
+       struct ldapmapping *map1 = (struct ldapmapping *)c1;
+       struct ldapmapping *map2 = (struct ldapmapping *)c2;
+       int rc = map1->src.bv_len - map2->src.bv_len;
+       if (rc) return rc;
+       return ( strcasecmp( map1->src.bv_val, map2->src.bv_val ) );
+}
+
+int
+asyncmeta_mapping_dup ( void *c1, void *c2 )
+{
+       struct ldapmapping *map1 = (struct ldapmapping *)c1;
+       struct ldapmapping *map2 = (struct ldapmapping *)c2;
+
+       return ( ( strcasecmp( map1->src.bv_val, map2->src.bv_val ) == 0 ) ? -1 : 0 );
+}
+
+void
+asyncmeta_map_init ( struct ldapmap *lm, struct ldapmapping **m )
+{
+       struct ldapmapping *mapping;
+
+       assert( m != NULL );
+
+       *m = NULL;
+
+       mapping = (struct ldapmapping *)ch_calloc( 2,
+                       sizeof( struct ldapmapping ) );
+       if ( mapping == NULL ) {
+               return;
+       }
+
+       ber_str2bv( "objectclass", STRLENOF("objectclass"), 1, &mapping[0].src);
+       ber_dupbv( &mapping[0].dst, &mapping[0].src );
+       mapping[1].src = mapping[0].src;
+       mapping[1].dst = mapping[0].dst;
+
+       avl_insert( &lm->map, (caddr_t)&mapping[0],
+                       asyncmeta_mapping_cmp, asyncmeta_mapping_dup );
+       avl_insert( &lm->remap, (caddr_t)&mapping[1],
+                       asyncmeta_mapping_cmp, asyncmeta_mapping_dup );
+       *m = mapping;
+}
+
+int
+asyncmeta_mapping ( struct ldapmap *map, struct berval *s, struct ldapmapping **m,
+       int remap )
+{
+       Avlnode *tree;
+       struct ldapmapping fmapping;
+
+       assert( m != NULL );
+
+       /* let special attrnames slip through (ITS#5760) */
+       if ( bvmatch( s, slap_bv_no_attrs )
+               || bvmatch( s, slap_bv_all_user_attrs )
+               || bvmatch( s, slap_bv_all_operational_attrs ) )
+       {
+               *m = NULL;
+               return 0;
+       }
+
+       if ( remap == BACKLDAP_REMAP ) {
+               tree = map->remap;
+
+       } else {
+               tree = map->map;
+       }
+
+       fmapping.src = *s;
+       *m = (struct ldapmapping *)avl_find( tree, (caddr_t)&fmapping, asyncmeta_mapping_cmp );
+       if ( *m == NULL ) {
+               return map->drop_missing;
+       }
+
+       return 0;
+}
+
+void
+asyncmeta_map ( struct ldapmap *map, struct berval *s, struct berval *bv,
+       int remap )
+{
+       struct ldapmapping *mapping;
+       int drop_missing;
+
+       /* map->map may be NULL when mapping is configured,
+        * but map->remap can't */
+       if ( map->remap == NULL ) {
+               *bv = *s;
+               return;
+       }
+
+       BER_BVZERO( bv );
+       drop_missing = asyncmeta_mapping( map, s, &mapping, remap );
+       if ( mapping != NULL ) {
+               if ( !BER_BVISNULL( &mapping->dst ) ) {
+                       *bv = mapping->dst;
+               }
+               return;
+       }
+
+       if ( !drop_missing ) {
+               *bv = *s;
+       }
+}
+
+int
+asyncmeta_map_attrs(
+               Operation *op,
+               struct ldapmap *at_map,
+               AttributeName *an,
+               int remap,
+               char ***mapped_attrs )
+{
+       int i, x, j;
+       char **na;
+       struct berval mapped;
+
+       if ( an == NULL && op->o_bd->be_extra_anlist == NULL ) {
+               *mapped_attrs = NULL;
+               return LDAP_SUCCESS;
+       }
+
+       i = 0;
+       if ( an != NULL ) {
+               for ( ; !BER_BVISNULL( &an[i].an_name ); i++ )
+                       /*  */ ;
+       }
+
+       x = 0;
+       if ( op->o_bd->be_extra_anlist != NULL ) {
+               for ( ; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ )
+                       /*  */ ;
+       }
+
+       assert( i > 0 || x > 0 );
+
+       na = (char **)ber_memcalloc_x( i + x + 1, sizeof(char *), op->o_tmpmemctx );
+       if ( na == NULL ) {
+               *mapped_attrs = NULL;
+               return LDAP_NO_MEMORY;
+       }
+
+       j = 0;
+       if ( i > 0 ) {
+               for ( i = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) {
+                       asyncmeta_map( at_map, &an[i].an_name, &mapped, remap );
+                       if ( !BER_BVISNULL( &mapped ) && !BER_BVISEMPTY( &mapped ) ) {
+                               na[j++] = mapped.bv_val;
+                       }
+               }
+       }
+
+       if ( x > 0 ) {
+               for ( x = 0; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ ) {
+                       if ( op->o_bd->be_extra_anlist[x].an_desc &&
+                               ad_inlist( op->o_bd->be_extra_anlist[x].an_desc, an ) )
+                       {
+                               continue;
+                       }
+
+                       asyncmeta_map( at_map, &op->o_bd->be_extra_anlist[x].an_name, &mapped, remap );
+                       if ( !BER_BVISNULL( &mapped ) && !BER_BVISEMPTY( &mapped ) ) {
+                               na[j++] = mapped.bv_val;
+                       }
+               }
+       }
+
+       if ( j == 0 && ( i > 0 || x > 0 ) ) {
+               na[j++] = LDAP_NO_ATTRS;
+       }
+       na[j] = NULL;
+
+       *mapped_attrs = na;
+
+       return LDAP_SUCCESS;
+}
+
+static int
+map_attr_value(
+               a_dncookie              *dc,
+               AttributeDescription    *ad,
+               struct berval           *mapped_attr,
+               struct berval           *value,
+               struct berval           *mapped_value,
+               int                     remap,
+               void                    *memctx )
+{
+       struct berval           vtmp;
+       int                     freeval = 0;
+
+       asyncmeta_map( &dc->target->mt_rwmap.rwm_at, &ad->ad_cname, mapped_attr, remap );
+       if ( BER_BVISNULL( mapped_attr ) || BER_BVISEMPTY( mapped_attr ) ) {
+#if 0
+               /*
+                * FIXME: are we sure we need to search oc_map if at_map fails?
+                */
+               asyncmeta_map( &dc->target->mt_rwmap.rwm_oc, &ad->ad_cname, mapped_attr, remap );
+               if ( BER_BVISNULL( mapped_attr ) || BER_BVISEMPTY( mapped_attr ) ) {
+                       *mapped_attr = ad->ad_cname;
+               }
+#endif
+               if ( dc->target->mt_rwmap.rwm_at.drop_missing ) {
+                       return -1;
+               }
+
+               *mapped_attr = ad->ad_cname;
+       }
+
+       if ( value == NULL ) {
+               return 0;
+       }
+
+       if ( ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName )
+       {
+               a_dncookie fdc = *dc;
+
+               fdc.ctx = "searchFilterAttrDN";
+
+               switch ( asyncmeta_dn_massage( &fdc, value, &vtmp ) ) {
+               case LDAP_SUCCESS:
+                       if ( vtmp.bv_val != value->bv_val ) {
+                               freeval = 1;
+                       }
+                       break;
+
+               case LDAP_UNWILLING_TO_PERFORM:
+                       return -1;
+
+               case LDAP_OTHER:
+                       return -1;
+               }
+
+       } else if ( ad->ad_type->sat_equality &&
+               ad->ad_type->sat_equality->smr_usage & SLAP_MR_MUTATION_NORMALIZER )
+       {
+               if ( ad->ad_type->sat_equality->smr_normalize(
+                       (SLAP_MR_DENORMALIZE|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX),
+                       NULL, NULL, value, &vtmp, memctx ) )
+               {
+                       return -1;
+               }
+               freeval = 2;
+
+       } else if ( ad == slap_schema.si_ad_objectClass || ad == slap_schema.si_ad_structuralObjectClass ) {
+               asyncmeta_map( &dc->target->mt_rwmap.rwm_oc, value, &vtmp, remap );
+               if ( BER_BVISNULL( &vtmp ) || BER_BVISEMPTY( &vtmp ) ) {
+                       vtmp = *value;
+               }
+
+       } else {
+               vtmp = *value;
+       }
+
+       filter_escape_value_x( &vtmp, mapped_value, memctx );
+
+       switch ( freeval ) {
+       case 1:
+               ber_memfree( vtmp.bv_val );
+               break;
+       case 2:
+               ber_memfree_x( vtmp.bv_val, memctx );
+               break;
+       }
+
+       return 0;
+}
+
+static int
+asyncmeta_int_filter_map_rewrite(
+               a_dncookie              *dc,
+               Filter                  *f,
+               struct berval   *fstr,
+               int                             remap,
+               void                    *memctx )
+{
+       int             i;
+       Filter          *p;
+       struct berval   atmp,
+                       vtmp,
+                       *tmp;
+       static struct berval
+                       /* better than nothing... */
+                       ber_bvfalse = BER_BVC( "(!(objectClass=*))" ),
+                       ber_bvtf_false = BER_BVC( "(|)" ),
+                       /* better than nothing... */
+                       ber_bvtrue = BER_BVC( "(objectClass=*)" ),
+                       ber_bvtf_true = BER_BVC( "(&)" ),
+#if 0
+                       /* no longer needed; preserved for completeness */
+                       ber_bvundefined = BER_BVC( "(?=undefined)" ),
+#endif
+                       ber_bverror = BER_BVC( "(?=error)" ),
+                       ber_bvunknown = BER_BVC( "(?=unknown)" ),
+                       ber_bvnone = BER_BVC( "(?=none)" );
+       ber_len_t       len;
+
+       assert( fstr != NULL );
+       BER_BVZERO( fstr );
+
+       if ( f == NULL ) {
+               ber_dupbv_x( fstr, &ber_bvnone, memctx );
+               return LDAP_OTHER;
+       }
+
+       switch ( ( f->f_choice & SLAPD_FILTER_MASK ) ) {
+       case LDAP_FILTER_EQUALITY:
+               if ( map_attr_value( dc, f->f_av_desc, &atmp,
+                                       &f->f_av_value, &vtmp, remap, memctx ) )
+               {
+                       goto computed;
+               }
+
+               fstr->bv_len = atmp.bv_len + vtmp.bv_len
+                       + ( sizeof("(=)") - 1 );
+               fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx );
+
+               snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=%s)",
+                       atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" );
+
+               ber_memfree_x( vtmp.bv_val, memctx );
+               break;
+
+       case LDAP_FILTER_GE:
+               if ( map_attr_value( dc, f->f_av_desc, &atmp,
+                                       &f->f_av_value, &vtmp, remap, memctx ) )
+               {
+                       goto computed;
+               }
+
+               fstr->bv_len = atmp.bv_len + vtmp.bv_len
+                       + ( sizeof("(>=)") - 1 );
+               fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx );
+
+               snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s>=%s)",
+                       atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" );
+
+               ber_memfree_x( vtmp.bv_val, memctx );
+               break;
+
+       case LDAP_FILTER_LE:
+               if ( map_attr_value( dc, f->f_av_desc, &atmp,
+                                       &f->f_av_value, &vtmp, remap, memctx ) )
+               {
+                       goto computed;
+               }
+
+               fstr->bv_len = atmp.bv_len + vtmp.bv_len
+                       + ( sizeof("(<=)") - 1 );
+               fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx );
+
+               snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s<=%s)",
+                       atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" );
+
+               ber_memfree_x( vtmp.bv_val, memctx );
+               break;
+
+       case LDAP_FILTER_APPROX:
+               if ( map_attr_value( dc, f->f_av_desc, &atmp,
+                                       &f->f_av_value, &vtmp, remap, memctx ) )
+               {
+                       goto computed;
+               }
+
+               fstr->bv_len = atmp.bv_len + vtmp.bv_len
+                       + ( sizeof("(~=)") - 1 );
+               fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx );
+
+               snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s~=%s)",
+                       atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" );
+
+               ber_memfree_x( vtmp.bv_val, memctx );
+               break;
+
+       case LDAP_FILTER_SUBSTRINGS:
+               if ( map_attr_value( dc, f->f_sub_desc, &atmp,
+                                       NULL, NULL, remap, memctx ) )
+               {
+                       goto computed;
+               }
+
+               /* cannot be a DN ... */
+
+               fstr->bv_len = atmp.bv_len + ( STRLENOF( "(=*)" ) );
+               fstr->bv_val = ber_memalloc_x( fstr->bv_len + 128, memctx ); /* FIXME: why 128 ? */
+
+               snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)",
+                       atmp.bv_val );
+
+               if ( !BER_BVISNULL( &f->f_sub_initial ) ) {
+                       len = fstr->bv_len;
+
+                       filter_escape_value_x( &f->f_sub_initial, &vtmp, memctx );
+
+                       fstr->bv_len += vtmp.bv_len;
+                       fstr->bv_val = ber_memrealloc_x( fstr->bv_val, fstr->bv_len + 1, memctx );
+
+                       snprintf( &fstr->bv_val[len - 2], vtmp.bv_len + 3,
+                               /* "(attr=" */ "%s*)",
+                               vtmp.bv_len ? vtmp.bv_val : "" );
+
+                       ber_memfree_x( vtmp.bv_val, memctx );
+               }
+
+               if ( f->f_sub_any != NULL ) {
+                       for ( i = 0; !BER_BVISNULL( &f->f_sub_any[i] ); i++ ) {
+                               len = fstr->bv_len;
+                               filter_escape_value_x( &f->f_sub_any[i], &vtmp, memctx );
+
+                               fstr->bv_len += vtmp.bv_len + 1;
+                               fstr->bv_val = ber_memrealloc_x( fstr->bv_val, fstr->bv_len + 1, memctx );
+
+                               snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3,
+                                       /* "(attr=[init]*[any*]" */ "%s*)",
+                                       vtmp.bv_len ? vtmp.bv_val : "" );
+                               ber_memfree_x( vtmp.bv_val, memctx );
+                       }
+               }
+
+               if ( !BER_BVISNULL( &f->f_sub_final ) ) {
+                       len = fstr->bv_len;
+
+                       filter_escape_value_x( &f->f_sub_final, &vtmp, memctx );
+
+                       fstr->bv_len += vtmp.bv_len;
+                       fstr->bv_val = ber_memrealloc_x( fstr->bv_val, fstr->bv_len + 1, memctx );
+
+                       snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3,
+                               /* "(attr=[init*][any*]" */ "%s)",
+                               vtmp.bv_len ? vtmp.bv_val : "" );
+
+                       ber_memfree_x( vtmp.bv_val, memctx );
+               }
+
+               break;
+
+       case LDAP_FILTER_PRESENT:
+               if ( map_attr_value( dc, f->f_desc, &atmp,
+                                       NULL, NULL, remap, memctx ) )
+               {
+                       goto computed;
+               }
+
+               fstr->bv_len = atmp.bv_len + ( STRLENOF( "(=*)" ) );
+               fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx );
+
+               snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)",
+                       atmp.bv_val );
+               break;
+
+       case LDAP_FILTER_AND:
+       case LDAP_FILTER_OR:
+       case LDAP_FILTER_NOT:
+               fstr->bv_len = STRLENOF( "(%)" );
+               fstr->bv_val = ber_memalloc_x( fstr->bv_len + 128, memctx );    /* FIXME: why 128? */
+
+               snprintf( fstr->bv_val, fstr->bv_len + 1, "(%c)",
+                       f->f_choice == LDAP_FILTER_AND ? '&' :
+                       f->f_choice == LDAP_FILTER_OR ? '|' : '!' );
+
+               for ( p = f->f_list; p != NULL; p = p->f_next ) {
+                       int     rc;
+
+                       len = fstr->bv_len;
+
+                       rc = asyncmeta_int_filter_map_rewrite( dc, p, &vtmp, remap, memctx );
+                       if ( rc != LDAP_SUCCESS ) {
+                               return rc;
+                       }
+
+                       fstr->bv_len += vtmp.bv_len;
+                       fstr->bv_val = ber_memrealloc_x( fstr->bv_val, fstr->bv_len + 1, memctx );
+
+                       snprintf( &fstr->bv_val[len-1], vtmp.bv_len + 2,
+                               /*"("*/ "%s)", vtmp.bv_len ? vtmp.bv_val : "" );
+
+                       ber_memfree_x( vtmp.bv_val, memctx );
+               }
+
+               break;
+
+       case LDAP_FILTER_EXT:
+               if ( f->f_mr_desc ) {
+                       if ( map_attr_value( dc, f->f_mr_desc, &atmp,
+                                               &f->f_mr_value, &vtmp, remap, memctx ) )
+                       {
+                               goto computed;
+                       }
+
+               } else {
+                       BER_BVSTR( &atmp, "" );
+                       filter_escape_value_x( &f->f_mr_value, &vtmp, memctx );
+               }
+
+               /* FIXME: cleanup (less ?: operators...) */
+               fstr->bv_len = atmp.bv_len +
+                       ( f->f_mr_dnattrs ? STRLENOF( ":dn" ) : 0 ) +
+                       ( !BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_len + 1 : 0 ) +
+                       vtmp.bv_len + ( STRLENOF( "(:=)" ) );
+               fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx );
+
+               snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s:=%s)",
+                       atmp.bv_val,
+                       f->f_mr_dnattrs ? ":dn" : "",
+                       !BER_BVISEMPTY( &f->f_mr_rule_text ) ? ":" : "",
+                       !BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_val : "",
+                       vtmp.bv_len ? vtmp.bv_val : "" );
+               ber_memfree_x( vtmp.bv_val, memctx );
+               break;
+
+       case SLAPD_FILTER_COMPUTED:
+               switch ( f->f_result ) {
+               /* FIXME: treat UNDEFINED as FALSE */
+               case SLAPD_COMPARE_UNDEFINED:
+computed:;
+                       if ( META_BACK_TGT_NOUNDEFFILTER( dc->target ) ) {
+                               return LDAP_COMPARE_FALSE;
+                       }
+                       /* fallthru */
+
+               case LDAP_COMPARE_FALSE:
+                       if ( META_BACK_TGT_T_F( dc->target ) ) {
+                               tmp = &ber_bvtf_false;
+                               break;
+                       }
+                       tmp = &ber_bvfalse;
+                       break;
+
+               case LDAP_COMPARE_TRUE:
+                       if ( META_BACK_TGT_T_F( dc->target ) ) {
+                               tmp = &ber_bvtf_true;
+                               break;
+                       }
+
+                       tmp = &ber_bvtrue;
+                       break;
+
+               default:
+                       tmp = &ber_bverror;
+                       break;
+               }
+
+               ber_dupbv_x( fstr, tmp, memctx );
+               break;
+
+       default:
+               ber_dupbv_x( fstr, &ber_bvunknown, memctx );
+               break;
+       }
+
+       return 0;
+}
+
+int
+asyncmeta_filter_map_rewrite(
+               a_dncookie              *dc,
+               Filter                  *f,
+               struct berval   *fstr,
+               int                             remap,
+               void                    *memctx )
+{
+       int             rc;
+       a_dncookie      fdc;
+       struct berval   ftmp;
+       static char     *dmy = "";
+
+       rc = asyncmeta_int_filter_map_rewrite( dc, f, fstr, remap, memctx );
+
+       if ( rc != LDAP_SUCCESS ) {
+               return rc;
+       }
+
+       fdc = *dc;
+       ftmp = *fstr;
+
+       fdc.ctx = "searchFilter";
+
+       switch ( rewrite_session( fdc.target->mt_rwmap.rwm_rw, fdc.ctx,
+                               ( !BER_BVISEMPTY( &ftmp ) ? ftmp.bv_val : dmy ),
+                               fdc.conn, &fstr->bv_val ) )
+       {
+       case REWRITE_REGEXEC_OK:
+               if ( !BER_BVISNULL( fstr ) ) {
+                       fstr->bv_len = strlen( fstr->bv_val );
+
+               } else {
+                       *fstr = ftmp;
+               }
+               Debug( LDAP_DEBUG_ARGS,
+                       "[rw] %s: \"%s\" -> \"%s\"\n",
+                       fdc.ctx, BER_BVISNULL( &ftmp ) ? "" : ftmp.bv_val,
+                       BER_BVISNULL( fstr ) ? "" : fstr->bv_val );
+               rc = LDAP_SUCCESS;
+               break;
+
+       case REWRITE_REGEXEC_UNWILLING:
+               if ( fdc.rs ) {
+                       fdc.rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+                       fdc.rs->sr_text = "Operation not allowed";
+               }
+               rc = LDAP_UNWILLING_TO_PERFORM;
+               break;
+
+       case REWRITE_REGEXEC_ERR:
+               if ( fdc.rs ) {
+                       fdc.rs->sr_err = LDAP_OTHER;
+                       fdc.rs->sr_text = "Rewrite error";
+               }
+               rc = LDAP_OTHER;
+               break;
+       }
+
+       if ( fstr->bv_val == dmy ) {
+               BER_BVZERO( fstr );
+
+       } else if ( fstr->bv_val != ftmp.bv_val ) {
+               /* NOTE: need to realloc mapped filter on slab
+                * and free the original one, until librewrite
+                * becomes slab-aware
+                */
+               ber_dupbv_x( &ftmp, fstr, memctx );
+               ch_free( fstr->bv_val );
+               *fstr = ftmp;
+       }
+
+       return rc;
+}
+
+int
+asyncmeta_referral_result_rewrite(
+       a_dncookie              *dc,
+       BerVarray               a_vals,
+       void                    *memctx
+)
+{
+       int             i, last;
+
+       assert( dc != NULL );
+       assert( a_vals != NULL );
+
+       for ( last = 0; !BER_BVISNULL( &a_vals[ last ] ); last++ )
+               ;
+       last--;
+
+       for ( i = 0; !BER_BVISNULL( &a_vals[ i ] ); i++ ) {
+               struct berval   dn,
+                               olddn = BER_BVNULL;
+               int             rc;
+               LDAPURLDesc     *ludp;
+
+               rc = ldap_url_parse( a_vals[ i ].bv_val, &ludp );
+               if ( rc != LDAP_URL_SUCCESS ) {
+                       /* leave attr untouched if massage failed */
+                       continue;
+               }
+
+               /* FIXME: URLs like "ldap:///dc=suffix" if passed
+                * thru ldap_url_parse() and ldap_url_desc2str()
+                * get rewritten as "ldap:///dc=suffix??base";
+                * we don't want this to occur... */
+               if ( ludp->lud_scope == LDAP_SCOPE_BASE ) {
+                       ludp->lud_scope = LDAP_SCOPE_DEFAULT;
+               }
+
+               ber_str2bv( ludp->lud_dn, 0, 0, &olddn );
+
+               rc = asyncmeta_dn_massage( dc, &olddn, &dn );
+               switch ( rc ) {
+               case LDAP_UNWILLING_TO_PERFORM:
+                       /*
+                        * FIXME: need to check if it may be considered
+                        * legal to trim values when adding/modifying;
+                        * it should be when searching (e.g. ACLs).
+                        */
+                       ber_memfree( a_vals[ i ].bv_val );
+                       if ( last > i ) {
+                               a_vals[ i ] = a_vals[ last ];
+                       }
+                       BER_BVZERO( &a_vals[ last ] );
+                       last--;
+                       i--;
+                       break;
+
+               default:
+                       /* leave attr untouched if massage failed */
+                       if ( !BER_BVISNULL( &dn ) && olddn.bv_val != dn.bv_val )
+                       {
+                               char    *newurl;
+
+                               ludp->lud_dn = dn.bv_val;
+                               newurl = ldap_url_desc2str( ludp );
+                               free( dn.bv_val );
+                               if ( newurl == NULL ) {
+                                       /* FIXME: leave attr untouched
+                                        * even if ldap_url_desc2str failed...
+                                        */
+                                       break;
+                               }
+
+                               ber_memfree_x( a_vals[ i ].bv_val, memctx );
+                               ber_str2bv_x( newurl, 0, 1, &a_vals[ i ], memctx );
+                               ber_memfree( newurl );
+                               ludp->lud_dn = olddn.bv_val;
+                       }
+                       break;
+               }
+
+               ldap_free_urldesc( ludp );
+       }
+
+       return 0;
+}
+
+/*
+ * I don't like this much, but we need two different
+ * functions because different heap managers may be
+ * in use in back-ldap/meta to reduce the amount of
+ * calls to malloc routines, and some of the free()
+ * routines may be macros with args
+ */
+int
+asyncmeta_dnattr_rewrite(
+       a_dncookie              *dc,
+       BerVarray               a_vals
+)
+{
+       struct berval   bv;
+       int             i, last;
+
+       assert( a_vals != NULL );
+
+       for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ )
+               ;
+       last--;
+
+       for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) {
+               switch ( asyncmeta_dn_massage( dc, &a_vals[i], &bv ) ) {
+               case LDAP_UNWILLING_TO_PERFORM:
+                       /*
+                        * FIXME: need to check if it may be considered
+                        * legal to trim values when adding/modifying;
+                        * it should be when searching (e.g. ACLs).
+                        */
+                       ch_free( a_vals[i].bv_val );
+                       if ( last > i ) {
+                               a_vals[i] = a_vals[last];
+                       }
+                       BER_BVZERO( &a_vals[last] );
+                       last--;
+                       break;
+
+               default:
+                       /* leave attr untouched if massage failed */
+                       if ( !BER_BVISNULL( &bv ) && bv.bv_val != a_vals[i].bv_val ) {
+                               ch_free( a_vals[i].bv_val );
+                               a_vals[i] = bv;
+                       }
+                       break;
+               }
+       }
+
+       return 0;
+}
+
+int
+asyncmeta_dnattr_result_rewrite(
+       a_dncookie              *dc,
+       BerVarray               a_vals
+)
+{
+       struct berval   bv;
+       int             i, last;
+
+       assert( a_vals != NULL );
+
+       for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ )
+               ;
+       last--;
+
+       for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) {
+               switch ( asyncmeta_dn_massage( dc, &a_vals[i], &bv ) ) {
+               case LDAP_UNWILLING_TO_PERFORM:
+                       /*
+                        * FIXME: need to check if it may be considered
+                        * legal to trim values when adding/modifying;
+                        * it should be when searching (e.g. ACLs).
+                        */
+                       ber_memfree( a_vals[i].bv_val );
+                       if ( last > i ) {
+                               a_vals[i] = a_vals[last];
+                       }
+                       BER_BVZERO( &a_vals[last] );
+                       last--;
+                       break;
+
+               default:
+                       /* leave attr untouched if massage failed */
+                       if ( !BER_BVISNULL( &bv ) && a_vals[i].bv_val != bv.bv_val ) {
+                               ber_memfree( a_vals[i].bv_val );
+                               a_vals[i] = bv;
+                       }
+                       break;
+               }
+       }
+
+       return 0;
+}
diff --git a/servers/slapd/back-asyncmeta/message_queue.c b/servers/slapd/back-asyncmeta/message_queue.c
new file mode 100644 (file)
index 0000000..b00a91c
--- /dev/null
@@ -0,0 +1,545 @@
+/* message_queue.c - routines to maintain the per-connection lists
+ * of pending operations */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "lutil.h"
+
+
+LDAPControl **asyncmeta_copy_controls(Operation *op)
+{
+       LDAPControl **new_controls = NULL;
+       LDAPControl **c;
+       LDAPControl  *tmp_ctl = NULL;
+       int i, length = 1;
+
+
+       if (op->o_ctrls == NULL) {
+               return NULL;
+       }
+       for (c = op->o_ctrls; *c != NULL; c++) {
+               length++;
+       }
+
+       new_controls = op->o_tmpalloc( sizeof(LDAPControl*)*length, op->o_tmpmemctx );
+       for (i = 0; i < length-1; i ++) {
+               new_controls[i] = op->o_tmpalloc( sizeof(LDAPControl), op->o_tmpmemctx );
+               if (op->o_ctrls[i]->ldctl_value.bv_len > 0) {
+                       ber_dupbv_x( &new_controls[i]->ldctl_value, &op->o_ctrls[i]->ldctl_value, op->o_tmpmemctx);
+               }
+               if (op->o_ctrls[i]->ldctl_oid) {
+                       new_controls[i]->ldctl_oid = ber_strdup_x(op->o_ctrls[i]->ldctl_oid, op->o_tmpmemctx);
+               }
+               new_controls[i]->ldctl_iscritical = op->o_ctrls[i]->ldctl_iscritical;
+       }
+       new_controls[length-1] = NULL;
+       return new_controls;
+}
+
+static
+void asyncmeta_free_op_controls(Operation *op)
+{
+       LDAPControl **c;
+       for (c = op->o_ctrls; *c != NULL; c++) {
+               if ((*c)->ldctl_value.bv_len > 0) {
+                       free((*c)->ldctl_value.bv_val);
+               }
+               if ((*c)->ldctl_oid) {
+                       free((*c)->ldctl_oid);
+               }
+               free(*c);
+       }
+       free(op->o_ctrls);
+}
+
+
+static
+Modifications* asyncmeta_copy_modlist(Operation *op, Modifications *modlist)
+{
+       Modifications *ml;
+       Modifications *new_mods = NULL;
+       for ( ml = modlist; ml != NULL; ml = ml->sml_next ) {
+               Modifications *mod = op->o_tmpalloc( sizeof( Modifications ), op->o_tmpmemctx );
+               *mod = *ml;
+               if ( ml->sml_values ) {
+                               ber_bvarray_dup_x( &mod->sml_values, ml->sml_values, op->o_tmpmemctx );
+                               if ( ml->sml_nvalues ) {
+                                       ber_bvarray_dup_x( &mod->sml_nvalues, ml->sml_nvalues, op->o_tmpmemctx );
+                               }
+                       }
+               mod->sml_next = NULL;
+               if (new_mods == NULL) {
+                       new_mods = mod;
+               } else {
+                       new_mods->sml_next = mod;
+               }
+       }
+       return new_mods;
+}
+
+Operation *asyncmeta_copy_op(Operation *op)
+{
+       const char      *text;
+       int rc;
+       char txtbuf[SLAP_TEXT_BUFLEN];
+       size_t textlen = sizeof txtbuf;
+       Entry *e;
+       Operation *new_op = op->o_tmpcalloc( 1, sizeof(OperationBuffer), op->o_tmpmemctx );
+       *new_op = *op;
+       new_op->o_hdr = &((OperationBuffer *) new_op)->ob_hdr;
+       *(new_op->o_hdr) = *(op->o_hdr);
+       new_op->o_controls = ((OperationBuffer *) new_op)->ob_controls;
+       new_op->o_callback = op->o_callback;
+       new_op->o_ber = NULL;
+       new_op->o_bd = op->o_bd->bd_self;
+
+       ber_dupbv_x( &new_op->o_req_dn, &op->o_req_dn, op->o_tmpmemctx );
+       ber_dupbv_x( &new_op->o_req_ndn, &op->o_req_ndn, op->o_tmpmemctx );
+       op->o_callback = NULL;
+
+       if (op->o_ndn.bv_len > 0) {
+               ber_dupbv_x( &new_op->o_ndn, &op->o_ndn, op->o_tmpmemctx );
+       }
+       if (op->o_dn.bv_len > 0) {
+               ber_dupbv_x( &new_op->o_dn, &op->o_dn, op->o_tmpmemctx );
+       }
+
+       new_op->o_ctrls = asyncmeta_copy_controls(op);
+       switch (op->o_tag) {
+       case LDAP_REQ_SEARCH:
+       {
+               AttributeName *at_names;
+               int i;
+               for (i=0; op->ors_attrs && !BER_BVISNULL( &op->ors_attrs[i].an_name ); i++);
+               if (i > 0) {
+                       at_names = op->o_tmpcalloc( i+1, sizeof( AttributeName ), op->o_tmpmemctx );
+                       at_names[i].an_name.bv_len = 0;
+                       i--;
+                       for (i; i >= 0; i--) {
+                               at_names[i] = op->ors_attrs[i];
+                               ber_dupbv_x( &at_names[i].an_name, &op->ors_attrs[i].an_name, op->o_tmpmemctx );
+                       }
+               } else {
+                       at_names = NULL;
+               }
+               ber_dupbv_x( &new_op->ors_filterstr, &op->ors_filterstr, op->o_tmpmemctx );
+               new_op->ors_filter = filter_dup( op->ors_filter, op->o_tmpmemctx );
+               new_op->ors_attrs = at_names;
+       }
+       break;
+       case LDAP_REQ_ADD:
+       {
+               slap_entry2mods(op->ora_e, &new_op->ora_modlist, &text, txtbuf, textlen);
+               e = entry_alloc();
+               new_op->ora_e = e;
+               ber_dupbv_x( &e->e_name, &op->o_req_dn, op->o_tmpmemctx );
+               ber_dupbv_x( &e->e_nname, &op->o_req_ndn, op->o_tmpmemctx );
+               rc = slap_mods2entry( new_op->ora_modlist, &new_op->ora_e, 1, 0, &text, txtbuf, textlen);
+       }
+       break;
+       case LDAP_REQ_MODIFY:
+       {
+               new_op->orm_modlist = asyncmeta_copy_modlist(op, op->orm_modlist);
+       }
+       break;
+       case LDAP_REQ_COMPARE:
+               new_op->orc_ava = (AttributeAssertion *)ch_calloc( 1, sizeof( AttributeAssertion ));
+               *new_op->orc_ava = *op->orc_ava;
+               if ( !BER_BVISNULL( &op->orc_ava->aa_value ) ) {
+                       ber_dupbv_x( &new_op->orc_ava->aa_value,  &op->orc_ava->aa_value, op->o_tmpmemctx);
+               }
+               break;
+       case LDAP_REQ_MODRDN:
+
+               if (op->orr_newrdn.bv_len > 0) {
+                       ber_dupbv_x( &new_op->orr_newrdn, &op->orr_newrdn, op->o_tmpmemctx );
+               }
+               if (op->orr_nnewrdn.bv_len > 0) {
+                       ber_dupbv_x( &new_op->orr_nnewrdn, &op->orr_nnewrdn, op->o_tmpmemctx );
+               }
+               if (op->orr_newSup != NULL) {
+                       new_op->orr_newSup = op->o_tmpalloc( sizeof( struct berval ), op->o_tmpmemctx );
+                       new_op->orr_newSup->bv_len = 0;
+                       if (op->orr_newSup->bv_len > 0) {
+                               ber_dupbv_x( new_op->orr_newSup, op->orr_newSup, op->o_tmpmemctx );
+                       }
+               }
+
+               if (op->orr_nnewSup != NULL) {
+                       new_op->orr_nnewSup = op->o_tmpalloc( sizeof( struct berval ), op->o_tmpmemctx );
+                       new_op->orr_nnewSup->bv_len = 0;
+                       if (op->orr_nnewSup->bv_len > 0) {
+                               ber_dupbv_x( new_op->orr_nnewSup, op->orr_nnewSup, op->o_tmpmemctx );
+                       }
+               }
+               new_op->orr_modlist = asyncmeta_copy_modlist(op, op->orr_modlist);
+               break;
+       case LDAP_REQ_DELETE:
+       default:
+               break;
+       }
+       return new_op;
+}
+
+
+typedef struct listptr {
+       void *reserved;
+       struct listptr *next;
+} listptr;
+
+typedef struct listhead {
+       struct listptr *list;
+       int cnt;
+} listhead;
+
+static void *asyncmeta_memctx_destroy(void *key, void *data)
+{
+       listhead *lh = data;
+       listptr *lp;
+       while (lp = lh->list) {
+               lh->list = lp->next;
+               slap_sl_mem_destroy((void *)1, lp);
+       }
+       ch_free(lh);
+}
+
+#ifndef LH_MAX
+#define LH_MAX 16
+#endif
+
+static void *asyncmeta_memctx_get(void *threadctx)
+{
+       listhead *lh = NULL;
+       listptr *lp = NULL;
+       ldap_pvt_thread_pool_getkey(threadctx, asyncmeta_memctx_get, &lh, NULL);
+       if (!lh) {
+               lh = ch_malloc(sizeof(listhead));
+               lh->cnt = 0;
+               lh->list = NULL;
+               ldap_pvt_thread_pool_setkey(threadctx, asyncmeta_memctx_get, lh, asyncmeta_memctx_destroy, NULL, NULL);
+       }
+       if (lh->list) {
+               lp = lh->list;
+               lh->list = lp->next;
+               lh->cnt--;
+               slap_sl_mem_setctx(threadctx, lp);
+       }
+       return slap_sl_mem_create(SLAP_SLAB_SIZE, SLAP_SLAB_STACK, threadctx, 1);
+}
+
+static void asyncmeta_memctx_put(void *threadctx, void *memctx)
+{
+       listhead *lh = NULL;
+       ldap_pvt_thread_pool_getkey(threadctx, asyncmeta_memctx_get, &lh, NULL);
+       if (!lh) {
+               lh = ch_malloc(sizeof(listhead));
+               lh->cnt = 0;
+               lh->list = NULL;
+               ldap_pvt_thread_pool_setkey(threadctx, asyncmeta_memctx_get, lh, asyncmeta_memctx_destroy, NULL, NULL);
+       }
+       if (lh->cnt < LH_MAX) {
+               listptr *lp = memctx;
+               lp->next = lh->list;
+               lh->list = lp;
+               lh->cnt++;
+       } else {
+               slap_sl_mem_destroy((void *)1, memctx);
+       }
+}
+
+int asyncmeta_new_bm_context(Operation *op, SlapReply *rs, bm_context_t **new_bc, int ntargets)
+{
+       void *oldctx = op->o_tmpmemctx;
+
+       /* prevent old memctx from being destroyed */
+       slap_sl_mem_setctx(op->o_threadctx, NULL);
+       /* create new memctx */
+       op->o_tmpmemctx = asyncmeta_memctx_get( op->o_threadctx );
+       *new_bc = op->o_tmpcalloc( 1, sizeof( bm_context_t ), op->o_tmpmemctx );
+
+       (*new_bc)->op = asyncmeta_copy_op(op);
+       (*new_bc)->candidates = op->o_tmpcalloc(ntargets, sizeof(SlapReply),op->o_tmpmemctx);
+       /* restore original memctx */
+       slap_sl_mem_setctx(op->o_threadctx, oldctx);
+       op->o_tmpmemctx = oldctx;
+       return LDAP_SUCCESS;
+}
+
+void asyncmeta_free_op(Operation *op)
+{
+       assert (op != NULL);
+       switch (op->o_tag) {
+       case LDAP_REQ_SEARCH:
+               if (op->ors_filterstr.bv_len != 0) {
+                       free(op->ors_filterstr.bv_val);
+               }
+               if (op->ors_filter) {
+                       filter_free(op->ors_filter);
+               }
+               if (op->ors_attrs) {
+                       free(op->ors_attrs);
+               }
+               break;
+       case LDAP_REQ_ADD:
+               if ( op->ora_modlist != NULL ) {
+                       slap_mods_free(op->ora_modlist, 0 );
+               }
+
+               if ( op->ora_e != NULL ) {
+                       entry_free( op->ora_e );
+               }
+
+               break;
+       case LDAP_REQ_MODIFY:
+               if ( op->orm_modlist != NULL ) {
+                       slap_mods_free(op->orm_modlist, 1 );
+               }
+               break;
+       case LDAP_REQ_MODRDN:
+               if (op->orr_newrdn.bv_len > 0) {
+                       free(op->orr_newrdn.bv_val);
+               }
+               if (op->orr_nnewrdn.bv_len > 0) {
+                       free(op->orr_nnewrdn.bv_val);
+               }
+
+               if (op->orr_nnewSup != NULL ) {
+                       if (op->orr_nnewSup->bv_len > 0) {
+                               free(op->orr_nnewSup->bv_val);
+                       }
+                       free (op->orr_nnewSup);
+               }
+
+               if (op->orr_newSup != NULL ) {
+                       if (op->orr_newSup->bv_len > 0) {
+                               free(op->orr_newSup->bv_val);
+                       }
+                       free (op->orr_newSup);
+               }
+
+               if ( op->orr_modlist != NULL ) {
+                       slap_mods_free(op->orr_modlist, 1 );
+               }
+               break;
+       case LDAP_REQ_COMPARE:
+               if ( !BER_BVISNULL( &op->orc_ava->aa_value ) ) {
+                       free(op->orc_ava->aa_value.bv_val);
+               }
+               free(op->orc_ava);
+               break;
+       case LDAP_REQ_DELETE:
+               break;
+       default:
+               Debug( LDAP_DEBUG_TRACE, "==> asyncmeta_free_op : other message type",
+              0, 0, 0 );
+       }
+
+       if (op->o_ctrls != NULL) {
+               asyncmeta_free_op_controls(op);
+       }
+       if (op->o_ndn.bv_len > 0) {
+               free(op->o_ndn.bv_val);
+       }
+       if (op->o_dn.bv_len > 0) {
+               free( op->o_dn.bv_val );
+       }
+       if (op->o_req_dn.bv_len > 0) {
+               free(op->o_req_dn.bv_val);
+       }
+       if (op->o_req_dn.bv_len > 0) {
+               free(op->o_req_ndn.bv_val);
+       }
+       free(op);
+}
+
+
+
+
+void asyncmeta_clear_bm_context(bm_context_t *bc)
+{
+
+       Operation *op = bc->op;
+#if 0
+       bm_candidates_t  *cl;
+       a_metainfo_t    *mi;
+       int i = 0;
+       if (bmc == NULL) {
+               return;
+       } else if (bmc->cl == NULL) {
+               free(bmc);
+               return;
+       }
+       cl = bmc->cl;
+       op = cl->op;
+       switch (op->o_tag) {
+       case LDAP_REQ_SEARCH:
+               break;
+       case LDAP_REQ_ADD:
+               if ( (bmc->mdn.bv_len != 0) &&
+                    (bmc->mdn.bv_val != op->o_req_dn.bv_val) ) {
+                       free( bmc->mdn.bv_val );
+               }
+
+               if (bmc->data.add_d.attrs != NULL )  {
+                       while (bmc->data.add_d.attrs[i] != NULL) {
+                               free( bmc->data.add_d.attrs[i]->mod_bvalues );
+                               free( bmc->data.add_d.attrs[i] );
+                               i++;
+                       }
+                       free( bmc->data.add_d.attrs );
+                       }
+               break;
+       case LDAP_REQ_MODIFY:
+               if ( bmc->mdn.bv_val != op->o_req_dn.bv_val ) {
+                       free( bmc->mdn.bv_val );
+               }
+               if ( bmc->data.mod_d.modv != NULL ) {
+                       for ( i = 0; bmc->data.mod_d.modv[ i ]; i++ ) {
+                               free( bmc->data.mod_d.modv[ i ]->mod_bvalues );
+                       }
+               }
+               free( bmc->data.mod_d.mods );
+               free( bmc->data.mod_d.modv );
+
+               break;
+       case LDAP_REQ_MODRDN:
+               if ( bmc->mdn.bv_val != op->o_req_dn.bv_val ) {
+                       free( bmc->mdn.bv_val );
+               }
+
+               if ( bmc->data.modrdn_d.newSuperior.bv_len != 0 &&
+                    bmc->data.modrdn_d.newSuperior.bv_val != op->orr_newSup->bv_val )
+               {
+                       free( bmc->data.modrdn_d.newSuperior.bv_val );
+
+               }
+
+               if ( bmc->data.modrdn_d.newrdn.bv_len != 0 &&
+                    bmc->data.modrdn_d.newrdn.bv_val != op->orr_newrdn.bv_val )
+               {
+                       free( bmc->data.modrdn_d.newrdn.bv_val );
+
+               }
+               break;
+       case LDAP_REQ_COMPARE:
+               if ( bmc->mdn.bv_val != op->o_req_dn.bv_val ) {
+                       free( bmc->mdn.bv_val );
+               }
+               if ( op->orc_ava->aa_value.bv_val != bmc->data.comp_d.mapped_value.bv_val ) {
+                       free( bmc->data.comp_d.mapped_value.bv_val );
+                       }
+               break;
+       case LDAP_REQ_DELETE:
+               if ( bmc->mdn.bv_val != op->o_req_dn.bv_val ) {
+                       free( bmc->mdn.bv_val );
+               }
+               break;
+       default:
+               Debug( LDAP_DEBUG_TRACE, "==> asyncmeta_clear_bm_context: other message type",
+              0, 0, 0 );
+       }
+       if (bmc->dc != NULL) {
+               free (bmc->dc);
+       }
+       free(bmc);
+
+       if (clear_cl > 0) {
+               asyncmeta_free_candidate_list(cl, lock);
+               Debug( LDAP_DEBUG_TRACE, "==> asyncmeta_clear_bm_context: free_cl_list\n",
+              0, 0, 0 );
+       }
+#else
+       asyncmeta_memctx_put(op->o_threadctx, op->o_tmpmemctx);
+#endif
+}
+
+int asyncmeta_add_message_queue(a_metaconn_t *mc, bm_context_t *bc)
+{
+       a_metainfo_t *mi = mc->mc_info;
+       int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops;
+
+       Debug( LDAP_DEBUG_TRACE, "add_message_queue: mc %p, pending_ops %d, max_pending %d\n",
+               mc, mc->pending_ops, max_pending_ops );
+
+       if (mc->pending_ops >= max_pending_ops) {
+               return LDAP_BUSY;
+       }
+
+       LDAP_SLIST_INSERT_HEAD( &mc->mc_om_list, bc, bc_next);
+       mc->pending_ops++;
+       return LDAP_SUCCESS;
+}
+
+void
+asyncmeta_drop_bc(a_metaconn_t *mc, bm_context_t *bc)
+{
+       bm_context_t *om;
+       LDAP_SLIST_FOREACH( om, &mc->mc_om_list, bc_next ) {
+               if (om == bc) {
+                       LDAP_SLIST_REMOVE(&mc->mc_om_list, om, bm_context_t, bc_next);
+                       mc->pending_ops--;
+                       break;
+               }
+       }
+}
+
+bm_context_t *
+asyncmeta_find_message(ber_int_t msgid, a_metaconn_t *mc, int candidate)
+{
+       bm_context_t *om;
+       LDAP_SLIST_FOREACH( om, &mc->mc_om_list, bc_next ) {
+               if (om->candidates[candidate].sr_msgid == msgid) {
+                       break;
+               }
+       }
+       return om;
+}
+
+
+
+bm_context_t *
+asyncmeta_find_message_by_opmsguid (ber_int_t msgid, a_metaconn_t *mc, int remove)
+{
+       bm_context_t *om;
+       LDAP_SLIST_FOREACH( om, &mc->mc_om_list, bc_next ) {
+               if (om->op->o_msgid == msgid) {
+                       break;
+               }
+       }
+       if (remove && om) {
+               LDAP_SLIST_REMOVE(&mc->mc_om_list, om, bm_context_t, bc_next);
+               mc->pending_ops--;
+       }
+       return om;
+}
diff --git a/servers/slapd/back-asyncmeta/meta_result.c b/servers/slapd/back-asyncmeta/meta_result.c
new file mode 100644 (file)
index 0000000..74ad427
--- /dev/null
@@ -0,0 +1,1779 @@
+/* meta_result.c - target responses processing */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "ldap_rq.h"
+#include "../../../libraries/liblber/lber-int.h"
+
+static int
+asyncmeta_is_last_result(a_metaconn_t *mc, bm_context_t *bc, int candidate)
+{
+       a_metainfo_t    *mi = mc->mc_info;
+       a_metatarget_t  *mt = mi->mi_targets[ candidate ];
+       int i,found = 0;
+       SlapReply *candidates = bc->candidates;
+       for ( i = 0; i < mi->mi_ntargets; i++ ) {
+               if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+                       continue;
+               }
+               if (candidates[ i ].sr_msgid != META_MSGID_IGNORE ||
+                   candidates[ i ].sr_type != REP_RESULT) {
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+meta_search_candidate_t
+asyncmeta_dobind_result(
+       Operation               *op,
+       SlapReply               *rs,
+       a_metaconn_t            *mc,
+       int                     candidate,
+       SlapReply               *candidates,
+       LDAPMessage             *res )
+{
+       a_metainfo_t            *mi = mc->mc_info;
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+
+       meta_search_candidate_t retcode = META_SEARCH_NOT_CANDIDATE;
+       int                     rc;
+
+       assert( msc->msc_ldr != NULL );
+
+       /* FIXME: matched? referrals? response controls? */
+       rc = ldap_parse_result( msc->msc_ldr, res,
+               &candidates[ candidate ].sr_err,
+               NULL, NULL, NULL, NULL, 0 );
+       if ( rc != LDAP_SUCCESS ) {
+               candidates[ candidate ].sr_err = rc;
+       }
+       rc = slap_map_api2result( &candidates[ candidate ] );
+
+       LDAP_BACK_CONN_BINDING_CLEAR( msc );
+       if ( rc != LDAP_SUCCESS ) {
+               candidates[ candidate ].sr_err = rc;
+               if ( META_BACK_ONERR_STOP( mi ) || op->o_tag != LDAP_REQ_SEARCH) {
+                       asyncmeta_clear_one_msc(op, mc, candidate);
+                       retcode = META_SEARCH_ERR;
+                       rs->sr_err = rc;
+               }
+
+       } else {
+               /* FIXME: check if bound as idassert authcDN! */
+               if ( BER_BVISNULL( &msc->msc_bound_ndn )
+                       || BER_BVISEMPTY( &msc->msc_bound_ndn ) )
+               {
+                       LDAP_BACK_CONN_ISANON_SET( msc );
+
+               } else {
+                       if ( META_BACK_TGT_SAVECRED( mt ) &&
+                               !BER_BVISNULL( &msc->msc_cred ) &&
+                               !BER_BVISEMPTY( &msc->msc_cred ) )
+                       {
+                               ldap_set_rebind_proc( msc->msc_ldr, mt->mt_rebind_f, msc );
+                       }
+                       LDAP_BACK_CONN_ISBOUND_SET( msc );
+               }
+               retcode = META_SEARCH_CANDIDATE;
+
+               /* connect must be async */
+               //ldap_set_option( msc->msc_ld, LDAP_OPT_CONNECT_ASYNC, LDAP_OPT_OFF );
+       }
+
+       candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+       META_BINDING_CLEAR( &candidates[ candidate ] );
+
+       return retcode;
+}
+
+static int
+asyncmeta_send_entry(
+       Operation       *op,
+       SlapReply       *rs,
+       a_metaconn_t    *mc,
+       int             target,
+       LDAPMessage     *e )
+{
+       a_metainfo_t            *mi = mc->mc_info;
+       struct berval           a, mapped;
+       int                     check_duplicate_attrs = 0;
+       int                     check_sorted_attrs = 0;
+       Entry                   ent = {0};
+       BerElement              ber = *ldap_get_message_ber( e );
+       Attribute               *attr, **attrp;
+       struct berval           bdn,
+                               dn = BER_BVNULL;
+       const char              *text;
+       a_dncookie              dc;
+       ber_len_t               len;
+       int                     rc;
+
+       if ( ber_scanf( &ber, "l{", &len ) == LBER_ERROR ) {
+               return LDAP_DECODING_ERROR;
+       }
+
+       if ( ber_set_option( &ber, LBER_OPT_REMAINING_BYTES, &len ) != LBER_OPT_SUCCESS ) {
+               return LDAP_OTHER;
+       }
+
+       if ( ber_scanf( &ber, "m{", &bdn ) == LBER_ERROR ) {
+               return LDAP_DECODING_ERROR;
+       }
+
+       /*
+        * Rewrite the dn of the result, if needed
+        */
+       dc.target = mi->mi_targets[ target ];
+       dc.conn = op->o_conn;
+       dc.rs = rs;
+       dc.ctx = "searchResult";
+
+       rs->sr_err = asyncmeta_dn_massage( &dc, &bdn, &dn );
+       if ( rs->sr_err != LDAP_SUCCESS) {
+               return rs->sr_err;
+       }
+
+       /*
+        * Note: this may fail if the target host(s) schema differs
+        * from the one known to the meta, and a DN with unknown
+        * attributes is returned.
+        *
+        * FIXME: should we log anything, or delegate to dnNormalize?
+        */
+       rc = dnPrettyNormal( NULL, &dn, &ent.e_name, &ent.e_nname,
+               op->o_tmpmemctx );
+       if ( dn.bv_val != bdn.bv_val ) {
+                       free( dn.bv_val );
+       }
+       BER_BVZERO( &dn );
+
+       if ( rc != LDAP_SUCCESS ) {
+               Debug( LDAP_DEBUG_ANY,
+                       "%s asyncmeta_send_entry(\"%s\"): "
+                       "invalid DN syntax\n",
+                       op->o_log_prefix, ent.e_name.bv_val, 0 );
+               rc = LDAP_INVALID_DN_SYNTAX;
+               goto done;
+       }
+
+       /*
+        * cache dn
+        */
+       if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED ) {
+               ( void )asyncmeta_dncache_update_entry( &mi->mi_cache,
+                               &ent.e_nname, target );
+       }
+
+       attrp = &ent.e_attrs;
+
+       dc.ctx = "searchAttrDN";
+       while ( ber_scanf( &ber, "{m", &a ) != LBER_ERROR ) {
+               int                             last = 0;
+               slap_syntax_validate_func       *validate;
+               slap_syntax_transform_func      *pretty;
+
+               if ( ber_pvt_ber_remaining( &ber ) < 0 ) {
+                       Debug( LDAP_DEBUG_ANY,
+                               "%s asyncmeta_send_entry(\"%s\"): "
+                               "unable to parse attr \"%s\".\n",
+                               op->o_log_prefix, ent.e_name.bv_val, a.bv_val );
+
+                       rc = LDAP_OTHER;
+                       goto done;
+               }
+
+               if ( ber_pvt_ber_remaining( &ber ) == 0 ) {
+                       break;
+               }
+
+               asyncmeta_map( &mi->mi_targets[ target ]->mt_rwmap.rwm_at,
+                               &a, &mapped, BACKLDAP_REMAP );
+               if ( BER_BVISNULL( &mapped ) || mapped.bv_val[0] == '\0' ) {
+                       ( void )ber_scanf( &ber, "x" /* [W] */ );
+                       continue;
+               }
+               if ( mapped.bv_val != a.bv_val ) {
+                       /* will need to check for duplicate attrs */
+                       check_duplicate_attrs++;
+               }
+               attr = op->o_tmpcalloc( 1, sizeof(Attribute), op->o_tmpmemctx );
+               if ( slap_bv2ad( &mapped, &attr->a_desc, &text )
+                               != LDAP_SUCCESS) {
+                       if ( slap_bv2undef_ad( &mapped, &attr->a_desc, &text,
+                               SLAP_AD_PROXIED ) != LDAP_SUCCESS )
+                       {
+                               char    buf[ SLAP_TEXT_BUFLEN ];
+
+                               snprintf( buf, sizeof( buf ),
+                                       "%s meta_send_entry(\"%s\"): "
+                                       "slap_bv2undef_ad(%s): %s\n",
+                                       op->o_log_prefix, ent.e_name.bv_val,
+                                       mapped.bv_val, text );
+
+                               Debug( LDAP_DEBUG_ANY, "%s", buf, 0, 0 );
+                               ( void )ber_scanf( &ber, "x" /* [W] */ );
+                               op->o_tmpfree( attr, op->o_tmpmemctx );
+                               continue;
+                       }
+               }
+
+               if ( attr->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL )
+                       check_sorted_attrs = 1;
+
+               /* no subschemaSubentry */
+               if ( attr->a_desc == slap_schema.si_ad_subschemaSubentry
+                       || attr->a_desc == slap_schema.si_ad_entryDN )
+               {
+
+                       /*
+                        * We eat target's subschemaSubentry because
+                        * a search for this value is likely not
+                        * to resolve to the appropriate backend;
+                        * later, the local subschemaSubentry is
+                        * added.
+                        *
+                        * We also eat entryDN because the frontend
+                        * will reattach it without checking if already
+                        * present...
+                        */
+                       ( void )ber_scanf( &ber, "x" /* [W] */ );
+                       op->o_tmpfree( attr, op->o_tmpmemctx );
+                       continue;
+               }
+
+               if ( ber_scanf( &ber, "[W]", &attr->a_vals ) == LBER_ERROR
+                               || attr->a_vals == NULL )
+               {
+                       attr->a_vals = (struct berval *)&slap_dummy_bv;
+
+               } else {
+                       for ( last = 0; !BER_BVISNULL( &attr->a_vals[ last ] ); ++last )
+                               ;
+               }
+               attr->a_numvals = last;
+
+               validate = attr->a_desc->ad_type->sat_syntax->ssyn_validate;
+               pretty = attr->a_desc->ad_type->sat_syntax->ssyn_pretty;
+
+               if ( !validate && !pretty ) {
+                       attr_clean( attr );
+                       op->o_tmpfree( attr, op->o_tmpmemctx );
+                       goto next_attr;
+               }
+
+               if ( attr->a_desc == slap_schema.si_ad_objectClass
+                               || attr->a_desc == slap_schema.si_ad_structuralObjectClass )
+               {
+                       struct berval   *bv;
+
+                       for ( bv = attr->a_vals; !BER_BVISNULL( bv ); bv++ ) {
+                               ObjectClass *oc;
+
+                               asyncmeta_map( &mi->mi_targets[ target ]->mt_rwmap.rwm_oc,
+                                               bv, &mapped, BACKLDAP_REMAP );
+                               if ( BER_BVISNULL( &mapped ) || mapped.bv_val[0] == '\0') {
+remove_oc:;
+                                       free( bv->bv_val );
+                                       BER_BVZERO( bv );
+                                       if ( --last < 0 ) {
+                                               break;
+                                       }
+                                       *bv = attr->a_vals[ last ];
+                                       BER_BVZERO( &attr->a_vals[ last ] );
+                                       bv--;
+
+                               } else if ( mapped.bv_val != bv->bv_val ) {
+                                       int     i;
+
+                                       for ( i = 0; !BER_BVISNULL( &attr->a_vals[ i ] ); i++ ) {
+                                               if ( &attr->a_vals[ i ] == bv ) {
+                                                       continue;
+                                               }
+
+                                               if ( ber_bvstrcasecmp( &mapped, &attr->a_vals[ i ] ) == 0 ) {
+                                                       break;
+                                               }
+                                       }
+
+                                       if ( !BER_BVISNULL( &attr->a_vals[ i ] ) ) {
+                                               goto remove_oc;
+                                       }
+
+                                       ber_bvreplace( bv, &mapped );
+
+                               } else if ( ( oc = oc_bvfind_undef( bv ) ) == NULL ) {
+                                       goto remove_oc;
+
+                               } else {
+                                       ber_bvreplace( bv, &oc->soc_cname );
+                               }
+                       }
+               /*
+                * It is necessary to try to rewrite attributes with
+                * dn syntax because they might be used in ACLs as
+                * members of groups; since ACLs are applied to the
+                * rewritten stuff, no dn-based subecj clause could
+                * be used at the ldap backend side (see
+                * http://www.OpenLDAP.org/faq/data/cache/452.html)
+                * The problem can be overcome by moving the dn-based
+                * ACLs to the target directory server, and letting
+                * everything pass thru the ldap backend.
+                */
+               } else {
+                       int     i;
+
+                       if ( attr->a_desc->ad_type->sat_syntax ==
+                               slap_schema.si_syn_distinguishedName )
+                       {
+                               asyncmeta_dnattr_result_rewrite( &dc, attr->a_vals );
+
+                       } else if ( attr->a_desc == slap_schema.si_ad_ref ) {
+                               asyncmeta_referral_result_rewrite( &dc, attr->a_vals, NULL );
+
+                       }
+
+                       for ( i = 0; i < last; i++ ) {
+                               struct berval   pval;
+                               int             rc;
+
+                               if ( pretty ) {
+                                       rc = ordered_value_pretty( attr->a_desc,
+                                               &attr->a_vals[i], &pval, NULL );
+
+                               } else {
+                                       rc = ordered_value_validate( attr->a_desc,
+                                               &attr->a_vals[i], 0 );
+                               }
+
+                               if ( rc ) {
+                                       ber_memfree( attr->a_vals[i].bv_val );
+                                       if ( --last == i ) {
+                                               BER_BVZERO( &attr->a_vals[ i ] );
+                                               break;
+                                       }
+                                       attr->a_vals[i] = attr->a_vals[last];
+                                       BER_BVZERO( &attr->a_vals[last] );
+                                       i--;
+                                       continue;
+                               }
+
+                               if ( pretty ) {
+                                       ber_memfree( attr->a_vals[i].bv_val );
+                                       attr->a_vals[i] = pval;
+                               }
+                       }
+
+                       if ( last == 0 && attr->a_vals != &slap_dummy_bv ) {
+                               attr_clean( attr );
+                               op->o_tmpfree( attr, op->o_tmpmemctx );
+                               goto next_attr;
+                       }
+               }
+
+               if ( last && attr->a_desc->ad_type->sat_equality &&
+                       attr->a_desc->ad_type->sat_equality->smr_normalize )
+               {
+                       int i;
+
+                       attr->a_nvals = ch_malloc( ( last + 1 ) * sizeof( struct berval ) );
+                       for ( i = 0; i<last; i++ ) {
+                               /* if normalizer fails, drop this value */
+                               if ( ordered_value_normalize(
+                                       SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+                                       attr->a_desc,
+                                       attr->a_desc->ad_type->sat_equality,
+                                       &attr->a_vals[i], &attr->a_nvals[i],
+                                       NULL )) {
+                                       ber_memfree( attr->a_vals[i].bv_val );
+                                       if ( --last == i ) {
+                                               BER_BVZERO( &attr->a_vals[ i ] );
+                                               break;
+                                       }
+                                       attr->a_vals[i] = attr->a_vals[last];
+                                       BER_BVZERO( &attr->a_vals[last] );
+                                       i--;
+                               }
+                       }
+                       BER_BVZERO( &attr->a_nvals[i] );
+                       if ( last == 0 ) {
+                               attr_clean( attr );
+                               op->o_tmpfree( attr, op->o_tmpmemctx );
+                               goto next_attr;
+                       }
+
+               } else {
+                       attr->a_nvals = attr->a_vals;
+               }
+
+               attr->a_numvals = last;
+               *attrp = attr;
+               attrp = &attr->a_next;
+next_attr:;
+       }
+
+       /* only check if some mapping occurred */
+       if ( check_duplicate_attrs ) {
+               Attribute       **ap;
+
+               for ( ap = &ent.e_attrs; *ap != NULL; ap = &(*ap)->a_next ) {
+                       Attribute       **tap;
+
+                       for ( tap = &(*ap)->a_next; *tap != NULL; ) {
+                               if ( (*tap)->a_desc == (*ap)->a_desc ) {
+                                       Entry           e = { 0 };
+                                       Modification    mod = { 0 };
+                                       const char      *text = NULL;
+                                       char            textbuf[ SLAP_TEXT_BUFLEN ];
+                                       Attribute       *next = (*tap)->a_next;
+
+                                       BER_BVSTR( &e.e_name, "" );
+                                       BER_BVSTR( &e.e_nname, "" );
+                                       e.e_attrs = *ap;
+                                       mod.sm_op = LDAP_MOD_ADD;
+                                       mod.sm_desc = (*ap)->a_desc;
+                                       mod.sm_type = mod.sm_desc->ad_cname;
+                                       mod.sm_numvals = (*ap)->a_numvals;
+                                       mod.sm_values = (*tap)->a_vals;
+                                       if ( (*tap)->a_nvals != (*tap)->a_vals ) {
+                                               mod.sm_nvalues = (*tap)->a_nvals;
+                                       }
+
+                                       (void)modify_add_values( &e, &mod,
+                                               /* permissive */ 1,
+                                               &text, textbuf, sizeof( textbuf ) );
+
+                                       /* should not insert new attrs! */
+                                       assert( e.e_attrs == *ap );
+
+                                       attr_clean( *tap );
+                                       op->o_tmpfree( *tap, op->o_tmpmemctx );
+                                       *tap = next;
+
+                               } else {
+                                       tap = &(*tap)->a_next;
+                               }
+                       }
+               }
+       }
+
+       /* Check for sorted attributes */
+       if ( check_sorted_attrs ) {
+               for ( attr = ent.e_attrs; attr; attr = attr->a_next ) {
+                       if ( attr->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) {
+                               while ( attr->a_numvals > 1 ) {
+                                       int i;
+                                       int rc = slap_sort_vals( (Modifications *)attr, &text, &i, op->o_tmpmemctx );
+                                       if ( rc != LDAP_TYPE_OR_VALUE_EXISTS )
+                                               break;
+
+                                       /* Strip duplicate values */
+                                       if ( attr->a_nvals != attr->a_vals )
+                                               ber_memfree( attr->a_nvals[i].bv_val );
+                                       ber_memfree( attr->a_vals[i].bv_val );
+                                       attr->a_numvals--;
+                                       if ( (unsigned)i < attr->a_numvals ) {
+                                               attr->a_vals[i] = attr->a_vals[attr->a_numvals];
+                                               if ( attr->a_nvals != attr->a_vals )
+                                                       attr->a_nvals[i] = attr->a_nvals[attr->a_numvals];
+                                       }
+                                       BER_BVZERO(&attr->a_vals[attr->a_numvals]);
+                                       if ( attr->a_nvals != attr->a_vals )
+                                               BER_BVZERO(&attr->a_nvals[attr->a_numvals]);
+                               }
+                               attr->a_flags |= SLAP_ATTR_SORTED_VALS;
+                       }
+               }
+       }
+       Debug( LDAP_DEBUG_TRACE,
+              "%s asyncmeta_send_entry(\"%s\"): "
+              ".\n",
+              op->o_log_prefix, ent.e_name.bv_val, 0);
+       ldap_get_entry_controls( mc->mc_conns[target].msc_ldr,
+               e, &rs->sr_ctrls );
+       rs->sr_entry = &ent;
+       rs->sr_attrs = op->ors_attrs;
+       rs->sr_operational_attrs = NULL;
+       rs->sr_flags = mi->mi_targets[ target ]->mt_rep_flags;
+       rs->sr_err = LDAP_SUCCESS;
+       rc = send_search_entry( op, rs );
+       switch ( rc ) {
+       case LDAP_UNAVAILABLE:
+               rc = LDAP_OTHER;
+               break;
+       }
+
+done:;
+       if ( rs->sr_ctrls != NULL ) {
+               ldap_controls_free( rs->sr_ctrls );
+               rs->sr_ctrls = NULL;
+       }
+       while ( ent.e_attrs ) {
+               attr = ent.e_attrs;
+               ent.e_attrs = attr->a_next;
+               attr_clean( attr );
+               op->o_tmpfree( attr, op->o_tmpmemctx );
+       }
+       if (rs->sr_entry && rs->sr_entry != &ent) {
+               entry_free( rs->sr_entry );
+       }
+       rs->sr_entry = NULL;
+       rs->sr_attrs = NULL;
+       return rc;
+}
+
+int
+asyncmeta_search_last_result(a_metaconn_t *mc, bm_context_t *bc, int candidate, int sres)
+{
+       a_metainfo_t    *mi = mc->mc_info;
+       a_metatarget_t  *mt = mi->mi_targets[ candidate ];
+       Operation *op = bc->op;
+       SlapReply *rs = &bc->rs;
+       int        rc, i;
+       SlapReply *candidates = bc->candidates;
+       char *matched = NULL;
+
+       if ( bc->candidate_match > 0 ) {
+               struct berval   pmatched = BER_BVNULL;
+
+               /* we use the first one */
+               for ( i = 0; i < mi->mi_ntargets; i++ ) {
+                       if ( META_IS_CANDIDATE( &candidates[ i ] )
+                                       && candidates[ i ].sr_matched != NULL )
+                       {
+                               struct berval   bv, pbv;
+                               int             rc;
+
+                               /* if we got success, and this target
+                                * returned noSuchObject, and its suffix
+                                * is a superior of the searchBase,
+                                * ignore the matchedDN */
+                               if ( sres == LDAP_SUCCESS
+                                       && candidates[ i ].sr_err == LDAP_NO_SUCH_OBJECT
+                                       && op->o_req_ndn.bv_len > mi->mi_targets[ i ]->mt_nsuffix.bv_len )
+                               {
+                                       free( (char *)candidates[ i ].sr_matched );
+                                       candidates[ i ].sr_matched = NULL;
+                                       continue;
+                               }
+
+                               ber_str2bv( candidates[ i ].sr_matched, 0, 0, &bv );
+                               rc = dnPretty( NULL, &bv, &pbv, op->o_tmpmemctx );
+
+                               if ( rc == LDAP_SUCCESS ) {
+
+                                       /* NOTE: if they all are superiors
+                                        * of the baseDN, the shorter is also
+                                        * superior of the longer... */
+                                       if ( pbv.bv_len > pmatched.bv_len ) {
+                                               if ( !BER_BVISNULL( &pmatched ) ) {
+                                                       op->o_tmpfree( pmatched.bv_val, op->o_tmpmemctx );
+                                               }
+                                               pmatched = pbv;
+
+                                       } else {
+                                               op->o_tmpfree( pbv.bv_val, op->o_tmpmemctx );
+                                       }
+                               }
+
+                               if ( candidates[ i ].sr_matched != NULL ) {
+                                       free( (char *)candidates[ i ].sr_matched );
+                                       candidates[ i ].sr_matched = NULL;
+                               }
+                       }
+               }
+
+               if ( !BER_BVISNULL( &pmatched ) ) {
+                       matched = pmatched.bv_val;
+               }
+
+       } else if ( sres == LDAP_NO_SUCH_OBJECT ) {
+               matched = op->o_bd->be_suffix[ 0 ].bv_val;
+       }
+
+       /*
+        * In case we returned at least one entry, we return LDAP_SUCCESS
+        * otherwise, the latter error code we got
+        */
+
+       if ( sres == LDAP_SUCCESS ) {
+               if ( rs->sr_v2ref ) {
+                       sres = LDAP_REFERRAL;
+               }
+
+               if ( META_BACK_ONERR_REPORT( mi ) ) {
+                       /*
+                        * Report errors, if any
+                        *
+                        * FIXME: we should handle error codes and return the more
+                        * important/reasonable
+                        */
+                       for ( i = 0; i < mi->mi_ntargets; i++ ) {
+                               if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+                                       continue;
+                               }
+
+                               if ( candidates[ i ].sr_err != LDAP_SUCCESS
+                                       && candidates[ i ].sr_err != LDAP_NO_SUCH_OBJECT )
+                               {
+                                       sres = candidates[ i ].sr_err;
+                                       break;
+                               }
+                       }
+               }
+       }
+       Debug( LDAP_DEBUG_TRACE,
+              "%s asyncmeta_search_last_result(\"%s\"): "
+              ".\n",
+              op->o_log_prefix, 0, 0);
+       rs->sr_err = sres;
+       rs->sr_matched = ( sres == LDAP_SUCCESS ? NULL : matched );
+       rs->sr_ref = ( sres == LDAP_REFERRAL ? rs->sr_v2ref : NULL );
+       send_ldap_result(op, rs);
+       rs->sr_matched = NULL;
+       rs->sr_ref = NULL;
+}
+
+
+int
+asyncmeta_search_finish(a_metaconn_t *mc, bm_context_t *bc, int candidate, char *matched)
+{
+       a_metainfo_t    *mi = mc->mc_info;
+       a_metatarget_t  *mt = mi->mi_targets[ candidate ];
+       Operation *op = bc->op;
+       SlapReply *rs = &bc->rs;
+       SlapReply *candidates = bc->candidates;
+       int i;
+       int do_taint = 0;
+       if ( matched && matched != op->o_bd->be_suffix[ 0 ].bv_val ) {
+               op->o_tmpfree( matched, op->o_tmpmemctx );
+       }
+
+       if ( rs->sr_v2ref ) {
+               ber_bvarray_free_x( rs->sr_v2ref, op->o_tmpmemctx );
+       }
+
+       for ( i = 0; i < mi->mi_ntargets; i++ ) {
+               if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+                       continue;
+               }
+
+                       if ( META_IS_BINDING( &candidates[ i ] )
+                               || candidates[ i ].sr_msgid == META_MSGID_CONNECTING )
+                       {
+                               if ( LDAP_BACK_CONN_BINDING( &mc->mc_conns[ i ] )
+                                       || candidates[ i ].sr_msgid == META_MSGID_CONNECTING )
+                               {
+                                       assert( candidates[ i ].sr_msgid >= 0
+                                               || candidates[ i ].sr_msgid == META_MSGID_CONNECTING );
+                                       assert( mc->mc_conns[ i ].msc_ldr != NULL );
+
+#ifdef DEBUG_205
+                                       Debug( LDAP_DEBUG_ANY, "### %s asyncmeta_search_finish  "
+                                               "ldap_unbind_ext[%ld] ld=%p\n",
+                                               op->o_log_prefix, i, (void *)mc->mc_conns[i].msc_ldr );
+#endif /* DEBUG_205 */
+
+                               }
+                               META_BINDING_CLEAR( &candidates[ i ] );
+
+                       } else if ( candidates[ i ].sr_msgid >= 0 ) {
+                               (void)asyncmeta_back_cancel( mc, op,
+                                       candidates[ i ].sr_msgid, i );
+                       }
+
+               if ( candidates[ i ].sr_matched ) {
+                       free( (char *)candidates[ i ].sr_matched );
+                       candidates[ i ].sr_matched = NULL;
+               }
+
+               if ( candidates[ i ].sr_text ) {
+                       ldap_memfree( (char *)candidates[ i ].sr_text );
+                       candidates[ i ].sr_text = NULL;
+               }
+
+               if ( candidates[ i ].sr_ref ) {
+                       ber_bvarray_free( candidates[ i ].sr_ref );
+                       candidates[ i ].sr_ref = NULL;
+               }
+
+               if ( candidates[ i ].sr_ctrls ) {
+                       ldap_controls_free( candidates[ i ].sr_ctrls );
+                       candidates[ i ].sr_ctrls = NULL;
+               }
+
+               if ( META_BACK_TGT_QUARANTINE( mi->mi_targets[ i ] ) ) {
+                       asyncmeta_quarantine( op, mi, &candidates[ i ], i );
+               }
+               /* this remains as it is only for explicitly bound connections, for the time being */
+               /* only in case of timelimit exceeded, if the timelimit exceeded because
+                * one contacted target never responded, invalidate the connection
+                * NOTE: should we quarantine the target as well?  right now, the connection
+                * is invalidated; the next time it will be recreated and the target
+                * will be quarantined if it cannot be contacted */
+               if ( mi->mi_idle_timeout != 0
+                       && rs->sr_err == LDAP_TIMELIMIT_EXCEEDED
+                       && op->o_time > mc->mc_conns[ i ].msc_time )
+               {
+                       /* don't let anyone else use this expired connection */
+                       do_taint++;
+               }
+       }
+
+       if ( mc && do_taint) {
+               LDAP_BACK_CONN_TAINTED_SET( mc );
+       }
+
+       return rs->sr_err;
+}
+
+
+int
+asyncmeta_handle_bind_result(LDAPMessage *msg, a_metaconn_t *mc, bm_context_t *bc, int candidate)
+{
+       meta_search_candidate_t retcode;
+       a_metainfo_t    *mi;
+       a_metatarget_t  *mt;
+       Operation *op;
+       SlapReply *rs;
+       int        rc;
+       SlapReply *candidates;
+
+       mi = mc->mc_info;
+       mt = mi->mi_targets[ candidate ];
+       op = bc->op;
+       rs = &bc->rs;
+       candidates = bc->candidates;
+       Debug( LDAP_DEBUG_TRACE,
+              "%s asyncmeta_handle_bind_result[%d]\n",
+              op->o_log_prefix, candidate, 0);
+       retcode = asyncmeta_dobind_result( op, rs, mc, candidate, candidates, msg );
+       if ( retcode == META_SEARCH_CANDIDATE ) {
+               switch (op->o_tag) {
+               case LDAP_REQ_SEARCH:
+                       retcode = asyncmeta_back_search_start( op, rs, mc, bc, candidate,  NULL, 0 );
+                       break;
+               case LDAP_REQ_ADD:
+                       retcode = asyncmeta_back_add_start( op, rs, mc, bc, candidate);
+                       break;
+               case LDAP_REQ_MODIFY:
+                       retcode = asyncmeta_back_modify_start( op, rs, mc, bc, candidate);
+                       break;
+               case LDAP_REQ_MODRDN:
+                       retcode = asyncmeta_back_modrdn_start( op, rs, mc, bc, candidate);
+                       break;
+               case LDAP_REQ_COMPARE:
+                       retcode = asyncmeta_back_compare_start( op, rs, mc, bc, candidate);
+                       break;
+               case LDAP_REQ_DELETE:
+                       retcode = asyncmeta_back_delete_start( op, rs, mc, bc, candidate);
+                       break;
+               default:
+                       retcode = META_SEARCH_NOT_CANDIDATE;
+               }
+       }
+
+       switch ( retcode ) {
+       case META_SEARCH_CANDIDATE:
+               break;
+               /* means that failed but onerr == continue */
+       case META_SEARCH_NOT_CANDIDATE:
+       case META_SEARCH_ERR:
+               candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+               candidates[ candidate ].sr_type = REP_RESULT;
+               candidates[ candidate ].sr_err = rs->sr_err;
+               if ( META_BACK_ONERR_STOP( mi ) || op->o_tag != LDAP_REQ_SEARCH) {
+                       send_ldap_result(op, rs);
+               }
+               if (op->o_tag != LDAP_REQ_SEARCH) {
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+                       asyncmeta_drop_bc( mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+                       asyncmeta_clear_bm_context(bc);
+                       return retcode;
+               }
+               break;
+       default:
+               assert( 0 );
+               break;
+       }
+       bc->bc_active = 0;
+       return retcode;
+}
+
+int
+asyncmeta_handle_search_msg(LDAPMessage *res, a_metaconn_t *mc, bm_context_t *bc, int candidate)
+{
+       a_metainfo_t    *mi;
+       a_metatarget_t  *mt;
+       a_metasingleconn_t *msc;
+       Operation op;
+       SlapReply *rs;
+       int        i, j, rc, sres;
+       SlapReply *candidates;
+       char            **references = NULL;
+       LDAPControl     **ctrls = NULL;
+       a_dncookie dc;
+       LDAPMessage *msg;
+       ber_int_t id;
+
+       op = *bc->op;
+       rs = &bc->rs;
+       mi = mc->mc_info;
+       mt = mi->mi_targets[ candidate ];
+       msc = &mc->mc_conns[ candidate ];
+       dc.target = mt;
+       dc.conn = op.o_conn;
+       dc.rs = rs;
+       id = ldap_msgid(res);
+
+       candidates = bc->candidates;
+       i = candidate;
+
+       while (res) {
+       for (msg = ldap_first_message(msc->msc_ldr, res); msg; msg = ldap_next_message(msc->msc_ldr, msg)) {
+               switch(ldap_msgtype(msg)) {
+               case LDAP_RES_SEARCH_ENTRY:
+                       Debug( LDAP_DEBUG_TRACE,
+                               "%s asyncmeta_handle_search_msg: msc %p entry\n",
+                               op.o_log_prefix, msc, 0);
+                       if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) {
+                               /* don't retry any more... */
+                               candidates[ i ].sr_type = REP_RESULT;
+                       }
+                       /* count entries returned by target */
+                       candidates[ i ].sr_nentries++;
+                       rs->sr_err = asyncmeta_send_entry( &op, rs, mc, i, msg );
+                       switch ( rs->sr_err ) {
+                       case LDAP_SIZELIMIT_EXCEEDED:
+                               send_ldap_result(&op, rs);
+                               rs->sr_err = LDAP_SUCCESS;
+                               goto err_cleanup;
+                       case LDAP_UNAVAILABLE:
+                               rs->sr_err = LDAP_OTHER;
+                       }
+                       bc->is_ok++;
+                       break;
+
+               case LDAP_RES_SEARCH_REFERENCE:
+                       if ( META_BACK_TGT_NOREFS( mi->mi_targets[ i ] ) ) {
+                               rs->sr_err = LDAP_OTHER;
+                               send_ldap_result(&op, rs);
+                               goto err_cleanup;
+                       }
+                       if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) {
+                               /* don't retry any more... */
+                               candidates[ i ].sr_type = REP_RESULT;
+                       }
+                       bc->is_ok++;
+                       rc = ldap_parse_reference( msc->msc_ldr, msg,
+                                  &references, &rs->sr_ctrls, 0 );
+
+                       if ( rc != LDAP_SUCCESS || references == NULL ) {
+                               rs->sr_err = LDAP_OTHER;
+                               send_ldap_result(&op, rs);
+                               goto err_cleanup;
+                       }
+
+                       /* FIXME: merge all and return at the end */
+
+                       {
+                               int cnt;
+                               for ( cnt = 0; references[ cnt ]; cnt++ )
+                                       ;
+
+                               rs->sr_ref = ber_memalloc_x( sizeof( struct berval ) * ( cnt + 1 ),
+                                                                op.o_tmpmemctx );
+
+                               for ( cnt = 0; references[ cnt ]; cnt++ ) {
+                                       ber_str2bv_x( references[ cnt ], 0, 1, &rs->sr_ref[ cnt ],
+                                                         op.o_tmpmemctx );
+                               }
+                               BER_BVZERO( &rs->sr_ref[ cnt ] );
+                       }
+
+                       {
+                               dc.ctx = "referralDN";
+                               ( void )asyncmeta_referral_result_rewrite( &dc, rs->sr_ref,
+                                                                  op.o_tmpmemctx );
+                       }
+
+                       if ( rs->sr_ref != NULL ) {
+                               if (!BER_BVISNULL( &rs->sr_ref[ 0 ] ) ) {
+                                       /* ignore return value by now */
+                                       ( void )send_search_reference( &op, rs );
+                               }
+
+                               ber_bvarray_free_x( rs->sr_ref, op.o_tmpmemctx );
+                               rs->sr_ref = NULL;
+                       }
+
+                       /* cleanup */
+                       if ( references ) {
+                               ber_memvfree( (void **)references );
+                       }
+
+                       if ( rs->sr_ctrls ) {
+                               ldap_controls_free( rs->sr_ctrls );
+                               rs->sr_ctrls = NULL;
+                       }
+                       break;
+
+               case LDAP_RES_INTERMEDIATE:
+                       if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) {
+                               /* don't retry any more... */
+                               candidates[ i ].sr_type = REP_RESULT;
+                       }
+                       bc->is_ok++;
+
+                       /* FIXME: response controls
+                        * are passed without checks */
+                       rs->sr_err = ldap_parse_intermediate( msc->msc_ldr,
+                                                                 msg,
+                                                                 (char **)&rs->sr_rspoid,
+                                                                 &rs->sr_rspdata,
+                                                                 &rs->sr_ctrls,
+                                                                 0 );
+                       if ( rs->sr_err != LDAP_SUCCESS ) {
+                               candidates[ i ].sr_type = REP_RESULT;
+                               rs->sr_err = LDAP_OTHER;
+                               send_ldap_result(&op, rs);
+                               goto err_cleanup;
+                       }
+
+                       slap_send_ldap_intermediate( &op, rs );
+
+                       if ( rs->sr_rspoid != NULL ) {
+                               ber_memfree( (char *)rs->sr_rspoid );
+                               rs->sr_rspoid = NULL;
+                       }
+
+                       if ( rs->sr_rspdata != NULL ) {
+                               ber_bvfree( rs->sr_rspdata );
+                               rs->sr_rspdata = NULL;
+                       }
+
+                       if ( rs->sr_ctrls != NULL ) {
+                               ldap_controls_free( rs->sr_ctrls );
+                               rs->sr_ctrls = NULL;
+                       }
+                       break;
+
+               case LDAP_RES_SEARCH_RESULT:
+                       Debug( LDAP_DEBUG_TRACE,
+                               "%s asyncmeta_handle_search_msg: msc %p result\n",
+                               op.o_log_prefix, msc, 0);
+                       candidates[ i ].sr_type = REP_RESULT;
+                       candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+                       /* NOTE: ignores response controls
+                        * (and intermediate response controls
+                        * as well, except for those with search
+                        * references); this may not be correct,
+                        * but if they're not ignored then
+                        * back-meta would need to merge them
+                        * consistently (think of pagedResults...)
+                        */
+                       /* FIXME: response controls? */
+                       rs->sr_err = ldap_parse_result( msc->msc_ldr,
+                                                       msg,
+                                                       &candidates[ i ].sr_err,
+                                                               (char **)&candidates[ i ].sr_matched,
+                                                       (char **)&candidates[ i ].sr_text,
+                                                       &references,
+                                                       &ctrls /* &candidates[ i ].sr_ctrls (unused) */ ,
+                                                       0 );
+                       if ( rs->sr_err != LDAP_SUCCESS ) {
+                               candidates[ i ].sr_err = rs->sr_err;
+                               sres = slap_map_api2result( &candidates[ i ] );
+                               candidates[ i ].sr_type = REP_RESULT;
+                               goto finish;
+                       }
+
+                       rs->sr_err = candidates[ i ].sr_err;
+
+                       /* massage matchedDN if need be */
+                       if ( candidates[ i ].sr_matched != NULL ) {
+                               struct berval   match, mmatch;
+
+                               ber_str2bv( candidates[ i ].sr_matched,
+                                               0, 0, &match );
+                               candidates[ i ].sr_matched = NULL;
+
+                               dc.ctx = "matchedDN";
+                               dc.target = mi->mi_targets[ i ];
+                               if ( !asyncmeta_dn_massage( &dc, &match, &mmatch ) ) {
+                                       if ( mmatch.bv_val == match.bv_val ) {
+                                               candidates[ i ].sr_matched
+                                                       = ch_strdup( mmatch.bv_val );
+
+                                       } else {
+                                               candidates[ i ].sr_matched = mmatch.bv_val;
+                                       }
+
+                                       bc->candidate_match++;
+                               }
+                               ldap_memfree( match.bv_val );
+                       }
+
+                       /* add references to array */
+                       /* RFC 4511: referrals can only appear
+                        * if result code is LDAP_REFERRAL */
+                       if ( references != NULL
+                                && references[ 0 ] != NULL
+                                && references[ 0 ][ 0 ] != '\0' )
+                       {
+                               if ( rs->sr_err != LDAP_REFERRAL ) {
+                                       Debug( LDAP_DEBUG_ANY,
+                                                  "%s asncmeta_search_result[%d]: "
+                                                  "got referrals with err=%d\n",
+                                                  op.o_log_prefix,
+                                                  i, rs->sr_err );
+
+                               } else {
+                                       BerVarray       sr_ref;
+                                       int             cnt;
+
+                                       for ( cnt = 0; references[ cnt ]; cnt++ )
+                                               ;
+
+                                       sr_ref = ber_memalloc_x( sizeof( struct berval ) * ( cnt + 1 ),
+                                                                op.o_tmpmemctx );
+
+                                       for ( cnt = 0; references[ cnt ]; cnt++ ) {
+                                               ber_str2bv_x( references[ cnt ], 0, 1, &sr_ref[ cnt ],
+                                                                 op.o_tmpmemctx );
+                                       }
+                                       BER_BVZERO( &sr_ref[ cnt ] );
+
+                                       ( void )asyncmeta_referral_result_rewrite( &dc, sr_ref,
+                                                                                  op.o_tmpmemctx );
+
+                                       if ( rs->sr_v2ref == NULL ) {
+                                               rs->sr_v2ref = sr_ref;
+
+                                       } else {
+                                               for ( cnt = 0; !BER_BVISNULL( &sr_ref[ cnt ] ); cnt++ ) {
+                                                       ber_bvarray_add_x( &rs->sr_v2ref, &sr_ref[ cnt ],
+                                                                          op.o_tmpmemctx );
+                                               }
+                                               ber_memfree_x( sr_ref, op.o_tmpmemctx );
+                                       }
+                               }
+
+                       } else if ( rs->sr_err == LDAP_REFERRAL ) {
+                               Debug( LDAP_DEBUG_ANY,
+                                          "%s asyncmeta_search_result[%d]: "
+                                          "got err=%d with null "
+                                          "or empty referrals\n",
+                                          op.o_log_prefix,
+                                          i, rs->sr_err );
+
+                               rs->sr_err = LDAP_NO_SUCH_OBJECT;
+                       }
+
+                       /* cleanup */
+                       ber_memvfree( (void **)references );
+
+                       sres = slap_map_api2result( rs );
+
+                       if ( LogTest( LDAP_DEBUG_TRACE | LDAP_DEBUG_ANY ) ) {
+                               char buf[ SLAP_TEXT_BUFLEN ];
+                               snprintf( buf, sizeof( buf ),
+                                         "%s asyncmeta_search_result[%ld] "
+                                         "match=\"%s\" err=%d",
+                                         op.o_log_prefix, i,
+                                         candidates[ i ].sr_matched ? candidates[ i ].sr_matched : "",
+                                         (long) candidates[ i ].sr_err );
+                               if ( candidates[ i ].sr_err == LDAP_SUCCESS ) {
+                                       Debug( LDAP_DEBUG_TRACE, "%s.\n", buf, 0, 0 );
+
+                               } else {
+                                       Debug( LDAP_DEBUG_ANY, "%s (%s).\n",
+                                                  buf, ldap_err2string( candidates[ i ].sr_err ), 0 );
+                                                               }
+                       }
+
+                       switch ( sres ) {
+                       case LDAP_NO_SUCH_OBJECT:
+                               /* is_ok is touched any time a valid
+                                * (even intermediate) result is
+                                * returned; as a consequence, if
+                                * a candidate returns noSuchObject
+                                * it is ignored and the candidate
+                                * is simply demoted. */
+                               if ( bc->is_ok ) {
+                                       sres = LDAP_SUCCESS;
+                               }
+                               break;
+
+                       case LDAP_SUCCESS:
+                               if ( ctrls != NULL && ctrls[0] != NULL ) {
+#ifdef SLAPD_META_CLIENT_PR
+                                       LDAPControl *pr_c;
+
+                                       pr_c = ldap_control_find( LDAP_CONTROL_PAGEDRESULTS, ctrls, NULL );
+                                       if ( pr_c != NULL ) {
+                                               BerElementBuffer berbuf;
+                                               BerElement *ber = (BerElement *)&berbuf;
+                                               ber_tag_t tag;
+                                               ber_int_t prsize;
+                                               struct berval prcookie;
+
+                                               /* unsolicited, do not accept */
+                                               if ( mi->mi_targets[i]->mt_ps == 0 ) {
+                                                       rs->sr_err = LDAP_OTHER;
+                                                       goto err_pr;
+                                               }
+
+                                               ber_init2( ber, &pr_c->ldctl_value, LBER_USE_DER );
+
+                                               tag = ber_scanf( ber, "{im}", &prsize, &prcookie );
+                                               if ( tag == LBER_ERROR ) {
+                                                       rs->sr_err = LDAP_OTHER;
+                                                       goto err_pr;
+                                               }
+
+                                               /* more pages? new search request */
+                                               if ( !BER_BVISNULL( &prcookie ) && !BER_BVISEMPTY( &prcookie ) ) {
+                                                       if ( mi->mi_targets[i]->mt_ps > 0 ) {
+                                                               /* ignore size if specified */
+                                                               prsize = 0;
+
+                                                       } else if ( prsize == 0 ) {
+                                                               /* guess the page size from the entries returned so far */
+                                                               prsize = candidates[ i ].sr_nentries;
+                                                       }
+
+                                                       candidates[ i ].sr_nentries = 0;
+                                                       candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+                                                       candidates[ i ].sr_type = REP_INTERMEDIATE;
+
+                                                       assert( candidates[ i ].sr_matched == NULL );
+                                                       assert( candidates[ i ].sr_text == NULL );
+                                                       assert( candidates[ i ].sr_ref == NULL );
+
+                                                       switch ( asyncmeta_back_search_start( &op, rs, mc, bc, i, &prcookie, prsize ) )
+                                                       {
+                                                       case META_SEARCH_CANDIDATE:
+                                                               assert( candidates[ i ].sr_msgid >= 0 );
+                                                               ldap_controls_free( ctrls );
+                                                               //      goto free_message;
+
+                                                       case META_SEARCH_ERR:
+err_pr:;
+                                                               candidates[ i ].sr_err = rs->sr_err;
+                                                               candidates[ i ].sr_type = REP_RESULT;
+                                                               if ( META_BACK_ONERR_STOP( mi ) ) {
+                                                                       send_ldap_result(&op, rs);
+                                                                       ldap_controls_free( ctrls );
+                                                                       goto err_cleanup;
+                                                               }
+                                                               /* fallthru */
+
+                                                       case META_SEARCH_NOT_CANDIDATE:
+                                                               /* means that asyncmeta_back_search_start()
+                                                                * failed but onerr == continue */
+                                                               candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+                                                               candidates[ i ].sr_type = REP_RESULT;
+                                                               break;
+
+                                                       default:
+                                                               /* impossible */
+                                                               assert( 0 );
+                                                               break;
+                                                       }
+                                                       break;
+                                               }
+                                       }
+#endif /* SLAPD_META_CLIENT_PR */
+
+                                       ldap_controls_free( ctrls );
+                               }
+                               /* fallthru */
+
+                       case LDAP_REFERRAL:
+                               bc->is_ok++;
+                               break;
+
+                       case LDAP_SIZELIMIT_EXCEEDED:
+                               /* if a target returned sizelimitExceeded
+                                * and the entry count is equal to the
+                                * proxy's limit, the target would have
+                                * returned more, and the error must be
+                                * propagated to the client; otherwise,
+                                * the target enforced a limit lower
+                                * than what requested by the proxy;
+                                * ignore it */
+                               candidates[ i ].sr_err = rs->sr_err;
+                               if ( rs->sr_nentries == op.ors_slimit
+                                        || META_BACK_ONERR_STOP( mi ) )
+                               {
+                                       const char *save_text;
+got_err:
+                                       save_text = rs->sr_text;
+                                       rs->sr_text = candidates[ i ].sr_text;
+                                       send_ldap_result(&op, rs);
+                                       ch_free( candidates[ i ].sr_text );
+                                       candidates[ i ].sr_text = NULL;
+                                       rs->sr_text = save_text;
+                                       ldap_controls_free( ctrls );
+                                       goto err_cleanup;
+                               }
+                               break;
+
+                       default:
+                               candidates[ i ].sr_err = rs->sr_err;
+                               if ( META_BACK_ONERR_STOP( mi ) ) {
+                                       goto got_err;
+                               }
+                               break;
+                       }
+                       /* if this is the last result we will ever receive, send it back  */
+                       rc = rs->sr_err;
+                       if (asyncmeta_is_last_result(mc, bc, i) == 0) {
+                               Debug( LDAP_DEBUG_TRACE,
+                                       "%s asyncmeta_handle_search_msg: msc %p last result\n",
+                                       op.o_log_prefix, msc, 0);
+                               asyncmeta_search_last_result(mc, bc, i, sres);
+err_cleanup:
+                               rc = rs->sr_err;
+                               ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+                               asyncmeta_drop_bc( mc, bc);
+                               ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+                               asyncmeta_clear_bm_context(bc);
+                               ldap_msgfree(res);
+                               return rc;
+                       }
+finish:
+                       break;
+
+               default:
+                       continue;
+               }
+       }
+               ldap_msgfree(res);
+               res = NULL;
+               if (candidates[ i ].sr_type != REP_RESULT) {
+                       struct timeval  tv = {0};
+                       rc = ldap_result( msc->msc_ldr, id, LDAP_MSG_RECEIVED, &tv, &res );
+               }
+       }
+       bc->bc_active = 0;
+
+       return rc;
+}
+
+/* handles the received result for add, modify, modrdn, compare and delete ops */
+
+int asyncmeta_handle_common_result(LDAPMessage *msg, a_metaconn_t *mc, bm_context_t *bc, int candidate)
+{
+       a_metainfo_t    *mi;
+       a_metatarget_t  *mt;
+       a_metasingleconn_t *msc;
+       const char      *save_text = NULL,
+               *save_matched = NULL;
+       BerVarray       save_ref = NULL;
+       LDAPControl     **save_ctrls = NULL;
+       void            *matched_ctx = NULL;
+
+       char            *matched = NULL;
+       char            *text = NULL;
+       char            **refs = NULL;
+       LDAPControl     **ctrls = NULL;
+       Operation *op;
+       SlapReply *rs;
+       int             rc;
+
+       mi = mc->mc_info;
+       mt = mi->mi_targets[ candidate ];
+       msc = &mc->mc_conns[ candidate ];
+
+       op = bc->op;
+       rs = &bc->rs;
+       save_text = rs->sr_text,
+       save_matched = rs->sr_matched;
+       save_ref = rs->sr_ref;
+       save_ctrls = rs->sr_ctrls;
+       rs->sr_text = NULL;
+       rs->sr_matched = NULL;
+       rs->sr_ref = NULL;
+       rs->sr_ctrls = NULL;
+
+       /* only touch when activity actually took place... */
+       if ( mi->mi_idle_timeout != 0 && msc->msc_time < op->o_time ) {
+               msc->msc_time = op->o_time;
+       }
+
+       rc = ldap_parse_result( msc->msc_ldr, msg, &rs->sr_err,
+                               &matched, &text, &refs, &ctrls, 0 );
+
+       if ( rc == LDAP_SUCCESS ) {
+               rs->sr_text = text;
+       } else {
+               rs->sr_err = rc;
+       }
+       rs->sr_err = slap_map_api2result( rs );
+
+       /* RFC 4511: referrals can only appear
+        * if result code is LDAP_REFERRAL */
+       if ( refs != NULL
+            && refs[ 0 ] != NULL
+            && refs[ 0 ][ 0 ] != '\0' )
+       {
+               if ( rs->sr_err != LDAP_REFERRAL ) {
+                       Debug( LDAP_DEBUG_ANY,
+                              "%s asyncmeta_handle_common_result[%d]: "
+                              "got referrals with err=%d\n",
+                              op->o_log_prefix,
+                              candidate, rs->sr_err );
+
+               } else {
+                       int     i;
+
+                       for ( i = 0; refs[ i ] != NULL; i++ )
+                               /* count */ ;
+                       rs->sr_ref = op->o_tmpalloc( sizeof( struct berval ) * ( i + 1 ),
+                                                    op->o_tmpmemctx );
+                       for ( i = 0; refs[ i ] != NULL; i++ ) {
+                               ber_str2bv( refs[ i ], 0, 0, &rs->sr_ref[ i ] );
+                       }
+                       BER_BVZERO( &rs->sr_ref[ i ] );
+               }
+
+       } else if ( rs->sr_err == LDAP_REFERRAL ) {
+               Debug( LDAP_DEBUG_ANY,
+                      "%s asyncmeta_handle_common_result[%d]: "
+                      "got err=%d with null "
+                      "or empty referrals\n",
+                      op->o_log_prefix,
+                      candidate, rs->sr_err );
+
+               rs->sr_err = LDAP_NO_SUCH_OBJECT;
+       }
+
+       if ( ctrls != NULL ) {
+               rs->sr_ctrls = ctrls;
+       }
+
+       /* if the error in the reply structure is not
+        * LDAP_SUCCESS, try to map it from client
+        * to server error */
+       if ( !LDAP_ERR_OK( rs->sr_err ) ) {
+               rs->sr_err = slap_map_api2result( rs );
+
+               /* internal ops ( op->o_conn == NULL )
+                * must not reply to client */
+               if ( op->o_conn && !op->o_do_not_cache && matched ) {
+
+                       /* record the (massaged) matched
+                        * DN into the reply structure */
+                       rs->sr_matched = matched;
+               }
+       }
+
+       if ( META_BACK_TGT_QUARANTINE( mt ) ) {
+               asyncmeta_quarantine( op, mi, rs, candidate );
+       }
+
+       if ( matched != NULL ) {
+               struct berval   dn, pdn;
+
+               ber_str2bv( matched, 0, 0, &dn );
+               if ( dnPretty( NULL, &dn, &pdn, op->o_tmpmemctx ) == LDAP_SUCCESS ) {
+                       ldap_memfree( matched );
+                       matched_ctx = op->o_tmpmemctx;
+                       matched = pdn.bv_val;
+               }
+               rs->sr_matched = matched;
+       }
+
+       if ( rs->sr_err == LDAP_UNAVAILABLE ) {
+               if ( !( bc->sendok & LDAP_BACK_RETRYING ) ) {
+                       if ( op->o_conn && ( bc->sendok & LDAP_BACK_SENDERR ) ) {
+                               if ( rs->sr_text == NULL ) rs->sr_text = "Proxy operation retry failed";
+                                       send_ldap_result( op, rs );
+                       }
+               }
+
+       } else if ( op->o_conn &&
+               ( ( ( bc->sendok & LDAP_BACK_SENDOK ) && LDAP_ERR_OK( rs->sr_err ) )
+                       || ( ( bc->sendok & LDAP_BACK_SENDERR ) && !LDAP_ERR_OK( rs->sr_err ) ) ) )
+       {
+                       send_ldap_result( op, rs );
+       }
+       if ( matched ) {
+               op->o_tmpfree( (char *)rs->sr_matched, matched_ctx );
+       }
+       if ( text ) {
+               ldap_memfree( text );
+       }
+       if ( rs->sr_ref ) {
+               op->o_tmpfree( rs->sr_ref, op->o_tmpmemctx );
+               rs->sr_ref = NULL;
+       }
+       if ( refs ) {
+               ber_memvfree( (void **)refs );
+       }
+       if ( ctrls ) {
+               assert( rs->sr_ctrls != NULL );
+               ldap_controls_free( ctrls );
+       }
+
+       rs->sr_text = save_text;
+       rs->sr_matched = save_matched;
+       rs->sr_ref = save_ref;
+       rs->sr_ctrls = save_ctrls;
+       rc = (LDAP_ERR_OK( rs->sr_err ) ? LDAP_SUCCESS : rs->sr_err);
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+       asyncmeta_drop_bc( mc, bc);
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+       asyncmeta_clear_bm_context(bc);
+       return rc;
+}
+
+/* This takes care to clean out the outbound queue in case we have a read error
+ * sending back responses to the client */
+int
+asyncmeta_op_read_error(a_metaconn_t *mc, int candidate, int error)
+{
+       bm_context_t *bc, *onext;
+       int rc, cleanup;
+       Operation *op;
+       SlapReply *rs;
+       SlapReply *candidates;
+       /* no outstanding ops, nothing to do but log */
+       Debug( LDAP_DEBUG_ANY,
+              "asyncmeta_op_read_error: %x\n",
+              error,0,0 );
+#if 0
+       if (mc->mc_conns[candidate].conn) {
+               Connection *conn = mc->mc_conns[candidate].conn;
+               mc->mc_conns[candidate].conn = NULL;
+               connection_client_stop(conn);
+       }
+#endif
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+       asyncmeta_clear_one_msc(NULL, mc, candidate);
+       if (mc->pending_ops <= 0) {
+               ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+               return LDAP_SUCCESS;
+       }
+
+       for (bc = LDAP_SLIST_FIRST(&mc->mc_om_list); bc; bc = onext) {
+               onext = LDAP_SLIST_NEXT(bc, bc_next);
+               cleanup = 0;
+               candidates = bc->candidates;
+               /* was this op affected? */
+               if ( !META_IS_CANDIDATE( &candidates[ candidate ] ) )
+                       continue;
+
+               if (bc->op->o_abandon == 1) {
+                       continue;
+               }
+
+               op = bc->op;
+               rs = &bc->rs;
+               switch (op->o_tag) {
+               case LDAP_REQ_ADD:
+               case LDAP_REQ_MODIFY:
+               case LDAP_REQ_MODRDN:
+               case LDAP_REQ_COMPARE:
+               case LDAP_REQ_DELETE:
+                       rs->sr_err = LDAP_UNAVAILABLE;
+                       send_ldap_error( op, rs, rs->sr_err , "Read error on connection to target" );
+                       cleanup = 1;
+                       break;
+               case LDAP_REQ_SEARCH:
+               {
+                       a_metainfo_t *mi = mc->mc_info;
+                       rs->sr_err = LDAP_UNAVAILABLE;
+                       candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+                       candidates[ candidate ].sr_type = REP_RESULT;
+                       if ( META_BACK_ONERR_STOP( mi ) ||
+                               asyncmeta_is_last_result(mc, bc, candidate) && op->o_conn) {
+                               send_ldap_error(op, rs, rs->sr_err, "Read error on connection to target");
+                               cleanup = 1;
+                       }
+               }
+                       break;
+               default:
+                       break;
+               }
+
+               if (cleanup) {
+                       int j;
+                       a_metainfo_t *mi = mc->mc_info;
+                       for (j=0; j<mi->mi_ntargets; j++) {
+                               if (j != candidate && bc->candidates[j].sr_msgid >= 0
+                                   && mc->mc_conns[j].msc_ld != NULL) {
+                                       asyncmeta_back_abandon_candidate( mc, op,
+                                                                         bc->candidates[ j ].sr_msgid, j );
+                               }
+                       }
+                       asyncmeta_drop_bc( mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+                       asyncmeta_clear_bm_context(bc);
+               }
+       }
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+       return LDAP_SUCCESS;
+}
+
+void *
+asyncmeta_op_handle_result(void *ctx, void *arg)
+{
+       a_metaconn_t *mc = arg;
+       int             i, j, rc, ntargets, processed;
+       struct timeval  tv = {0};
+       LDAPMessage     *msg;
+       a_metasingleconn_t *msc;
+       bm_context_t *bc;
+       void *oldctx;
+
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+       rc = ++mc->mc_active;
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+       if (rc > 1)
+               return NULL;
+
+       ntargets = mc->mc_info->mi_ntargets;
+       i = ntargets;
+       oldctx = slap_sl_mem_create(SLAP_SLAB_SIZE, SLAP_SLAB_STACK, ctx, 0);   /* get existing memctx */
+
+again:
+       processed = 0;
+       for (j=0; j<ntargets; j++) {
+               i++;
+               if (i >= ntargets) i = 0;
+               if (!mc->mc_conns[i].msc_ldr) continue;
+               rc = ldap_result( mc->mc_conns[i].msc_ldr, LDAP_RES_ANY, LDAP_MSG_RECEIVED, &tv, &msg );
+               msc = &mc->mc_conns[i];
+               if (rc < 1) {
+                       if (rc < 0) {
+                               asyncmeta_op_read_error(mc, i, rc);
+                       }
+                       continue;
+               }
+               Debug(LDAP_DEBUG_TRACE, "asyncmeta_op_handle_result: got msgid %d on msc %p\n",
+                       ldap_msgid(msg), msc, 0);
+               ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+               bc = asyncmeta_find_message(ldap_msgid(msg), mc, i);
+
+               if (bc)
+                       bc->bc_active = 1;
+               ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+               if (!bc) {
+                       Debug( LDAP_DEBUG_ANY,
+                               "asyncmeta_op_handle_result: Unable to find bc for msguid %d\n", ldap_msgid(msg), 0, 0 );
+                       ldap_msgfree(msg);
+                       continue;
+               }
+
+               /* set our memctx */
+               bc->op->o_threadctx = ctx;
+               bc->op->o_tid = ldap_pvt_thread_pool_tid( ctx );
+               slap_sl_mem_setctx(ctx, bc->op->o_tmpmemctx);
+               if (bc->op->o_abandon == 1) {
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+                       asyncmeta_drop_bc( mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+                       asyncmeta_clear_bm_context(bc);
+                       if (msg)
+                               ldap_msgfree(msg);
+                       continue;
+               }
+
+               rc = ldap_msgtype( msg );
+               switch (rc) {
+               case LDAP_RES_BIND:
+                       asyncmeta_handle_bind_result(msg, mc, bc, i);
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+                       LDAP_SLIST_FOREACH( bc, &mc->mc_om_list, bc_next ) {
+                               if (bc->candidates[i].sr_msgid == META_MSGID_NEED_BIND) {
+                                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+                                       asyncmeta_handle_bind_result(msg, mc, bc, i);
+                                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+                               }
+                       }
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+                       msc->msc_timeout_ops = 0;
+                       mc->mc_info->mi_targets[i]->mt_timeout_ops = 0;
+                       break;
+               case LDAP_RES_SEARCH_ENTRY:
+               case LDAP_RES_SEARCH_REFERENCE:
+               case LDAP_RES_SEARCH_RESULT:
+               case LDAP_RES_INTERMEDIATE:
+                       asyncmeta_handle_search_msg(msg, mc, bc, i);
+                       msc->msc_timeout_ops = 0;
+                       mc->mc_info->mi_targets[i]->mt_timeout_ops = 0;
+                       msg = NULL;
+                       break;
+               case LDAP_RES_ADD:
+               case LDAP_RES_DELETE:
+               case LDAP_RES_MODDN:
+               case LDAP_RES_COMPARE:
+               case LDAP_RES_MODIFY:
+                       rc = asyncmeta_handle_common_result(msg, mc, bc, i);
+                       msc->msc_timeout_ops = 0;
+                       mc->mc_info->mi_targets[i]->mt_timeout_ops = 0;
+                       break;
+               default:
+                       {
+                       Debug( LDAP_DEBUG_ANY,
+                                  "asyncmeta_op_handle_result: "
+                                  "unrecognized response message tag=%d\n",
+                                  rc,0,0 );
+
+                       }
+               }
+               if (msg)
+                       ldap_msgfree(msg);
+       }
+
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+       rc = --mc->mc_active;
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+       if (rc) {
+               i++;
+               goto again;
+       }
+       slap_sl_mem_setctx(ctx, oldctx);
+       if (mc->mc_conns) {
+               for (i=0; i<ntargets; i++)
+                       if (mc->mc_conns[i].msc_ldr && mc->mc_conns[i].conn)
+                               connection_client_enable(mc->mc_conns[i].conn);
+       }
+       return NULL;
+}
+
+void asyncmeta_set_msc_time(a_metasingleconn_t *msc)
+{
+       msc->msc_time = slap_get_time();
+}
+
+void* asyncmeta_timeout_loop(void *ctx, void *arg)
+{
+       struct re_s* rtask = arg;
+       a_metainfo_t *mi = rtask->arg;
+       a_metaconn_t *mc;
+       bm_context_t *bc, *onext;
+       time_t current_time = slap_get_time();
+       int i, j;
+
+       for (i=0; i<mi->mi_num_conns; i++) {
+               mc = &mi->mi_conns[i];
+               ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+               for (bc = LDAP_SLIST_FIRST(&mc->mc_om_list); bc; bc = onext) {
+                       onext = LDAP_SLIST_NEXT(bc, bc_next);
+                       if (!bc->bc_active && bc->timeout && bc->stoptime <= current_time) {
+                               Operation *op = bc->op;
+                               SlapReply *rs = &bc->rs;
+                               int             timeout_err;
+                               const char *timeout_text;
+                               LDAP_SLIST_REMOVE(&mc->mc_om_list, bc, bm_context_t, bc_next);
+                               mc->pending_ops--;
+
+                               if (bc->searchtime) {
+                                       timeout_err = LDAP_TIMELIMIT_EXCEEDED;
+                                       timeout_text = NULL;
+                               } else {
+                                       timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+                                               LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+                                       timeout_text = "Operation timed out";
+                               }
+                               for (j=0; j<mi->mi_ntargets; j++) {
+                                       if (bc->candidates[j].sr_msgid >= 0) {
+                                               a_metasingleconn_t *msc = &mc->mc_conns[j];
+                                               a_metatarget_t     *mt = mi->mi_targets[j];
+                                               msc->msc_timeout_ops++;
+                                               asyncmeta_back_cancel( mc, op,
+                                                                      bc->candidates[ j ].sr_msgid, j );
+                                               if (!META_BACK_TGT_QUARANTINE( mt ) ||
+                                                   bc->candidates[j].sr_type == REP_RESULT) {
+                                                       continue;
+                                               }
+
+                                               if (mt->mt_isquarantined > LDAP_BACK_FQ_NO) {
+                                                       timeout_err = LDAP_UNAVAILABLE;
+                                               } else {
+                                                       mt->mt_timeout_ops++;
+                                                       if ((mi->mi_max_timeout_ops > 0) &&
+                                                           (mt->mt_timeout_ops > mi->mi_max_timeout_ops)) {
+                                                               timeout_err = LDAP_UNAVAILABLE;
+                                                               rs->sr_err = timeout_err;
+                                                               if (mt->mt_isquarantined == LDAP_BACK_FQ_NO)
+                                                                       asyncmeta_quarantine(op, mi, rs, j);
+                                                       }
+                                               }
+                                       }
+                               }
+                               send_ldap_error( op, rs, timeout_err, timeout_text );
+                               asyncmeta_clear_bm_context(bc);
+                       }
+               }
+
+               if (!mc->pending_ops && mi->mi_idle_timeout) {
+                       for (j=0; j<mi->mi_ntargets; j++) {
+                               a_metasingleconn_t *msc = &mc->mc_conns[j];
+                               if (msc->msc_ld && msc->msc_time > 0 && msc->msc_time + mi->mi_idle_timeout <= current_time) {
+                                       if (mc->mc_active < 1) {
+                                               asyncmeta_clear_one_msc(NULL, mc, j);
+                                       }
+                               }
+                       }
+               }
+               ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+       }
+
+       ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+       if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask )) {
+               ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+       }
+       rtask->interval.tv_sec = 1;
+       rtask->interval.tv_usec = 0;
+       ldap_pvt_runqueue_resched(&slapd_rq, rtask, 0);
+       ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+       return NULL;
+}
+
+#if 0
+int
+asyncmeta_back_cleanup( Operation *op, SlapReply *rs, bm_context_t *bc )
+{
+       if (bc->ctrls != NULL && bc->cl != NULL && bc->cl->mc != NULL) {
+               a_metainfo_t *mi = bc->cl->mc->mc_info;
+               (void)mi->mi_ldap_extra->controls_free(op, rs, &bc->ctrls );
+       }
+       asyncmeta_clear_bm_context(bc, 1, 1);
+       return rs->sr_err;
+}
+#endif
diff --git a/servers/slapd/back-asyncmeta/modify.c b/servers/slapd/back-asyncmeta/modify.c
new file mode 100644 (file)
index 0000000..d80f793
--- /dev/null
@@ -0,0 +1,369 @@
+/* modify.c - modify request handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+
+meta_search_candidate_t
+asyncmeta_back_modify_start(Operation *op,
+                           SlapReply *rs,
+                           a_metaconn_t *mc,
+                           bm_context_t *bc,
+                           int candidate)
+{
+       int             i, isupdate, rc = 0, nretries = 1;
+       a_dncookie      dc;
+       a_metainfo_t    *mi = mc->mc_info;
+       a_metatarget_t  *mt = mi->mi_targets[ candidate ];
+       LDAPMod         **modv = NULL;
+       LDAPMod         *mods = NULL;
+       struct berval mdn;
+       Modifications   *ml;
+       struct berval   mapped;
+       meta_search_candidate_t retcode = META_SEARCH_CANDIDATE;
+               BerElement *ber = NULL;
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+       SlapReply               *candidates = bc->candidates;
+       ber_int_t       msgid;
+       LDAPControl             **ctrls = NULL;
+
+       /*
+        * Rewrite the modify dn, if needed
+        */
+       dc.target = mt;
+       dc.conn = op->o_conn;
+       dc.rs = rs;
+       dc.ctx = "modifyDN";
+
+       switch ( asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn ) )
+       {
+       case LDAP_SUCCESS:
+               break;
+       case LDAP_UNWILLING_TO_PERFORM:
+               rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+               rs->sr_text = "Operation not allowed";
+               retcode = META_SEARCH_ERR;
+               goto doreturn;
+       default:
+               rs->sr_err = LDAP_NO_SUCH_OBJECT;
+               retcode = META_SEARCH_NOT_CANDIDATE;
+               goto doreturn;
+       }
+
+       for ( i = 0, ml = op->orm_modlist; ml; i++ ,ml = ml->sml_next )
+               ;
+
+       mods = ch_malloc( sizeof( LDAPMod )*i );
+       if ( mods == NULL ) {
+               rs->sr_err = LDAP_OTHER;
+               retcode = META_SEARCH_ERR;
+               goto doreturn;
+       }
+       modv = ( LDAPMod ** )ch_malloc( ( i + 1 )*sizeof( LDAPMod * ) );
+       if ( modv == NULL ) {
+               rs->sr_err = LDAP_OTHER;
+               retcode = META_SEARCH_ERR;
+               goto doreturn;
+       }
+
+       dc.ctx = "modifyAttrDN";
+       isupdate = be_shadow_update( op );
+       for ( i = 0, ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+               int     j, is_oc = 0;
+
+               if ( !isupdate && !get_relax( op ) && ml->sml_desc->ad_type->sat_no_user_mod  )
+               {
+                       continue;
+               }
+
+               if ( ml->sml_desc == slap_schema.si_ad_objectClass
+                               || ml->sml_desc == slap_schema.si_ad_structuralObjectClass )
+               {
+                       is_oc = 1;
+                       mapped = ml->sml_desc->ad_cname;
+
+               } else {
+                       asyncmeta_map( &mt->mt_rwmap.rwm_at,
+                                       &ml->sml_desc->ad_cname, &mapped,
+                                       BACKLDAP_MAP );
+                       if ( BER_BVISNULL( &mapped ) || BER_BVISEMPTY( &mapped ) ) {
+                               continue;
+                       }
+               }
+
+               modv[ i ] = &mods[ i ];
+               mods[ i ].mod_op = ml->sml_op | LDAP_MOD_BVALUES;
+               mods[ i ].mod_type = mapped.bv_val;
+
+               /*
+                * FIXME: dn-valued attrs should be rewritten
+                * to allow their use in ACLs at the back-ldap
+                * level.
+                */
+               if ( ml->sml_values != NULL ) {
+                       if ( is_oc ) {
+                               for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ )
+                                       ;
+                               mods[ i ].mod_bvalues =
+                                       (struct berval **)ch_malloc( ( j + 1 ) *
+                                       sizeof( struct berval * ) );
+                               for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); ) {
+                                       struct ldapmapping      *mapping;
+
+                                       asyncmeta_mapping( &mt->mt_rwmap.rwm_oc,
+                                                       &ml->sml_values[ j ], &mapping, BACKLDAP_MAP );
+
+                                       if ( mapping == NULL ) {
+                                               if ( mt->mt_rwmap.rwm_oc.drop_missing ) {
+                                                       continue;
+                                               }
+                                               mods[ i ].mod_bvalues[ j ] = &ml->sml_values[ j ];
+
+                                       } else {
+                                               mods[ i ].mod_bvalues[ j ] = &mapping->dst;
+                                       }
+                                       j++;
+                               }
+                               mods[ i ].mod_bvalues[ j ] = NULL;
+
+                       } else {
+                               if ( ml->sml_desc->ad_type->sat_syntax ==
+                                               slap_schema.si_syn_distinguishedName )
+                               {
+                                       ( void )asyncmeta_dnattr_rewrite( &dc, ml->sml_values );
+                                       if ( ml->sml_values == NULL ) {
+                                               continue;
+                                       }
+                               }
+
+                               for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ )
+                                       ;
+                               mods[ i ].mod_bvalues =
+                                       (struct berval **)ch_malloc( ( j + 1 ) *
+                                       sizeof( struct berval * ) );
+                               for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) {
+                                       mods[ i ].mod_bvalues[ j ] = &ml->sml_values[ j ];
+                               }
+                               mods[ i ].mod_bvalues[ j ] = NULL;
+                       }
+
+               } else {
+                       mods[ i ].mod_bvalues = NULL;
+               }
+
+               i++;
+       }
+       modv[ i ] = 0;
+
+retry:;
+       ctrls = op->o_ctrls;
+       if ( asyncmeta_controls_add( op, rs, mc, candidate, &ctrls ) != LDAP_SUCCESS )
+       {
+               candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+               retcode = META_SEARCH_ERR;
+               goto done;
+       }
+
+       ber = ldap_build_modify_req( msc->msc_ld, mdn.bv_val, modv, ctrls, NULL, &msgid);
+       if (ber) {
+               candidates[ candidate ].sr_msgid = msgid;
+               rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_MODIFY,
+                                               mdn.bv_val, ber, msgid );
+               if (rc == msgid)
+                       rc = LDAP_SUCCESS;
+               else
+                       rc = LDAP_SERVER_DOWN;
+
+               switch ( rc ) {
+               case LDAP_SUCCESS:
+                       retcode = META_SEARCH_CANDIDATE;
+                       asyncmeta_set_msc_time(msc);
+                       break;
+
+               case LDAP_SERVER_DOWN:
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_clear_one_msc(NULL, mc, candidate);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       if ( nretries && asyncmeta_retry( op, rs, &mc, candidate, LDAP_BACK_DONTSEND ) ) {
+                               nretries = 0;
+                               /* if the identity changed, there might be need to re-authz */
+                               (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+                               goto retry;
+                       }
+
+               default:
+                       candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+                       retcode = META_SEARCH_ERR;
+               }
+       }
+
+done:
+       (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+       if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+               free( mdn.bv_val );
+               BER_BVZERO( &mdn );
+       }
+       if ( modv != NULL ) {
+               for ( i = 0; modv[ i ]; i++ ) {
+                       free( modv[ i ]->mod_bvalues );
+               }
+       }
+       free( mods );
+       free( modv );
+
+doreturn:;
+       Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_modify_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid );
+       return retcode;
+}
+
+int
+asyncmeta_back_modify( Operation *op, SlapReply *rs )
+{
+       a_metainfo_t    *mi = ( a_metainfo_t * )op->o_bd->be_private;
+       a_metatarget_t  *mt;
+       a_metaconn_t    *mc;
+       int             rc, candidate = -1;
+       OperationBuffer opbuf;
+       bm_context_t *bc;
+       SlapReply *candidates;
+       slap_callback *cb = op->o_callback;
+
+       Debug(LDAP_DEBUG_ARGS, "==> asyncmeta_back_modify: %s\n",
+             op->o_req_dn.bv_val, 0, 0 );
+
+       asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets );
+       if (bc == NULL) {
+               rs->sr_err = LDAP_OTHER;
+               asyncmeta_sender_error(op, rs, cb);
+               return rs->sr_err;
+       }
+
+       candidates = bc->candidates;
+       mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0);
+       if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+               asyncmeta_sender_error(op, rs, cb);
+               asyncmeta_clear_bm_context(bc);
+               return rs->sr_err;
+       }
+
+       mt = mi->mi_targets[ candidate ];
+       bc->timeout = mt->mt_timeout[ SLAP_OP_MODIFY ];
+       bc->retrying = LDAP_BACK_RETRYING;
+       bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying );
+       bc->stoptime = op->o_time + bc->timeout;
+
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+       rc = asyncmeta_add_message_queue(mc, bc);
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+       if (rc != LDAP_SUCCESS) {
+               rs->sr_err = LDAP_BUSY;
+               rs->sr_text = "Maximum pending ops limit exceeded";
+               asyncmeta_clear_bm_context(bc);
+               asyncmeta_sender_error(op, rs, cb);
+               goto finish;
+       }
+
+       rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate);
+       switch (rc)
+       {
+       case META_SEARCH_CANDIDATE:
+               /* target is already bound, just send the request */
+               Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modify:  "
+                      "cnd=\"%ld\"\n", op->o_log_prefix, candidate , 0);
+
+               rc = asyncmeta_back_modify_start( op, rs, mc, bc, candidate);
+               if (rc == META_SEARCH_ERR) {
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_drop_bc(mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       asyncmeta_sender_error(op, rs, cb);
+                       asyncmeta_clear_bm_context(bc);
+                       goto finish;
+
+               }
+                       break;
+       case META_SEARCH_NOT_CANDIDATE:
+               Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modify: NOT_CANDIDATE "
+                      "cnd=\"%ld\"\n", op->o_log_prefix, candidate , 0);
+               candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+               ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+               asyncmeta_drop_bc(mc, bc);
+               ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+               asyncmeta_sender_error(op, rs, cb);
+               asyncmeta_clear_bm_context(bc);
+               goto finish;
+
+       case META_SEARCH_NEED_BIND:
+       case META_SEARCH_CONNECTING:
+               Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modify: NEED_BIND "
+                      "cnd=\"%ld\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+               rc = asyncmeta_dobind_init(op, rs, bc, mc, candidate);
+               if (rc == META_SEARCH_ERR) {
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_drop_bc(mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       asyncmeta_sender_error(op, rs, cb);
+                       asyncmeta_clear_bm_context(bc);
+                       goto finish;
+               }
+               break;
+       case META_SEARCH_BINDING:
+                       Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modify: BINDING "
+                              "cnd=\"%ld\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+                       /* Todo add the context to the message queue but do not send the request
+                          the receiver must send this when we are done binding */
+                       /* question - how would do receiver know to which targets??? */
+                       break;
+
+       case META_SEARCH_ERR:
+                       Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modify: ERR "
+                              "cnd=\"%ldd\"\n", op->o_log_prefix, candidate , 0);
+                       candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+                       candidates[ candidate ].sr_type = REP_RESULT;
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_drop_bc(mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       asyncmeta_sender_error(op, rs, cb);
+                       asyncmeta_clear_bm_context(bc);
+                       goto finish;
+               default:
+                       assert( 0 );
+                       break;
+               }
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+       asyncmeta_start_one_listener(mc, candidates, candidate);
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+finish:
+       return rs->sr_err;
+}
diff --git a/servers/slapd/back-asyncmeta/modrdn.c b/servers/slapd/back-asyncmeta/modrdn.c
new file mode 100644 (file)
index 0000000..a3f729a
--- /dev/null
@@ -0,0 +1,315 @@
+/* modrdn.c - modrdn request handler for back-syncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+
+meta_search_candidate_t
+asyncmeta_back_modrdn_start(Operation *op,
+                           SlapReply *rs,
+                           a_metaconn_t *mc,
+                           bm_context_t *bc,
+                           int candidate)
+{
+       a_dncookie      dc;
+       a_metainfo_t    *mi = mc->mc_info;
+       a_metatarget_t  *mt = mi->mi_targets[ candidate ];
+       struct berval   mdn = BER_BVNULL,
+               mnewSuperior = BER_BVNULL,
+               newrdn = BER_BVNULL;
+       int rc = 0, nretries = 1;
+       LDAPControl     **ctrls = NULL;
+       meta_search_candidate_t retcode = META_SEARCH_CANDIDATE;
+       BerElement *ber = NULL;
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+       SlapReply               *candidates = bc->candidates;
+       ber_int_t       msgid;
+
+       dc.target = mt;
+       dc.conn = op->o_conn;
+       dc.rs = rs;
+
+       if ( op->orr_newSup ) {
+
+               /*
+                * NOTE: the newParent, if defined, must be on the
+                * same target as the entry to be renamed.  This check
+                * has been anticipated in meta_back_getconn()
+                */
+               /*
+                * FIXME: one possibility is to delete the entry
+                * from one target and add it to the other;
+                * unfortunately we'd need write access to both,
+                * which is nearly impossible; for administration
+                * needs, the rootdn of the metadirectory could
+                * be mapped to an administrative account on each
+                * target (the binddn?); we'll see.
+                */
+               /*
+                * NOTE: we need to port the identity assertion
+                * feature from back-ldap
+                */
+
+               /* needs LDAPv3 */
+               switch ( mt->mt_version ) {
+               case LDAP_VERSION3:
+                       break;
+
+               case 0:
+                       if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) {
+                               break;
+                       }
+                       /* fall thru */
+
+               default:
+                       /* op->o_protocol cannot be anything but LDAPv3,
+                        * otherwise wouldn't be here */
+                       rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+                       retcode = META_SEARCH_ERR;
+                       goto done;
+               }
+
+               /*
+                * Rewrite the new superior, if defined and required
+                */
+               dc.ctx = "newSuperiorDN";
+               if ( asyncmeta_dn_massage( &dc, op->orr_newSup, &mnewSuperior ) ) {
+                       rs->sr_err = LDAP_OTHER;
+                       retcode = META_SEARCH_ERR;
+                       goto done;
+               }
+       }
+
+       /*
+        * Rewrite the modrdn dn, if required
+        */
+       dc.ctx = "modrDN";
+       if ( asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn ) ) {
+               rs->sr_err = LDAP_OTHER;
+               retcode = META_SEARCH_ERR;
+               goto done;
+       }
+
+       /* NOTE: we need to copy the newRDN in case it was formed
+        * from a DN by simply changing the length (ITS#5397) */
+       newrdn = op->orr_newrdn;
+       if ( newrdn.bv_val[ newrdn.bv_len ] != '\0' ) {
+               ber_dupbv_x( &newrdn, &op->orr_newrdn, op->o_tmpmemctx );
+       }
+retry:;
+       ctrls = op->o_ctrls;
+       if ( asyncmeta_controls_add( op, rs, mc, candidate, &ctrls ) != LDAP_SUCCESS )
+       {
+               candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+               retcode = META_SEARCH_ERR;
+               goto done;
+       }
+
+       ber = ldap_build_moddn_req( msc->msc_ld, mdn.bv_val, newrdn.bv_val,
+                       mnewSuperior.bv_val, op->orr_deleteoldrdn, ctrls, NULL, &msgid);
+       if (ber) {
+               candidates[ candidate ].sr_msgid = msgid;
+               rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_MODRDN,
+                                               mdn.bv_val, ber, msgid );
+               if (rc == msgid)
+                       rc = LDAP_SUCCESS;
+               else
+                       rc = LDAP_SERVER_DOWN;
+
+               switch ( rc ) {
+               case LDAP_SUCCESS:
+                       retcode = META_SEARCH_CANDIDATE;
+                       asyncmeta_set_msc_time(msc);
+                       break;
+
+               case LDAP_SERVER_DOWN:
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_clear_one_msc(NULL, mc, candidate);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       if ( nretries && asyncmeta_retry( op, rs, &mc, candidate, LDAP_BACK_DONTSEND ) ) {
+                               nretries = 0;
+                               /* if the identity changed, there might be need to re-authz */
+                               (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+                               goto retry;
+                       }
+
+               default:
+                       candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+                       retcode = META_SEARCH_ERR;
+               }
+       }
+
+done:
+       (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+       if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+               free( mdn.bv_val );
+               BER_BVZERO( &mdn );
+       }
+
+       if ( !BER_BVISNULL( &mnewSuperior )
+                       && mnewSuperior.bv_val != op->orr_newSup->bv_val )
+       {
+               free( mnewSuperior.bv_val );
+               BER_BVZERO( &mnewSuperior );
+       }
+
+       if ( newrdn.bv_val != op->orr_newrdn.bv_val ) {
+               op->o_tmpfree( newrdn.bv_val, op->o_tmpmemctx );
+       }
+
+doreturn:;
+       Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_modrdn_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid );
+       return retcode;
+}
+
+int
+asyncmeta_back_modrdn( Operation *op, SlapReply *rs )
+{
+       a_metainfo_t    *mi = ( a_metainfo_t * )op->o_bd->be_private;
+       a_metatarget_t  *mt;
+       a_metaconn_t    *mc;
+       int             rc, candidate = -1;
+       OperationBuffer opbuf;
+       bm_context_t *bc;
+       SlapReply *candidates;
+       slap_callback *cb = op->o_callback;
+
+       Debug(LDAP_DEBUG_ARGS, "==> asyncmeta_back_modrdn: %s\n",
+             op->o_req_dn.bv_val, 0, 0 );
+
+       asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets );
+       if (bc == NULL) {
+               rs->sr_err = LDAP_OTHER;
+               asyncmeta_sender_error(op, rs, cb);
+               return rs->sr_err;
+       }
+
+       candidates = bc->candidates;
+       mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0);
+       if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+               asyncmeta_sender_error(op, rs, cb);
+               asyncmeta_clear_bm_context(bc);
+               return rs->sr_err;
+       }
+
+       mt = mi->mi_targets[ candidate ];
+       bc->timeout = mt->mt_timeout[ SLAP_OP_MODRDN ];
+       bc->retrying = LDAP_BACK_RETRYING;
+       bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying );
+       bc->stoptime = op->o_time + bc->timeout;
+
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+       rc = asyncmeta_add_message_queue(mc, bc);
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+       if (rc != LDAP_SUCCESS) {
+               rs->sr_err = LDAP_BUSY;
+               rs->sr_text = "Maximum pending ops limit exceeded";
+               asyncmeta_clear_bm_context(bc);
+               asyncmeta_sender_error(op, rs, cb);
+               goto finish;
+       }
+
+       rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate);
+       switch (rc)
+       {
+       case META_SEARCH_CANDIDATE:
+               /* target is already bound, just send the request */
+               Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modrdn:  "
+                      "cnd=\"%ld\"\n", op->o_log_prefix, candidate , 0);
+
+               rc = asyncmeta_back_modrdn_start( op, rs, mc, bc, candidate);
+               if (rc == META_SEARCH_ERR) {
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_drop_bc(mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       asyncmeta_sender_error(op, rs, cb);
+                       asyncmeta_clear_bm_context(bc);
+                       goto finish;
+
+               }
+                       break;
+       case META_SEARCH_NOT_CANDIDATE:
+               Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modrdn: NOT_CANDIDATE "
+                      "cnd=\"%ld\"\n", op->o_log_prefix, candidate , 0);
+               candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+               ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+               asyncmeta_drop_bc(mc, bc);
+               ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+               asyncmeta_sender_error(op, rs, cb);
+               asyncmeta_clear_bm_context(bc);
+               goto finish;
+
+       case META_SEARCH_NEED_BIND:
+       case META_SEARCH_CONNECTING:
+               Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modrdn: NEED_BIND "
+                      "cnd=\"%ld\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+               rc = asyncmeta_dobind_init(op, rs, bc, mc, candidate);
+               if (rc == META_SEARCH_ERR) {
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_drop_bc(mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       asyncmeta_sender_error(op, rs, cb);
+                       asyncmeta_clear_bm_context(bc);
+                       goto finish;
+               }
+               break;
+       case META_SEARCH_BINDING:
+                       Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modrdn: BINDING "
+                              "cnd=\"%ld\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+                       /* Todo add the context to the message queue but do not send the request
+                          the receiver must send this when we are done binding */
+                       /* question - how would do receiver know to which targets??? */
+                       break;
+
+       case META_SEARCH_ERR:
+                       Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modrdn: ERR "
+                              "cnd=\"%ldd\"\n", op->o_log_prefix, candidate , 0);
+                       candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+                       candidates[ candidate ].sr_type = REP_RESULT;
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       asyncmeta_drop_bc(mc, bc);
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       asyncmeta_sender_error(op, rs, cb);
+                       asyncmeta_clear_bm_context(bc);
+                       goto finish;
+               default:
+                       assert( 0 );
+                       break;
+               }
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+       asyncmeta_start_one_listener(mc, candidates, candidate);
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+finish:
+       return rs->sr_err;
+}
diff --git a/servers/slapd/back-asyncmeta/proto-asyncmeta.h b/servers/slapd/back-asyncmeta/proto-asyncmeta.h
new file mode 100644 (file)
index 0000000..9cd300e
--- /dev/null
@@ -0,0 +1,54 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#ifndef PROTO_ASYNCMETA_H
+#define PROTO_ASYNCMETA_H
+
+LDAP_BEGIN_DECL
+
+extern BI_init                 asyncmeta_back_initialize;
+
+extern BI_open                 asyncmeta_back_open;
+extern BI_close                        asyncmeta_back_close;
+extern BI_destroy              asyncmeta_back_destroy;
+
+extern BI_db_init              asyncmeta_back_db_init;
+extern BI_db_open              asyncmeta_back_db_open;
+extern BI_db_destroy           asyncmeta_back_db_destroy;
+extern BI_db_close             asyncmeta_back_db_close;
+extern BI_db_config            asyncmeta_back_db_config;
+
+extern BI_op_bind              asyncmeta_back_bind;
+extern BI_op_search            asyncmeta_back_search;
+extern BI_op_compare           asyncmeta_back_compare;
+extern BI_op_modify            asyncmeta_back_modify;
+extern BI_op_modrdn            asyncmeta_back_modrdn;
+extern BI_op_add               asyncmeta_back_add;
+extern BI_op_delete            asyncmeta_back_delete;
+extern BI_op_abandon           asyncmeta_back_abandon;
+
+extern BI_connection_destroy   asyncmeta_back_conn_destroy;
+
+int asyncmeta_back_init_cf( BackendInfo *bi );
+
+LDAP_END_DECL
+
+#endif /* PROTO_ASYNCMETA_H */
diff --git a/servers/slapd/back-asyncmeta/search.c b/servers/slapd/back-asyncmeta/search.c
new file mode 100644 (file)
index 0000000..db7927b
--- /dev/null
@@ -0,0 +1,680 @@
+/* search.c - search request handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "../../../libraries/liblber/lber-int.h"
+
+#include "../../../libraries/libldap/ldap-int.h"
+#undef ldap_debug
+#define        ldap_debug      slap_debug
+
+static void
+asyncmeta_handle_onerr_stop(Operation *op,
+                           SlapReply *rs,
+                           a_metaconn_t *mc,
+                           bm_context_t *bc,
+                           int candidate,
+                           slap_callback *cb)
+{
+       a_metainfo_t *mi = mc->mc_info;
+       int j;
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+       if (bc->bc_active > 0) {
+               ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+               return;
+       }
+       bc->bc_active = 1;
+       asyncmeta_drop_bc(mc, bc);
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+       for (j=0; j<mi->mi_ntargets; j++) {
+               if (j != candidate && bc->candidates[j].sr_msgid >= 0
+                   && mc->mc_conns[j].msc_ld != NULL) {
+                       asyncmeta_back_abandon_candidate( mc, op,
+                                               bc->candidates[ j ].sr_msgid, j );
+               }
+       }
+       if (cb != NULL) {
+               op->o_callback = cb;
+       }
+       send_ldap_result(op, rs);
+       asyncmeta_clear_bm_context(bc);
+}
+
+meta_search_candidate_t
+asyncmeta_back_search_start(
+                               Operation *op,
+                               SlapReply *rs,
+                           a_metaconn_t *mc,
+                           bm_context_t *bc,
+                           int candidate,
+                           struct berval               *prcookie,
+                           ber_int_t           prsize )
+{
+       SlapReply               *candidates = bc->candidates;
+       a_metainfo_t            *mi = ( a_metainfo_t * )mc->mc_info;
+       a_metatarget_t          *mt = mi->mi_targets[ candidate ];
+       a_metasingleconn_t      *msc = &mc->mc_conns[ candidate ];
+       a_dncookie              dc;
+       struct berval           realbase = op->o_req_dn;
+       int                     realscope = op->ors_scope;
+       struct berval           mbase = BER_BVNULL;
+       struct berval           mfilter = BER_BVNULL;
+       char                    **mapped_attrs = NULL;
+       int                     rc;
+       meta_search_candidate_t retcode;
+       int timelimit;
+       int                     nretries = 1;
+       LDAPControl             **ctrls = NULL;
+       BerElement *ber;
+       ber_int_t       msgid;
+#ifdef SLAPD_META_CLIENT_PR
+       LDAPControl             **save_ctrls = NULL;
+#endif /* SLAPD_META_CLIENT_PR */
+
+       /* this should not happen; just in case... */
+       if ( msc->msc_ld == NULL ) {
+               Debug( LDAP_DEBUG_ANY,
+                       "%s: asyncmeta_back_search_start candidate=%d ld=NULL%s.\n",
+                       op->o_log_prefix, candidate,
+                       META_BACK_ONERR_STOP( mi ) ? "" : " (ignored)" );
+               candidates[ candidate ].sr_err = LDAP_OTHER;
+               if ( META_BACK_ONERR_STOP( mi ) ) {
+                       return META_SEARCH_ERR;
+               }
+               candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+               return META_SEARCH_NOT_CANDIDATE;
+       }
+
+       Debug( LDAP_DEBUG_TRACE, "%s >>> asyncmeta_back_search_start[%d]\n", op->o_log_prefix, candidate, 0 );
+       /*
+        * modifies the base according to the scope, if required
+        */
+       if ( mt->mt_nsuffix.bv_len > op->o_req_ndn.bv_len ) {
+               switch ( op->ors_scope ) {
+               case LDAP_SCOPE_SUBTREE:
+                       /*
+                        * make the target suffix the new base
+                        * FIXME: this is very forgiving, because
+                        * "illegal" searchBases may be turned
+                        * into the suffix of the target; however,
+                        * the requested searchBase already passed
+                        * thru the candidate analyzer...
+                        */
+                       if ( dnIsSuffix( &mt->mt_nsuffix, &op->o_req_ndn ) ) {
+                               realbase = mt->mt_nsuffix;
+                               if ( mt->mt_scope == LDAP_SCOPE_SUBORDINATE ) {
+                                       realscope = LDAP_SCOPE_SUBORDINATE;
+                               }
+
+                       } else {
+                               /*
+                                * this target is no longer candidate
+                                */
+                               retcode = META_SEARCH_NOT_CANDIDATE;
+                               goto doreturn;
+                       }
+                       break;
+
+               case LDAP_SCOPE_SUBORDINATE:
+               case LDAP_SCOPE_ONELEVEL:
+               {
+                       struct berval   rdn = mt->mt_nsuffix;
+                       rdn.bv_len -= op->o_req_ndn.bv_len + STRLENOF( "," );
+                       if ( dnIsOneLevelRDN( &rdn )
+                                       && dnIsSuffix( &mt->mt_nsuffix, &op->o_req_ndn ) )
+                       {
+                               /*
+                                * if there is exactly one level,
+                                * make the target suffix the new
+                                * base, and make scope "base"
+                                */
+                               realbase = mt->mt_nsuffix;
+                               if ( op->ors_scope == LDAP_SCOPE_SUBORDINATE ) {
+                                       if ( mt->mt_scope == LDAP_SCOPE_SUBORDINATE ) {
+                                               realscope = LDAP_SCOPE_SUBORDINATE;
+                                       } else {
+                                               realscope = LDAP_SCOPE_SUBTREE;
+                                       }
+                               } else {
+                                       realscope = LDAP_SCOPE_BASE;
+                               }
+                               break;
+                       } /* else continue with the next case */
+               }
+
+               case LDAP_SCOPE_BASE:
+                       /*
+                        * this target is no longer candidate
+                        */
+                       retcode = META_SEARCH_NOT_CANDIDATE;
+                       goto doreturn;
+               }
+       }
+
+       /* check filter expression */
+       if ( mt->mt_filter ) {
+               metafilter_t *mf;
+               for ( mf = mt->mt_filter; mf; mf = mf->mf_next ) {
+                       if ( regexec( &mf->mf_regex, op->ors_filterstr.bv_val, 0, NULL, 0 ) == 0 )
+                               break;
+               }
+               /* nothing matched, this target is no longer a candidate */
+               if ( !mf ) {
+                       retcode = META_SEARCH_NOT_CANDIDATE;
+                       goto doreturn;
+               }
+       }
+
+       /*
+        * Rewrite the search base, if required
+        */
+       dc.target = mt;
+       dc.ctx = "searchBase";
+       dc.conn = op->o_conn;
+       dc.rs = rs;
+       switch ( asyncmeta_dn_massage( &dc, &realbase, &mbase ) ) {
+       case LDAP_SUCCESS:
+               break;
+
+       case LDAP_UNWILLING_TO_PERFORM:
+               rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+               rs->sr_text = "Operation not allowed";
+               retcode = META_SEARCH_ERR;
+               goto doreturn;
+
+       default:
+
+               /*
+                * this target is no longer candidate
+                */
+               retcode = META_SEARCH_NOT_CANDIDATE;
+               goto doreturn;
+       }
+
+       /*
+        * Maps filter
+        */
+       rc = asyncmeta_filter_map_rewrite( &dc, op->ors_filter,
+                       &mfilter, BACKLDAP_MAP, NULL );
+       switch ( rc ) {
+       case LDAP_SUCCESS:
+               break;
+
+       case LDAP_COMPARE_FALSE:
+       default:
+               /*
+                * this target is no longer candidate
+                */
+               retcode = META_SEARCH_NOT_CANDIDATE;
+               goto done;
+       }
+
+       /*
+        * Maps required attributes
+        */
+       rc = asyncmeta_map_attrs( op, &mt->mt_rwmap.rwm_at,
+                       op->ors_attrs, BACKLDAP_MAP, &mapped_attrs );
+       if ( rc != LDAP_SUCCESS ) {
+               /*
+                * this target is no longer candidate
+                */
+               retcode = META_SEARCH_NOT_CANDIDATE;
+               goto done;
+       }
+
+       if ( op->ors_tlimit != SLAP_NO_LIMIT ) {
+               timelimit = op->ors_tlimit > 0 ? op->ors_tlimit : 1;
+       } else {
+               timelimit = -1; /* no limit */
+       }
+
+#ifdef SLAPD_META_CLIENT_PR
+       save_ctrls = op->o_ctrls;
+       {
+               LDAPControl *pr_c = NULL;
+               int i = 0, nc = 0;
+
+               if ( save_ctrls ) {
+                       for ( ; save_ctrls[i] != NULL; i++ );
+                       nc = i;
+                       pr_c = ldap_control_find( LDAP_CONTROL_PAGEDRESULTS, save_ctrls, NULL );
+               }
+
+               if ( pr_c != NULL ) nc--;
+               if ( mt->mt_ps > 0 || prcookie != NULL ) nc++;
+
+               if ( mt->mt_ps > 0 || prcookie != NULL || pr_c != NULL ) {
+                       int src = 0, dst = 0;
+                       BerElementBuffer berbuf;
+                       BerElement *ber = (BerElement *)&berbuf;
+                       struct berval val = BER_BVNULL;
+                       ber_len_t len;
+
+                       len = sizeof( LDAPControl * )*( nc + 1 ) + sizeof( LDAPControl );
+
+                       if ( mt->mt_ps > 0 || prcookie != NULL ) {
+                               struct berval nullcookie = BER_BVNULL;
+                               ber_tag_t tag;
+
+                               if ( prsize == 0 && mt->mt_ps > 0 ) prsize = mt->mt_ps;
+                               if ( prcookie == NULL ) prcookie = &nullcookie;
+
+                               ber_init2( ber, NULL, LBER_USE_DER );
+                               tag = ber_printf( ber, "{iO}", prsize, prcookie );
+                               if ( tag == LBER_ERROR ) {
+                                       /* error */
+                                       (void) ber_free_buf( ber );
+                                       goto done_pr;
+                               }
+
+                               tag = ber_flatten2( ber, &val, 0 );
+                               if ( tag == LBER_ERROR ) {
+                                       /* error */
+                                       (void) ber_free_buf( ber );
+                                       goto done_pr;
+                               }
+
+                               len += val.bv_len + 1;
+                       }
+
+                       op->o_ctrls = op->o_tmpalloc( len, op->o_tmpmemctx );
+                       if ( save_ctrls ) {
+                               for ( ; save_ctrls[ src ] != NULL; src++ ) {
+                                       if ( save_ctrls[ src ] != pr_c ) {
+                                               op->o_ctrls[ dst ] = save_ctrls[ src ];
+                                               dst++;
+                                       }
+                               }
+                       }
+
+                       if ( mt->mt_ps > 0 || prcookie != NULL ) {
+                               op->o_ctrls[ dst ] = (LDAPControl *)&op->o_ctrls[ nc + 1 ];
+
+                               op->o_ctrls[ dst ]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS;
+                               op->o_ctrls[ dst ]->ldctl_iscritical = 1;
+
+                               op->o_ctrls[ dst ]->ldctl_value.bv_val = (char *)&op->o_ctrls[ dst ][ 1 ];
+                               AC_MEMCPY( op->o_ctrls[ dst ]->ldctl_value.bv_val, val.bv_val, val.bv_len + 1 );
+                               op->o_ctrls[ dst ]->ldctl_value.bv_len = val.bv_len;
+                               dst++;
+
+                               (void)ber_free_buf( ber );
+                       }
+
+                       op->o_ctrls[ dst ] = NULL;
+               }
+done_pr:;
+       }
+#endif /* SLAPD_META_CLIENT_PR */
+
+retry:;
+       asyncmeta_set_msc_time(msc);
+       ctrls = op->o_ctrls;
+       if (nretries == 0)
+       {
+               if (rc != LDAP_SUCCESS)
+               {
+                       rs->sr_err = LDAP_BUSY;
+                       retcode = META_SEARCH_ERR;
+                       candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+                       goto done;
+               }
+       }
+
+       if ( asyncmeta_controls_add( op, rs, mc, candidate, &ctrls )
+               != LDAP_SUCCESS )
+       {
+               candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+               retcode = META_SEARCH_NOT_CANDIDATE;
+               goto done;
+       }
+
+       /*
+        * Starts the search
+        */
+       ber = ldap_build_search_req( msc->msc_ld,
+                       mbase.bv_val, realscope, mfilter.bv_val,
+                       mapped_attrs, op->ors_attrsonly,
+                       ctrls, NULL, timelimit, op->ors_slimit, op->ors_deref,
+                       &msgid );
+       if (ber) {
+               candidates[ candidate ].sr_msgid = msgid;
+               rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_SEARCH,
+                       mbase.bv_val, ber, msgid );
+               if (rc == msgid)
+                       rc = LDAP_SUCCESS;
+               else
+                       rc = LDAP_SERVER_DOWN;
+               switch ( rc ) {
+               case LDAP_SUCCESS:
+                       retcode = META_SEARCH_CANDIDATE;
+                       asyncmeta_set_msc_time(msc);
+                       break;
+
+               case LDAP_SERVER_DOWN:
+                       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+                       if (mc->mc_active < 1) {
+                               asyncmeta_clear_one_msc(NULL, mc, candidate);
+                       }
+                       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+                       if ( nretries && asyncmeta_retry( op, rs, &mc, candidate, LDAP_BACK_DONTSEND ) ) {
+                               nretries = 0;
+                               /* if the identity changed, there might be need to re-authz */
+                               (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+                               goto retry;
+                       }
+                       rs->sr_err = LDAP_UNAVAILABLE;
+                       retcode = META_SEARCH_ERR;
+                       break;
+               default:
+                       candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+                       retcode = META_SEARCH_NOT_CANDIDATE;
+               }
+       }
+
+done:;
+       (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+#ifdef SLAPD_META_CLIENT_PR
+       if ( save_ctrls != op->o_ctrls ) {
+               op->o_tmpfree( op->o_ctrls, op->o_tmpmemctx );
+               op->o_ctrls = save_ctrls;
+       }
+#endif /* SLAPD_META_CLIENT_PR */
+
+       if ( mapped_attrs ) {
+               ber_memfree_x( mapped_attrs, op->o_tmpmemctx );
+       }
+       if ( mfilter.bv_val != op->ors_filterstr.bv_val ) {
+               ber_memfree_x( mfilter.bv_val, NULL );
+       }
+       if ( mbase.bv_val != realbase.bv_val ) {
+               free( mbase.bv_val );
+       }
+
+doreturn:;
+       Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_search_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid );
+       return retcode;
+}
+
+int
+asyncmeta_back_search( Operation *op, SlapReply *rs )
+{
+       a_metainfo_t    *mi = ( a_metainfo_t * )op->o_bd->be_private;
+       struct timeval  save_tv = { 0, 0 },
+                       tv;
+       time_t          stoptime = (time_t)(-1),
+                       lastres_time = slap_get_time(),
+                       timeout = 0;
+       int             rc = 0, sres = LDAP_SUCCESS;
+       char            *matched = NULL;
+       int             last = 0, ncandidates = 0,
+                       initial_candidates = 0, candidate_match = 0,
+                       needbind = 0;
+       ldap_back_send_t        sendok = LDAP_BACK_SENDERR;
+       long            i;
+       int             is_ok = 0;
+       void            *savepriv;
+       SlapReply       *candidates = NULL;
+       int             do_taint = 0;
+       bm_context_t *bc;
+       a_metaconn_t *mc;
+       slap_callback *cb = op->o_callback;
+
+       rs_assert_ready( rs );
+       rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia, we can set rs = non-entry */
+
+       /*
+        * controls are set in ldap_back_dobind()
+        *
+        * FIXME: in case of values return filter, we might want
+        * to map attrs and maybe rewrite value
+        */
+
+       asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets );
+       if (bc == NULL) {
+               rs->sr_err = LDAP_OTHER;
+               send_ldap_result(op, rs);
+               return rs->sr_err;
+       }
+
+       candidates = bc->candidates;
+       mc = asyncmeta_getconn( op, rs, candidates, NULL, LDAP_BACK_DONTSEND, 0);
+       if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+               op->o_callback = cb;
+               send_ldap_result(op, rs);
+               asyncmeta_clear_bm_context(bc);
+               return rs->sr_err;
+       }
+
+       /*
+        * Inits searches
+        */
+
+       for ( i = 0; i < mi->mi_ntargets; i++ ) {
+               /* reset sr_msgid; it is used in most loops
+                * to check if that target is still to be considered */
+               candidates[ i ].sr_msgid = META_MSGID_UNDEFINED;
+
+               /* a target is marked as candidate by asyncmeta_getconn();
+                * if for any reason (an error, it's over or so) it is
+                * no longer active, sr_msgid is set to META_MSGID_IGNORE
+                * but it remains candidate, which means it has been active
+                * at some point during the operation.  This allows to
+                * use its response code and more to compute the final
+                * response */
+               if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+                       continue;
+               }
+
+               candidates[ i ].sr_matched = NULL;
+               candidates[ i ].sr_text = NULL;
+               candidates[ i ].sr_ref = NULL;
+               candidates[ i ].sr_ctrls = NULL;
+               candidates[ i ].sr_nentries = 0;
+               candidates[ i ].sr_type = -1;
+
+               /* get largest timeout among candidates */
+               if ( mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ]
+                       && mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ] > timeout )
+               {
+                       timeout = mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ];
+               }
+       }
+
+       bc->timeout = timeout;
+       bc->stoptime = op->o_time + bc->timeout;
+
+       if ( op->ors_tlimit != SLAP_NO_LIMIT ) {
+               stoptime = op->o_time + op->ors_tlimit;
+               if (stoptime < bc->stoptime) {
+                       bc->stoptime = stoptime;
+                       bc->searchtime = 1;
+                       bc->timeout = op->ors_tlimit;
+               }
+       }
+
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+       rc = asyncmeta_add_message_queue(mc, bc);
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+       if (rc != LDAP_SUCCESS) {
+               rs->sr_err = LDAP_BUSY;
+               rs->sr_text = "Maximum pending ops limit exceeded";
+               asyncmeta_clear_bm_context(bc);
+               op->o_callback = cb;
+               send_ldap_result(op, rs);
+               goto finish;
+       }
+
+       for ( i = 0; i < mi->mi_ntargets; i++ ) {
+               if ( !META_IS_CANDIDATE( &candidates[ i ] )
+                       || candidates[ i ].sr_err != LDAP_SUCCESS )
+               {
+                       continue;
+               }
+
+               rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, i);
+               switch (rc)
+               {
+               case META_SEARCH_CANDIDATE:
+                       /* target is already bound, just send the search request */
+                       ncandidates++;
+                       Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_search: IS_CANDIDATE "
+                              "cnd=\"%ld\"\n", op->o_log_prefix, i , 0);
+
+                       rc = asyncmeta_back_search_start( op, rs, mc, bc, i,  NULL, 0 );
+                       if (rc == META_SEARCH_ERR) {
+                               META_CANDIDATE_CLEAR(&candidates[i]);
+                               candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+                               if ( META_BACK_ONERR_STOP( mi ) ) {
+                                       asyncmeta_handle_onerr_stop(op,rs,mc,bc,i,cb);
+                                       goto finish;
+                               }
+                               else {
+                                       continue;
+                               }
+                       }
+                       break;
+               case META_SEARCH_NOT_CANDIDATE:
+                       Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_search: NOT_CANDIDATE "
+                              "cnd=\"%ld\"\n", op->o_log_prefix, i , 0);
+                       candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+                       break;
+
+               case META_SEARCH_NEED_BIND:
+               case META_SEARCH_CONNECTING:
+                       Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_search: NEED_BIND "
+                              "cnd=\"%ld\" %p\n", op->o_log_prefix, i , &mc->mc_conns[i]);
+                       ncandidates++;
+                       rc = asyncmeta_dobind_init(op, rs, bc, mc, i);
+                       if (rc == META_SEARCH_ERR) {
+                               candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+                               if ( META_BACK_ONERR_STOP( mi ) ) {
+                                       asyncmeta_handle_onerr_stop(op,rs,mc,bc,i,cb);
+                                       goto finish;
+                               }
+                               else {
+                                       continue;
+                               }
+                       }
+                       break;
+               case META_SEARCH_BINDING:
+                       Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_search: BINDING "
+                              "cnd=\"%ld\" %p\n", op->o_log_prefix, i , &mc->mc_conns[i]);
+                       ncandidates++;
+                       /* Todo add the context to the message queue but do not send the request
+                        the receiver must send this when we are done binding */
+                       /* question - how would do receiver know to which targets??? */
+                       break;
+
+               case META_SEARCH_ERR:
+                       Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_search: SEARCH_ERR "
+                              "cnd=\"%ldd\"\n", op->o_log_prefix, i , 0);
+                       candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+                       candidates[ i ].sr_type = REP_RESULT;
+
+                       if ( META_BACK_ONERR_STOP( mi ) ) {
+                               asyncmeta_handle_onerr_stop(op,rs,mc,bc,i,cb);
+                               goto finish;
+                       }
+                       else {
+                               continue;
+                       }
+                       break;
+
+               default:
+                       assert( 0 );
+                       break;
+               }
+       }
+
+       initial_candidates = ncandidates;
+
+       if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+               char    cnd[ SLAP_TEXT_BUFLEN ];
+               int     c;
+
+               for ( c = 0; c < mi->mi_ntargets; c++ ) {
+                       if ( META_IS_CANDIDATE( &candidates[ c ] ) ) {
+                               cnd[ c ] = '*';
+                       } else {
+                               cnd[ c ] = ' ';
+                       }
+               }
+               cnd[ c ] = '\0';
+
+               Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_search: ncandidates=%d "
+                       "cnd=\"%s\"\n", op->o_log_prefix, ncandidates, cnd );
+       }
+
+       if ( initial_candidates == 0 ) {
+               /* NOTE: here we are not sending any matchedDN;
+                * this is intended, because if the back-meta
+                * is serving this search request, but no valid
+                * candidate could be looked up, it means that
+                * there is a hole in the mapping of the targets
+                * and thus no knowledge of any remote superior
+                * is available */
+               Debug( LDAP_DEBUG_ANY, "%s asyncmeta_back_search: "
+                       "base=\"%s\" scope=%d: "
+                       "no candidate could be selected\n",
+                       op->o_log_prefix, op->o_req_dn.bv_val,
+                       op->ors_scope );
+
+               /* FIXME: we're sending the first error we encounter;
+                * maybe we should pick the worst... */
+               rc = LDAP_NO_SUCH_OBJECT;
+               for ( i = 0; i < mi->mi_ntargets; i++ ) {
+                       if ( META_IS_CANDIDATE( &candidates[ i ] )
+                               && candidates[ i ].sr_err != LDAP_SUCCESS )
+                       {
+                               rc = candidates[ i ].sr_err;
+                               break;
+                       }
+               }
+               rs->sr_err = rc;
+               ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+               asyncmeta_drop_bc(mc, bc);
+               ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+               op->o_callback = cb;
+               send_ldap_result(op, rs);
+               asyncmeta_clear_bm_context(bc);
+               goto finish;
+       }
+       ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+       asyncmeta_start_listeners(mc, candidates);
+       ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+finish:
+       return rs->sr_err;
+}
diff --git a/servers/slapd/back-asyncmeta/suffixmassage.c b/servers/slapd/back-asyncmeta/suffixmassage.c
new file mode 100644 (file)
index 0000000..8c1bd3c
--- /dev/null
@@ -0,0 +1,112 @@
+/* suffixmassage.c - massages ldap backend dns */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+/* This is an altered version */
+
+/*
+ * Copyright 1999, Howard Chu, All rights reserved. <hyc@highlandsun.com>
+ * Copyright 2000, Pierangelo Masarati, All rights reserved. <ando@sys-net.it>
+ *
+ * Module back-ldap, originally developed by Howard Chu
+ *
+ * has been modified by Pierangelo Masarati. The original copyright
+ * notice has been maintained.
+ *
+ * Permission is granted to anyone to use this software for any purpose
+ * on any computer system, and to alter it and redistribute it, subject
+ * to the following restrictions:
+ *
+ * 1. The author is not responsible for the consequences of use of this
+ *    software, no matter how awful, even if they arise from flaws in it.
+ *
+ * 2. The origin of this software must not be misrepresented, either by
+ *    explicit claim or by omission.  Since few users ever read sources,
+ *    credits should appear in the documentation.
+ *
+ * 3. Altered versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.  Since few users
+ *    ever read sources, credits should appear in the documentation.
+ *
+ * 4. This notice may not be removed or altered.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+int
+asyncmeta_dn_massage(
+       a_dncookie      *dc,
+       struct berval   *dn,
+       struct berval   *res )
+{
+       int             rc = 0;
+       static char     *dmy = "";
+
+       switch ( rewrite_session( dc->target->mt_rwmap.rwm_rw, dc->ctx,
+                               ( dn->bv_val ? dn->bv_val : dmy ),
+                               dc->conn, &res->bv_val ) )
+       {
+       case REWRITE_REGEXEC_OK:
+               if ( res->bv_val != NULL ) {
+                       res->bv_len = strlen( res->bv_val );
+               } else {
+                       *res = *dn;
+               }
+               Debug( LDAP_DEBUG_ARGS,
+                       "[rw] %s: \"%s\" -> \"%s\"\n",
+                       dc->ctx,
+                       BER_BVISNULL( dn ) ? "" : dn->bv_val,
+                       BER_BVISNULL( res ) ? "" : res->bv_val );
+               rc = LDAP_SUCCESS;
+               break;
+
+       case REWRITE_REGEXEC_UNWILLING:
+               if ( dc->rs ) {
+                       dc->rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+                       dc->rs->sr_text = "Operation not allowed";
+               }
+               rc = LDAP_UNWILLING_TO_PERFORM;
+               break;
+
+       case REWRITE_REGEXEC_ERR:
+               if ( dc->rs ) {
+                       dc->rs->sr_err = LDAP_OTHER;
+                       dc->rs->sr_text = "Rewrite error";
+               }
+               rc = LDAP_OTHER;
+               break;
+       }
+
+       if ( res->bv_val == dmy ) {
+               BER_BVZERO( res );
+       }
+
+       return rc;
+}
diff --git a/servers/slapd/back-asyncmeta/unbind.c b/servers/slapd/back-asyncmeta/unbind.c
new file mode 100644 (file)
index 0000000..58207bf
--- /dev/null
@@ -0,0 +1,55 @@
+/* unbind.c - unbind handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+int
+asyncmeta_back_conn_destroy(
+       Backend         *be,
+       Connection      *conn )
+{
+       a_metainfo_t    *mi = ( a_metainfo_t * )be->be_private;
+       int             i;
+
+       Debug( LDAP_DEBUG_TRACE,
+               "=>asyncmeta_back_conn_destroy: fetching conn=%ld DN=\"%s\"\n",
+               conn->c_connid,
+               BER_BVISNULL( &conn->c_ndn ) ? "" : conn->c_ndn.bv_val, 0 );
+       /*
+        * Cleanup rewrite session
+        */
+       for ( i = 0; i < mi->mi_ntargets; ++i ) {
+               rewrite_session_delete( mi->mi_targets[ i ]->mt_rwmap.rwm_rw, conn );
+       }
+
+       return 0;
+}
index 41d03e912409c1e2c01b0e4030483124282c0bf7..c76b9d48e7f646757c1aec39e0e82a43e9668be0 100644 (file)
@@ -255,6 +255,21 @@ over_back_response ( Operation *op, SlapReply *rs )
        return rc;
 }
 
+static int
+over_back_response_cleanup(Operation *op, SlapReply *rs)
+{
+    if (rs->sr_type == REP_RESULT) {
+        if (op->o_callback != NULL) {
+            slap_callback *sc = op->o_callback;
+            op->o_callback = sc->sc_next;
+
+            free( sc );
+        }
+    }
+
+    return 0;
+}
+
 static int
 over_access_allowed(
        Operation               *op,
@@ -727,7 +742,8 @@ over_op_func(
        slap_overinfo *oi;
        slap_overinst *on;
        BackendDB *be = op->o_bd, db;
-       slap_callback cb = {NULL, over_back_response, NULL, NULL}, **sc;
+       slap_callback **sc;
+       slap_callback *cb = (slap_callback *) ch_malloc( sizeof( slap_callback ));
        int rc = SLAP_CB_CONTINUE;
 
        /* FIXME: used to happen for instance during abandon
@@ -742,14 +758,18 @@ over_op_func(
                db.be_flags |= SLAP_DBFLAG_OVERLAY;
                op->o_bd = &db;
        }
-       cb.sc_next = op->o_callback;
-       cb.sc_private = oi;
-       op->o_callback = &cb;
+       cb->sc_cleanup = over_back_response_cleanup;
+       cb->sc_response = over_back_response;
+       cb->sc_writewait = NULL;
+       cb->sc_next = op->o_callback;
+       cb->sc_private = oi;
+       op->o_callback = cb;
 
        rc = overlay_op_walk( op, rs, which, oi, on );
        for ( sc = &op->o_callback; *sc; sc = &(*sc)->sc_next ) {
-               if ( *sc == &cb ) {
-                       *sc = cb.sc_next;
+               if ( *sc == cb ) {
+                       *sc = cb->sc_next;
+                       ch_free( cb );
                        break;
                }
        }
index fe366cf4351017d4e4a0d693405ca4f47fb00855..9c6790e022d183b8085f4b7c46d5b7c3ac4765ba 100644 (file)
@@ -185,7 +185,7 @@ main( int argc, char **argv )
        /* by default, tolerate referrals and no such object */
        tester_ignore_str2errlist( "REFERRAL,NO_SUCH_OBJECT" );
 
-       while ( (i = getopt( argc, argv, "ACc:D:e:Ff:H:h:i:L:l:M:m:p:r:t:T:w:v" )) != EOF ) {
+       while ( (i = getopt( argc, argv, "ACc:D:e:Ff:H:h:i:L:l:M:m:Np:r:t:T:w:v" )) != EOF ) {
                switch ( i ) {
                case 'A':
                        noattrs++;