]> git.sur5r.net Git - openldap/commitdiff
import retcode overlay
authorPierangelo Masarati <ando@openldap.org>
Sun, 19 Jun 2005 22:39:44 +0000 (22:39 +0000)
committerPierangelo Masarati <ando@openldap.org>
Sun, 19 Jun 2005 22:39:44 +0000 (22:39 +0000)
doc/man/man5/slapo-retcode.5 [new file with mode: 0644]
servers/slapd/overlays/retcode.c [new file with mode: 0644]
tests/data/retcode.conf [new file with mode: 0644]
tests/data/slapd-retcode.conf [new file with mode: 0644]
tests/scripts/test038-retcode [new file with mode: 0755]

diff --git a/doc/man/man5/slapo-retcode.5 b/doc/man/man5/slapo-retcode.5
new file mode 100644 (file)
index 0000000..5fae8b1
--- /dev/null
@@ -0,0 +1,153 @@
+.TH SLAPO-RETCODE5 "RELEASEDATE" "OpenLDAP LDVERSION"
+.\" Copyright 1998-2004 The OpenLDAP Foundation, All Rights Reserved.
+.\" Copying restrictions apply.  See the COPYRIGHT file.
+.\" Copyright 2001, Pierangelo Masarati, All rights reserved. <ando@sys-net.it>
+.\" $Header$
+.SH NAME
+slapo-retcode \- dynamic listing overlay
+.SH SYNOPSIS
+ETCDIR/slapd.conf
+.SH DESCRIPTION
+The
+.B retcode
+overlay to
+.BR slapd (8)
+is usefult to test the behavior of clients when server-generated erroneous
+and/or unusual responses occur, e.g. error codes, referrals, 
+excessive response times and so on.
+
+The error responses are generated according to different strategies.
+.LP
+In the first case, all operations targeted at a specific configurable
+subtree cause the object related to the request DN to be looked up
+and checked for return code data: a response code, plus an optional
+textual message, an optional configurable delay, and, when the response code
+is referral, a (list of) referral(s).
+.LP
+Well-known response codes from standard track documents are provided
+in \fBretcode.conf\fP, which can be included after instantiating
+the overlay.
+.LP
+In the second case, objects of the \fBerrObject\fP class, when returned
+as intermediate responses of a search request, are changed into 
+the response dictated by their content.
+.LP
+A third mode causes objects to be looked up from the underlying database 
+to discover if their class is \fBerrObject\fP; in that case, their content 
+is used to compute the corresponding response.
+.LP
+The behavior is disabled by using the \fBmanageDSAit\fP control (RFC 3296);
+in that case, the resulting object, either present in the directory 
+or dynamically generated by the overlay, or contained in the request,
+is handled as usual.
+.LP 
+The config directives that are specific to the
+.B retcode
+overlay must be prefixed by
+.BR retcode\- ,
+to avoid conflicts with directives specific to the underlying database
+or to other stacked overlays.  The following specific directives 
+can be used to configure the retcode overlay: 
+.TP
+.B retcode\-parent <DN>
+This directive defines the parent DN where dynamically generated
+entries reside.
+If not defined, the suffix of the database is used.
+.HP
+.hy 0
+.B retcode\-item <RDN> <errCode> [op=<oplist>] [text=<message>]
+.B [ref=<referral>] [sleeptime=<sec>]
+.RS
+A dynamically generated entry, located below \fBretcode\-parent\fP.
+The \fB<errCode>\fP is the number of the response code;
+it can be in any format supported by strtol.
+The optional \fB<oplist>\fP is a list of operations that cause
+response code generation; if absent, all operations are affected.
+The \fBref\fP field is only allowed for the \fBreferral\fP 
+response code.
+.RE
+.TP
+.B retcode\-indir
+Enables exploitation of in-directory stored errObjects.  May result
+in lots of unnecessary overhead.
+
+.SH SCHEMA
+The following schema items are created and used by the overlay:
+.LP
+The error code:
+.RS 4
+(  1.3.6.1.4.1.4203.666.11.4.1.1
+    NAME ( 'errCode' )
+    DESC 'LDAP error code'
+    EQUALITY integerMatch
+    ORDERING integerOrderingMatch
+    SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+    SINGLE-VALUE )
+.RE
+.LP
+The operations that trigger the response code:
+.RS 4
+( 1.3.6.1.4.1.4203.666.11.4.1.2
+    NAME ( 'errOp' )
+    DESC 'Operations the errObject applies to'
+    EQUALITY caseIgnoreMatch
+    SUBSTR caseIgnoreSubstringsMatch
+    SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
+.RE
+.LP
+The text message:
+.RS 4
+( 1.3.6.1.4.1.4203.666.11.4.1.3
+    NAME ( 'errText' )
+    DESC 'LDAP error textual description'
+    EQUALITY caseIgnoreMatch
+    SUBSTR caseIgnoreSubstringsMatch
+    SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+    SINGLE-VALUE )
+.RE
+.LP
+The sleep time before the response is actually returned to the client:
+.RS 4
+( 1.3.6.1.4.1.4203.666.11.4.1.4
+    NAME ( 'errSleepTime' )
+    DESC 'Time to wait before returning the error'
+    EQUALITY integerMatch
+    SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+    SINGLE-VALUE )
+.RE
+.LP
+The objectclass:
+.RS 4
+( 1.3.6.1.4.1.4203.666.11.4.3.1
+    NAME ( 'errObject' )
+    SUP top STRUCTURAL
+    MUST ( errCode )
+    MAY ( cn $ description $ errOp $ errText $ errSleepTime ) )
+.RE
+
+.SH EXAMPLE
+.LP
+.RS
+.nf
+overlay         retcode
+retcode-parent  "ou=RetCodes,dc=example,dc=com"
+include         ./retcode.conf
+
+# Wait 10 seconds, then return success (0x00)
+retcode-item    "cn=Success after 10 seconds" 0x00 sleeptime=10
+# Wait 10 seconds, then return timelimitExceeded (0x03)
+retcode-item    "cn=Timelimit after 10 seconds" 0x03 sleeptime=10
+.fi
+.RE
+.LP
+.LP
+
+.SH FILES
+.TP
+ETCDIR/slapd.conf
+default slapd configuration file
+.SH SEE ALSO
+.BR slapd.conf (5),
+.BR slapd (8),
+.SH AUTHOR
+Pierangelo Masarati
diff --git a/servers/slapd/overlays/retcode.c b/servers/slapd/overlays/retcode.c
new file mode 100644 (file)
index 0000000..105be67
--- /dev/null
@@ -0,0 +1,1138 @@
+/* retcode.c - customizable response for client testing purposes */
+/* $Header$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005 The OpenLDAP Foundation.
+ * Portions Copyright 2005 Pierangelo Masarati <ando@sys-net.it>
+ * 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 initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_RETCODE
+
+#include <stdio.h>
+
+#include <ac/unistd.h>
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "lutil.h"
+
+static slap_overinst           retcode;
+
+static AttributeDescription    *ad_errCode;
+static AttributeDescription    *ad_errText;
+static AttributeDescription    *ad_errOp;
+static AttributeDescription    *ad_errSleepTime;
+static ObjectClass             *oc_errObject;
+
+typedef enum retcode_op_e {
+       SN_DG_OP_NONE           = 0x0000,
+       SN_DG_OP_ADD            = 0x0001,
+       SN_DG_OP_BIND           = 0x0002,
+       SN_DG_OP_COMPARE        = 0x0004,
+       SN_DG_OP_DELETE         = 0x0008,
+       SN_DG_OP_MODIFY         = 0x0010,
+       SN_DG_OP_RENAME         = 0x0020,
+       SN_DG_OP_SEARCH         = 0x0040,
+       SN_DG_EXTENDED          = 0x0080,
+       SN_DG_OP_AUTH           = SN_DG_OP_BIND,
+       SN_DG_OP_READ           = (SN_DG_OP_COMPARE|SN_DG_OP_SEARCH),
+       SN_DG_OP_WRITE          = (SN_DG_OP_ADD|SN_DG_OP_DELETE|SN_DG_OP_MODIFY|SN_DG_OP_RENAME),
+       SN_DG_OP_ALL            = (SN_DG_OP_AUTH|SN_DG_OP_READ|SN_DG_OP_WRITE|SN_DG_EXTENDED)
+} retcode_op_e;
+
+typedef struct retcode_item_t {
+       struct berval           rdi_dn;
+       struct berval           rdi_ndn;
+       struct berval           rdi_text;
+       int                     rdi_err;
+       BerVarray               rdi_ref;
+       int                     rdi_sleeptime;
+       Entry                   rdi_e;
+       slap_mask_t             rdi_mask;
+       struct retcode_item_t   *rdi_next;
+} retcode_item_t;
+
+typedef struct retcode_t {
+       struct berval           rd_pdn;
+       struct berval           rd_npdn;
+
+       retcode_item_t          *rd_item;
+
+       unsigned                rd_flags;
+#define        RETCODE_FNONE           0x00
+#define        RETCODE_FINDIR          0x01
+#define        RETCODE_INDIR( rd )     ( (rd)->rd_flags & RETCODE_FINDIR )
+} retcode_t;
+
+static int
+retcode_entry_response( Operation *op, SlapReply *rs, Entry *e );
+
+static int
+retcode_cleanup_cb( Operation *op, SlapReply *rs )
+{
+       rs->sr_matched = NULL;
+       rs->sr_text = NULL;
+
+       if ( rs->sr_ref != NULL ) {
+               ber_bvarray_free( rs->sr_ref );
+               rs->sr_ref = NULL;
+       }
+
+       ch_free( op->o_callback );
+       op->o_callback = NULL;
+
+       return SLAP_CB_CONTINUE;
+}
+
+static int
+retcode_send_onelevel( Operation *op, SlapReply *rs )
+{
+       slap_overinst   *on = (slap_overinst *)op->o_bd->bd_info;
+       retcode_t       *rd = (retcode_t *)on->on_bi.bi_private;
+
+       retcode_item_t  *rdi;
+       
+       for ( rdi = rd->rd_item; rdi != NULL; rdi = rdi->rdi_next ) {
+               int     rc;
+
+               if ( op->o_abandon ) {
+                       return rs->sr_err = SLAPD_ABANDON;
+               }
+
+               rs->sr_err = test_filter( op, &rdi->rdi_e, op->ors_filter );
+               if ( rs->sr_err == LDAP_COMPARE_TRUE ) {
+                       if ( op->ors_slimit == rs->sr_nentries ) {
+                               rs->sr_err = LDAP_SIZELIMIT_EXCEEDED;
+                               goto done;
+                       }
+
+                       /* safe default */
+                       rs->sr_attrs = op->ors_attrs;
+                       rs->sr_operational_attrs = NULL;
+                       rs->sr_ctrls = NULL;
+                       rs->sr_flags = 0;
+                       rs->sr_err = LDAP_SUCCESS;
+                       rs->sr_entry = &rdi->rdi_e;
+
+                       rc = send_search_entry( op, rs );
+
+                       switch ( rc ) {
+                       case 0:         /* entry sent ok */
+                               break;
+                       case 1:         /* entry not sent */
+                               break;
+                       case -1:        /* connection closed */
+                               rs->sr_entry = NULL;
+                               rs->sr_err = LDAP_OTHER;
+                               goto done;
+                       }
+               }
+               rs->sr_err = LDAP_SUCCESS;
+       }
+
+done:;
+
+       send_ldap_result( op, rs );
+
+       return rs->sr_err;
+}
+
+static int
+retcode_op_add( Operation *op, SlapReply *rs )
+{
+       return retcode_entry_response( op, rs, op->ora_e );
+}
+
+typedef struct retcode_cb_t {
+       unsigned        rdc_flags;
+       ber_tag_t       rdc_tag;
+} retcode_cb_t;
+
+static int
+retcode_cb_response( Operation *op, SlapReply *rs )
+{
+       retcode_cb_t    *rdc = (retcode_cb_t *)op->o_callback->sc_private;
+
+       if ( rs->sr_type == REP_SEARCH ) {
+               ber_tag_t       o_tag = op->o_tag;
+               int             rc;
+
+               op->o_tag = rdc->rdc_tag;
+               rc = retcode_entry_response( op, rs, rs->sr_entry );
+               op->o_tag = o_tag;
+
+               return rc;
+       }
+
+       if ( rs->sr_err == LDAP_SUCCESS ) {
+               rdc->rdc_flags = SLAP_CB_CONTINUE;
+       }
+
+       return 0;
+}
+
+static int
+retcode_op_internal( Operation *op, SlapReply *rs )
+{
+       slap_overinst   *on = (slap_overinst *)op->o_bd->bd_info;
+
+       Operation       op2 = *op;
+       SlapReply       rs2 = { 0 };
+       BackendDB       db = *op->o_bd;
+       slap_callback   sc = { 0 };
+       retcode_cb_t    rdc;
+
+       int             rc;
+
+       op2.o_tag = LDAP_REQ_SEARCH;
+       op2.ors_scope = LDAP_SCOPE_BASE;
+       op2.ors_deref = LDAP_DEREF_NEVER;
+       op2.ors_tlimit = SLAP_NO_LIMIT;
+       op2.ors_slimit = SLAP_NO_LIMIT;
+       op2.ors_limit = NULL;
+       op2.ors_attrsonly = 0;
+       op2.ors_attrs = slap_anlist_all_attributes;
+
+       ber_str2bv_x( "(objectClass=errObject)", STRLENOF( "(objectClass=errObject)" ),
+               1, &op2.ors_filterstr, op2.o_tmpmemctx );
+       op2.ors_filter = str2filter_x( &op2, op2.ors_filterstr.bv_val );
+
+       db.bd_info = on->on_info->oi_orig;
+       op2.o_bd = &db;
+
+       rdc.rdc_flags = RETCODE_FINDIR;
+       rdc.rdc_tag = op->o_tag;
+       sc.sc_response = retcode_cb_response;
+       sc.sc_private = &rdc;
+       op2.o_callback = &sc;
+
+       rc = op2.o_bd->be_search( &op2, &rs2 );
+
+       filter_free_x( &op2, op2.ors_filter );
+       ber_memfree_x( op2.ors_filterstr.bv_val, op2.o_tmpmemctx );
+
+       if ( rdc.rdc_flags == SLAP_CB_CONTINUE ) {
+               return SLAP_CB_CONTINUE;
+       }
+
+       return rc;
+}
+
+static int
+retcode_op_func( Operation *op, SlapReply *rs )
+{
+       slap_overinst   *on = (slap_overinst *)op->o_bd->bd_info;
+       retcode_t       *rd = (retcode_t *)on->on_bi.bi_private;
+
+       retcode_item_t  *rdi;
+       struct berval           nrdn, npdn;
+
+       slap_callback           *cb = NULL;
+
+       if ( !dnIsSuffix( &op->o_req_ndn, &rd->rd_npdn ) ) {
+               if ( RETCODE_INDIR( rd ) ) {
+                       switch ( op->o_tag ) {
+                       case LDAP_REQ_ADD:
+                               return retcode_op_add( op, rs );
+
+                       case LDAP_REQ_MODIFY:
+                       case LDAP_REQ_BIND:
+                       case LDAP_REQ_DELETE:
+                       case LDAP_REQ_MODRDN:
+                       case LDAP_REQ_COMPARE:
+                       case LDAP_REQ_SEARCH:
+                               return retcode_op_internal( op, rs );
+                       }
+               }
+
+               return SLAP_CB_CONTINUE;
+       }
+
+       if ( op->o_tag == LDAP_REQ_SEARCH
+                       && op->ors_scope != LDAP_SCOPE_BASE
+                       && op->o_req_ndn.bv_len == rd->rd_npdn.bv_len )
+       {
+               return retcode_send_onelevel( op, rs );
+       }
+
+       dnParent( &op->o_req_ndn, &npdn );
+       if ( npdn.bv_len != rd->rd_npdn.bv_len ) {
+               rs->sr_err = LDAP_NO_SUCH_OBJECT;
+               rs->sr_matched = rd->rd_pdn.bv_val;
+               send_ldap_result( op, rs );
+               rs->sr_matched = NULL;
+               return rs->sr_err;
+       }
+
+       dnRdn( &op->o_req_ndn, &nrdn );
+
+       for ( rdi = rd->rd_item; rdi != NULL; rdi = rdi->rdi_next ) {
+               struct berval   rdi_nrdn;
+
+               dnRdn( &rdi->rdi_ndn, &rdi_nrdn );
+               if ( dn_match( &nrdn, &rdi_nrdn ) ) {
+                       break;
+               }
+       }
+
+       if ( rdi != NULL && rdi->rdi_mask != SN_DG_OP_ALL ) {
+               retcode_op_e    o_tag = SN_DG_OP_NONE;
+
+               switch ( op->o_tag ) {
+               case LDAP_REQ_ADD:
+                       o_tag = SN_DG_OP_ADD;
+                       break;
+
+               case LDAP_REQ_BIND:
+                       o_tag = SN_DG_OP_BIND;
+                       break;
+
+               case LDAP_REQ_COMPARE:
+                       o_tag = SN_DG_OP_COMPARE;
+                       break;
+
+               case LDAP_REQ_DELETE:
+                       o_tag = SN_DG_OP_DELETE;
+                       break;
+
+               case LDAP_REQ_MODIFY:
+                       o_tag = SN_DG_OP_MODIFY;
+                       break;
+
+               case LDAP_REQ_MODRDN:
+                       o_tag = SN_DG_OP_RENAME;
+                       break;
+
+               case LDAP_REQ_SEARCH:
+                       o_tag = SN_DG_OP_SEARCH;
+                       break;
+
+               case LDAP_REQ_EXTENDED:
+                       o_tag = SN_DG_EXTENDED;
+                       break;
+
+               default:
+                       /* Should not happen */
+                       break;
+               }
+
+               if ( !( o_tag & rdi->rdi_mask ) ) {
+                       return SLAP_CB_CONTINUE;
+               }
+       }
+
+       if ( rdi == NULL ) {
+               rs->sr_matched = rd->rd_pdn.bv_val;
+               rs->sr_err = LDAP_NO_SUCH_OBJECT;
+               rs->sr_text = "retcode not found";
+
+       } else {
+               rs->sr_err = rdi->rdi_err;
+               rs->sr_text = rdi->rdi_text.bv_val;
+
+               /* FIXME: we only honor the rdi_ref field in case rdi_err
+                * is LDAP_REFERRAL otherwise send_ldap_result() bails out */
+               if ( rs->sr_err == LDAP_REFERRAL ) {
+                       BerVarray       ref;
+
+                       if ( rdi->rdi_ref != NULL ) {
+                               ref = rdi->rdi_ref;
+
+                       } else if ( default_referral != NULL ) {
+                               ref = default_referral;
+                       }
+
+                       if ( ref != NULL ) {
+                               rs->sr_ref = referral_rewrite( ref,
+                                       NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT );
+
+                       } else {
+                               rs->sr_err = LDAP_OTHER;
+                               rs->sr_text = "bad referral object";
+                       }
+               }
+
+               if ( rdi->rdi_sleeptime > 0 ) {
+                       sleep( rdi->rdi_sleeptime );
+               }
+       }
+
+       switch ( op->o_tag ) {
+       case LDAP_REQ_EXTENDED:
+               if ( rdi == NULL ) {
+                       break;
+               }
+               cb = ( slap_callback * )ch_malloc( sizeof( slap_callback ) );
+               memset( cb, 0, sizeof( slap_callback ) );
+               cb->sc_cleanup = retcode_cleanup_cb;
+               op->o_callback = cb;
+               break;
+
+       default:
+               send_ldap_result( op, rs );
+               if ( rs->sr_ref != NULL ) {
+                       ber_bvarray_free( rs->sr_ref );
+               }
+               rs->sr_matched = NULL;
+               rs->sr_text = NULL;
+               break;
+       }
+
+       return rs->sr_err;
+}
+
+static int
+retcode_op2str( ber_tag_t op, struct berval *bv )
+{
+       switch ( op ) {
+       case LDAP_REQ_BIND:
+               BER_BVSTR( bv, "bind" );
+               return 0;
+       case LDAP_REQ_ADD:
+               BER_BVSTR( bv, "add" );
+               return 0;
+       case LDAP_REQ_DELETE:
+               BER_BVSTR( bv, "delete" );
+               return 0;
+       case LDAP_REQ_MODRDN:
+               BER_BVSTR( bv, "modrdn" );
+               return 0;
+       case LDAP_REQ_MODIFY:
+               BER_BVSTR( bv, "modify" );
+               return 0;
+       case LDAP_REQ_COMPARE:
+               BER_BVSTR( bv, "compare" );
+               return 0;
+       case LDAP_REQ_SEARCH:
+               BER_BVSTR( bv, "search" );
+               return 0;
+       case LDAP_REQ_EXTENDED:
+               BER_BVSTR( bv, "extended" );
+               return 0;
+       }
+       return -1;
+}
+
+static int
+retcode_entry_response( Operation *op, SlapReply *rs, Entry *e )
+{
+       slap_overinst   *on = (slap_overinst *)op->o_bd->bd_info;
+
+       Attribute       *a;
+       int             err;
+       char            *next;
+
+       if ( get_manageDSAit( op ) ) {
+               return SLAP_CB_CONTINUE;
+       }
+
+       if ( !is_entry_objectclass( e, oc_errObject, 0 ) ) {
+               return SLAP_CB_CONTINUE;
+       }
+
+       /* operation */
+       a = attr_find( e->e_attrs, ad_errOp );
+       if ( a != NULL ) {
+               int             i,
+                               gotit = 0;
+               struct berval   bv = BER_BVNULL;
+
+               (void)retcode_op2str( op->o_tag, &bv );
+
+               if ( BER_BVISNULL( &bv ) ) {
+                       return SLAP_CB_CONTINUE;
+               }
+
+               for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
+                       if ( bvmatch( &a->a_nvals[ i ], &bv ) ) {
+                               gotit = 1;
+                               break;
+                       }
+               }
+
+               if ( !gotit ) {
+                       return SLAP_CB_CONTINUE;
+               }
+       }
+
+       /* error code */
+       a = attr_find( e->e_attrs, ad_errCode );
+       if ( a == NULL ) {
+               return SLAP_CB_CONTINUE;
+       }
+       err = strtol( a->a_nvals[ 0 ].bv_val, &next, 0 );
+       if ( next == a->a_nvals[ 0 ].bv_val || next[ 0 ] != '\0' ) {
+               return SLAP_CB_CONTINUE;
+       }
+       rs->sr_err = err;
+
+       /* sleep time */
+       a = attr_find( e->e_attrs, ad_errSleepTime );
+       if ( a != NULL ) {
+               int     sleepTime;
+
+               sleepTime = strtoul( a->a_nvals[ 0 ].bv_val, &next, 0 );
+               if ( next != a->a_nvals[ 0 ].bv_val && next[ 0 ] == '\0' ) {
+                       sleep( sleepTime );
+               }
+       }
+
+       if ( rs->sr_err != LDAP_SUCCESS ) {
+               BackendDB       db = *op->o_bd;
+               void            *o_callback = op->o_callback;
+
+               /* message text */
+               a = attr_find( e->e_attrs, ad_errText );
+               if ( a != NULL ) {
+                       rs->sr_text = a->a_vals[ 0 ].bv_val;
+               }
+
+               db.bd_info = on->on_info->oi_orig;
+               op->o_bd = &db;
+               op->o_callback = NULL;
+
+               /* referral */
+               if ( rs->sr_err == LDAP_REFERRAL ) {
+                       BerVarray       refs = default_referral;
+
+                       a = attr_find( e->e_attrs, slap_schema.si_ad_ref );
+                       if ( a != NULL ) {
+                               refs = a->a_vals;
+                       }
+                       rs->sr_ref = referral_rewrite( refs,
+                               NULL, &op->o_req_dn, op->oq_search.rs_scope );
+       
+                       send_search_reference( op, rs );
+                       ber_bvarray_free( rs->sr_ref );
+                       rs->sr_ref = NULL;
+
+               } else {
+                       send_ldap_result( op, rs );
+               }
+
+               rs->sr_text = NULL;
+               op->o_callback = o_callback;
+       }
+       
+       if ( rs->sr_err != LDAP_SUCCESS ) {
+               op->o_abandon = 1;
+               return rs->sr_err;
+       }
+
+       return SLAP_CB_CONTINUE;
+}
+
+static int
+retcode_response( Operation *op, SlapReply *rs )
+{
+       slap_overinst   *on = (slap_overinst *)op->o_bd->bd_info;
+       retcode_t       *rd = (retcode_t *)on->on_bi.bi_private;
+
+       if ( rs->sr_type != REP_SEARCH || !RETCODE_INDIR( rd ) ) {
+               return SLAP_CB_CONTINUE;
+       }
+
+       return retcode_entry_response( op, rs, rs->sr_entry );
+}
+
+static int
+retcode_db_init( BackendDB *be )
+{
+       slap_overinst   *on = (slap_overinst *)be->bd_info;
+       retcode_t       *rd;
+
+       rd = (retcode_t *)ch_malloc( sizeof( retcode_t ) );
+       memset( rd, 0, sizeof( retcode_t ) );
+
+       on->on_bi.bi_private = (void *)rd;
+
+       return 0;
+}
+
+static int
+retcode_db_config(
+       BackendDB       *be,
+       const char      *fname,
+       int             lineno,
+       int             argc,
+       char            **argv )
+{
+       slap_overinst   *on = (slap_overinst *)be->bd_info;
+       retcode_t       *rd = (retcode_t *)on->on_bi.bi_private;
+
+       char                    *argv0 = argv[ 0 ] + STRLENOF( "retcode-" );
+
+       if ( strncasecmp( argv[ 0 ], "retcode-", STRLENOF( "retcode-" ) ) != 0 ) {
+               return SLAP_CONF_UNKNOWN;
+       }
+
+       if ( strcasecmp( argv0, "parent" ) == 0 ) {
+               struct berval   dn;
+               int             rc;
+
+               if ( argc != 2 ) {
+                       fprintf( stderr, "%s: line %d: retcode: "
+                               "\"retcode-parent <DN>\": missing <DN>\n",
+                               fname, lineno );
+                       return 1;
+               }
+
+               if ( !BER_BVISNULL( &rd->rd_pdn ) ) {
+                       fprintf( stderr, "%s: line %d: retcode: "
+                               "parent already defined.\n", fname, lineno );
+                       return 1;
+               }
+
+               ber_str2bv( argv[ 1 ], 0, 0, &dn );
+
+               rc = dnPrettyNormal( NULL, &dn, &rd->rd_pdn, &rd->rd_npdn, NULL );
+               if ( rc != LDAP_SUCCESS ) {
+                       fprintf( stderr, "%s: line %d: retcode: "
+                               "unable to normalize parent DN \"%s\": %d\n",
+                               fname, lineno, argv[ 1 ], rc );
+                       return 1;
+               }
+
+       } else if ( strcasecmp( argv0, "item" ) == 0 ) {
+               retcode_item_t  rdi = { BER_BVNULL }, **rdip;
+               struct berval           bv, rdn, nrdn;
+               int                     rc;
+               char                    *next = NULL;
+
+               if ( argc < 3 ) {
+                       fprintf( stderr, "%s: line %d: retcode: "
+                               "\"retcode-item <RDN> <retcode> [<text>]\": "
+                               "missing args\n",
+                               fname, lineno, argv[ 1 ] );
+                       return 1;
+               }
+
+               ber_str2bv( argv[ 1 ], 0, 0, &bv );
+               
+               rc = dnPrettyNormal( NULL, &bv, &rdn, &nrdn, NULL );
+               if ( rc != LDAP_SUCCESS ) {
+                       fprintf( stderr, "%s: line %d: retcode: "
+                               "unable to normalize RDN \"%s\": %d\n",
+                               fname, lineno, argv[ 1 ], rc );
+                       return 1;
+               }
+
+               if ( !dnIsOneLevelRDN( &nrdn ) ) {
+                       fprintf( stderr, "%s: line %d: retcode: "
+                               "value \"%s\" is not a RDN\n",
+                               fname, lineno, argv[ 1 ] );
+                       return 1;
+               }
+
+               if ( BER_BVISNULL( &rd->rd_npdn ) ) {
+                       /* FIXME: we use the database suffix */
+                       if ( be->be_nsuffix == NULL ) {
+                               fprintf( stderr, "%s: line %d: retcode: "
+                                       "either \"retcode-parent\" "
+                                       "or \"suffix\" must be defined.\n",
+                                       fname, lineno );
+                               return 1;
+                       }
+
+                       ber_dupbv( &rd->rd_pdn, &be->be_suffix[ 0 ] );
+                       ber_dupbv( &rd->rd_npdn, &be->be_nsuffix[ 0 ] );
+               }
+
+               build_new_dn( &rdi.rdi_dn, &rd->rd_pdn, &rdn, NULL );
+               build_new_dn( &rdi.rdi_ndn, &rd->rd_npdn, &nrdn, NULL );
+
+               ch_free( rdn.bv_val );
+               ch_free( nrdn.bv_val );
+
+               rdi.rdi_err = strtol( argv[ 2 ], &next, 0 );
+               if ( next == argv[ 2 ] || next[ 0 ] != '\0' ) {
+                       fprintf( stderr, "%s: line %d: retcode: "
+                               "unable to parse return code \"%s\"\n",
+                               fname, lineno, argv[ 2 ] );
+                       return 1;
+               }
+
+               rdi.rdi_mask = SN_DG_OP_ALL;
+
+               if ( argc > 3 ) {
+                       int     i;
+
+                       for ( i = 3; i < argc; i++ ) {
+                               if ( strncasecmp( argv[ i ], "op=", STRLENOF( "op=" ) ) == 0 )
+                               {
+                                       char            **ops;
+                                       int             j;
+
+                                       ops = ldap_str2charray( &argv[ i ][ STRLENOF( "op=" ) ], "," );
+                                       assert( ops != NULL );
+
+                                       rdi.rdi_mask = SN_DG_OP_NONE;
+
+                                       for ( j = 0; ops[ j ] != NULL; j++ ) {
+                                               if ( strcasecmp( ops[ j ], "add" ) == 0 ) {
+                                                       rdi.rdi_mask |= SN_DG_OP_ADD;
+
+                                               } else if ( strcasecmp( ops[ j ], "bind" ) == 0 ) {
+                                                       rdi.rdi_mask |= SN_DG_OP_BIND;
+
+                                               } else if ( strcasecmp( ops[ j ], "compare" ) == 0 ) {
+                                                       rdi.rdi_mask |= SN_DG_OP_COMPARE;
+
+                                               } else if ( strcasecmp( ops[ j ], "add" ) == 0 ) {
+                                                       rdi.rdi_mask |= SN_DG_OP_DELETE;
+
+                                               } else if ( strcasecmp( ops[ j ], "modify" ) == 0 ) {
+                                                       rdi.rdi_mask |= SN_DG_OP_MODIFY;
+
+                                               } else if ( strcasecmp( ops[ j ], "rename" ) == 0 ) {
+                                                       rdi.rdi_mask |= SN_DG_OP_RENAME;
+
+                                               } else if ( strcasecmp( ops[ j ], "search" ) == 0 ) {
+                                                       rdi.rdi_mask |= SN_DG_OP_SEARCH;
+
+                                               } else if ( strcasecmp( ops[ j ], "extended" ) == 0 ) {
+                                                       rdi.rdi_mask |= SN_DG_EXTENDED;
+
+                                               } else if ( strcasecmp( ops[ j ], "auth" ) == 0 ) {
+                                                       rdi.rdi_mask |= SN_DG_OP_AUTH;
+
+                                               } else if ( strcasecmp( ops[ j ], "read" ) == 0 ) {
+                                                       rdi.rdi_mask |= SN_DG_OP_READ;
+
+                                               } else if ( strcasecmp( ops[ j ], "write" ) == 0 ) {
+                                                       rdi.rdi_mask |= SN_DG_OP_WRITE;
+
+                                               } else if ( strcasecmp( ops[ j ], "all" ) == 0 ) {
+                                                       rdi.rdi_mask |= SN_DG_OP_ALL;
+
+                                               } else {
+                                                       fprintf( stderr, "retcode: unknown op \"%s\"\n",
+                                                               ops[ j ] );
+                                                       return 1;
+                                               }
+                                       }
+
+                                       ldap_charray_free( ops );
+
+                               } else if ( strncasecmp( argv[ i ], "text=", STRLENOF( "text=" ) ) == 0 )
+                               {
+                                       if ( !BER_BVISNULL( &rdi.rdi_text ) ) {
+                                               fprintf( stderr, "%s: line %d: retcode: "
+                                                       "\"text\" already provided.\n",
+                                                       fname, lineno );
+                                               return 1;
+                                       }
+                                       ber_str2bv( &argv[ i ][ STRLENOF( "text=" ) ], 0, 1, &rdi.rdi_text );
+
+                               } else if ( strncasecmp( argv[ i ], "ref=", STRLENOF( "ref=" ) ) == 0 )
+                               {
+                                       char            **refs;
+                                       int             j;
+
+                                       if ( rdi.rdi_ref != NULL ) {
+                                               fprintf( stderr, "%s: line %d: retcode: "
+                                                       "\"ref\" already provided.\n",
+                                                       fname, lineno );
+                                               return 1;
+                                       }
+
+                                       if ( rdi.rdi_err != LDAP_REFERRAL ) {
+                                               fprintf( stderr, "%s: line %d: retcode: "
+                                                       "providing \"ref\"\n"
+                                                       "\talong with a non-referral "
+                                                       "resultCode may cause slapd failures\n"
+                                                       "\trelated to internal checks.\n",
+                                                       fname, lineno );
+                                       }
+
+                                       refs = ldap_str2charray( &argv[ i ][ STRLENOF( "ref=" ) ], " " );
+                                       assert( refs != NULL );
+
+                                       for ( j = 0; refs[ j ] != NULL; j++ ) {
+                                               struct berval   bv;
+
+                                               ber_str2bv( refs[ j ], 0, 1, &bv );
+                                               ber_bvarray_add( &rdi.rdi_ref, &bv );
+                                       }
+
+                                       ldap_charray_free( refs );
+
+                               } else if ( strncasecmp( argv[ i ], "sleeptime=", STRLENOF( "sleeptime=" ) ) == 0 )
+                               {
+                                       char            *next;
+                                       if ( rdi.rdi_sleeptime != 0 ) {
+                                               fprintf( stderr, "%s: line %d: retcode: "
+                                                       "\"sleeptime\" already provided.\n",
+                                                       fname, lineno );
+                                               return 1;
+                                       }
+
+                                       rdi.rdi_sleeptime = strtol( &argv[ i ][ STRLENOF( "sleeptime=" ) ], &next, 10 );
+                                       if ( next == argv[ i ] || next[ 0 ] != '\0' ) {
+                                               fprintf( stderr, "%s: line %d: retcode: "
+                                                       "unable to parse \"sleeptime=%s\".\n",
+                                                       fname, lineno, &argv[ i ][ STRLENOF( "sleeptime=" ) ] );
+                                               return 1;
+                                       }
+
+                               } else {
+                                       fprintf( stderr, "%s: line %d: retcode: "
+                                               "unknown option \"%s\".\n",
+                                                       fname, lineno, argv[ i ] );
+                                       return 1;
+                               }
+                       }
+               }
+
+               for ( rdip = &rd->rd_item; *rdip; rdip = &(*rdip)->rdi_next )
+                       /* go to last */ ;
+
+               
+               *rdip = ( retcode_item_t * )ch_malloc( sizeof( retcode_item_t ) );
+               *(*rdip) = rdi;
+
+       } else if ( strcasecmp( argv0, "indir" ) == 0 ) {
+               rd->rd_flags |= RETCODE_FINDIR;
+
+       } else {
+               return SLAP_CONF_UNKNOWN;
+       }
+
+       return 0;
+}
+
+static int
+retcode_db_open( BackendDB *be )
+{
+       slap_overinst   *on = (slap_overinst *)be->bd_info;
+       retcode_t       *rd = (retcode_t *)on->on_bi.bi_private;
+
+       retcode_item_t  *rdi;
+
+       for ( rdi = rd->rd_item; rdi; rdi = rdi->rdi_next ) {
+               LDAPRDN                 rdn = NULL;
+               int                     rc, j;
+               char*                   p;
+               struct berval           val[ 3 ];
+               char                    buf[ SLAP_TEXT_BUFLEN ];
+
+               /* DN */
+               rdi->rdi_e.e_name = rdi->rdi_dn;
+               rdi->rdi_e.e_nname = rdi->rdi_ndn;
+
+               /* objectClass */
+               val[ 0 ] = oc_errObject->soc_cname;
+               val[ 1 ] = slap_schema.si_oc_extensibleObject->soc_cname;
+               BER_BVZERO( &val[ 2 ] );
+
+               attr_merge( &rdi->rdi_e, slap_schema.si_ad_objectClass, val, NULL );
+
+               /* RDN avas */
+               rc = ldap_bv2rdn( &rdi->rdi_dn, &rdn, (char **) &p,
+                               LDAP_DN_FORMAT_LDAP );
+
+               assert( rc == LDAP_SUCCESS );
+
+               for ( j = 0; rdn[ j ]; j++ ) {
+                       LDAPAVA                 *ava = rdn[ j ];
+                       AttributeDescription    *ad = NULL;
+                       const char              *text;
+
+                       rc = slap_bv2ad( &ava->la_attr, &ad, &text );
+                       assert( rc == LDAP_SUCCESS );
+                       
+                       attr_merge_normalize_one( &rdi->rdi_e, ad,
+                                       &ava->la_value, NULL );
+               }
+
+               ldap_rdnfree( rdn );
+
+               /* error code */
+               snprintf( buf, sizeof( buf ), "%d", rdi->rdi_err );
+               ber_str2bv( buf, 0, 0, &val[ 0 ] );
+
+               attr_merge_one( &rdi->rdi_e, ad_errCode, &val[ 0 ], NULL );
+
+               if ( rdi->rdi_ref != NULL ) {
+                       attr_merge_normalize( &rdi->rdi_e, slap_schema.si_ad_ref,
+                               rdi->rdi_ref, NULL );
+               }
+
+               /* text */
+               if ( !BER_BVISNULL( &rdi->rdi_text ) ) {
+                       val[ 0 ] = rdi->rdi_text;
+
+                       attr_merge_normalize_one( &rdi->rdi_e, ad_errText, &val[ 0 ], NULL );
+               }
+
+               /* sleep time */
+               if ( rdi->rdi_sleeptime > 0 ) {
+                       snprintf( buf, sizeof( buf ), "%d", rdi->rdi_sleeptime );
+                       ber_str2bv( buf, 0, 0, &val[ 0 ] );
+
+                       attr_merge_one( &rdi->rdi_e, ad_errSleepTime, &val[ 0 ], NULL );
+               }
+
+               /* operations */
+               if ( rdi->rdi_mask & SN_DG_OP_ADD ) {
+                       BER_BVSTR( &val[ 0 ], "add" );
+                       attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+               }
+
+               if ( rdi->rdi_mask & SN_DG_OP_BIND ) {
+                       BER_BVSTR( &val[ 0 ], "bind" );
+                       attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+               }
+
+               if ( rdi->rdi_mask & SN_DG_OP_COMPARE ) {
+                       BER_BVSTR( &val[ 0 ], "compare" );
+                       attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+               }
+
+               if ( rdi->rdi_mask & SN_DG_OP_DELETE ) {
+                       BER_BVSTR( &val[ 0 ], "delete" );
+                       attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+               }
+
+               if ( rdi->rdi_mask & SN_DG_EXTENDED ) {
+                       BER_BVSTR( &val[ 0 ], "extended" );
+                       attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+               }
+
+               if ( rdi->rdi_mask & SN_DG_OP_MODIFY ) {
+                       BER_BVSTR( &val[ 0 ], "modify" );
+                       attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+               }
+
+               if ( rdi->rdi_mask & SN_DG_OP_RENAME ) {
+                       BER_BVSTR( &val[ 0 ], "rename" );
+                       attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+               }
+
+               if ( rdi->rdi_mask & SN_DG_OP_SEARCH ) {
+                       BER_BVSTR( &val[ 0 ], "search" );
+                       attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+               }
+       }
+
+       return 0;
+}
+
+static int
+retcode_db_destroy( BackendDB *be )
+{
+       slap_overinst   *on = (slap_overinst *)be->bd_info;
+       retcode_t       *rd = (retcode_t *)on->on_bi.bi_private;
+
+       if ( rd ) {
+               retcode_item_t  *rdi, *next;
+
+               for ( rdi = rd->rd_item; rdi != NULL; rdi = next ) {
+                       ber_memfree( rdi->rdi_dn.bv_val );
+                       ber_memfree( rdi->rdi_ndn.bv_val );
+
+                       if ( !BER_BVISNULL( &rdi->rdi_text ) ) {
+                               ber_memfree( rdi->rdi_text.bv_val );
+                       }
+
+                       BER_BVZERO( &rdi->rdi_e.e_name );
+                       BER_BVZERO( &rdi->rdi_e.e_nname );
+
+                       entry_clean( &rdi->rdi_e );
+
+                       next = rdi->rdi_next;
+
+                       ch_free( rdi );
+               }
+
+               ber_memfree( rd );
+       }
+
+       return 0;
+}
+
+#if SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC
+static
+#endif /* SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC */
+int
+retcode_init( void )
+{
+       int             i, code;
+       const char      *err;
+
+       static struct {
+               char                    *name;
+               char                    *desc;
+               AttributeDescription    **ad;
+       } retcode_at[] = {
+               { "errCode", "( 1.3.6.1.4.1.4203.666.11.4.1.1 "
+                       "NAME ( 'errCode' ) "
+                       "DESC 'LDAP error code' "
+                       "EQUALITY integerMatch "
+                       "ORDERING integerOrderingMatch "
+                       "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+                       "SINGLE-VALUE )",
+                       &ad_errCode },
+               { "errOp", "( 1.3.6.1.4.1.4203.666.11.4.1.2 "
+                       "NAME ( 'errOp' ) "
+                       "DESC 'Operations the errObject applies to' "
+                       "EQUALITY caseIgnoreMatch "
+                       "SUBSTR caseIgnoreSubstringsMatch "
+                       "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
+                       &ad_errOp},
+               { "errText", "( 1.3.6.1.4.1.4203.666.11.4.1.3 "
+                       "NAME ( 'errText' ) "
+                       "DESC 'LDAP error textual description' "
+                       "EQUALITY caseIgnoreMatch "
+                       "SUBSTR caseIgnoreSubstringsMatch "
+                       "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
+                       "SINGLE-VALUE )",
+                       &ad_errText },
+               { "errSleepTime", "( 1.3.6.1.4.1.4203.666.11.4.1.4 "
+                       "NAME ( 'errSleepTime' ) "
+                       "DESC 'Time to wait before returning the error' "
+                       "EQUALITY integerMatch "
+                       "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+                       "SINGLE-VALUE )",
+                       &ad_errSleepTime },
+               { NULL }
+       };
+
+       static struct {
+               char            *name;
+               char            *desc;
+               ObjectClass     **oc;
+       } retcode_oc[] = {
+               { "errObject", "( 1.3.6.1.4.1.4203.666.11.4.3.1 "
+                       "NAME ( 'errObject' ) "
+                       "SUP top STRUCTURAL "
+                       "MUST ( errCode ) "
+                       "MAY ( "
+                               "cn "
+                               "$ description "
+                               "$ errOp "
+                               "$ errText "
+                               "$ errSleepTime "
+                       ") )",
+                       &oc_errObject },
+               { NULL }
+       };
+
+
+       for ( i = 0; retcode_at[ i ].name != NULL; i++ ) {
+               LDAPAttributeType       *at;
+
+               at = ldap_str2attributetype( retcode_at[ i ].desc,
+                       &code, &err, LDAP_SCHEMA_ALLOW_ALL );
+               if ( !at ) {
+                       fprintf( stderr, "retcode: "
+                               "AttributeType load failed: %s %s\n",
+                               ldap_scherr2str( code ), err );
+                       return code;
+               }
+
+#if LDAP_VENDOR_VERSION_MINOR == X || LDAP_VENDOR_VERSION_MINOR > 2
+               code = at_add( at, 0, NULL, &err );
+#else
+               code = at_add( at, &err );
+#endif
+               ldap_memfree( at );
+               if ( code != LDAP_SUCCESS ) {
+                       fprintf( stderr, "retcode: "
+                               "AttributeType load failed: %s %s\n",
+                               scherr2str( code ), err );
+                       return code;
+               }
+
+               code = slap_str2ad( retcode_at[ i ].name,
+                               retcode_at[ i ].ad, &err );
+               if ( code != LDAP_SUCCESS ) {
+                       fprintf( stderr, "retcode: unable to find "
+                               "AttributeDescription \"%s\": %d (%s)\n",
+                               retcode_at[ i ].name, code, err );
+                       return 1;
+               }
+       }
+
+       for ( i = 0; retcode_oc[ i ].name != NULL; i++ ) {
+               LDAPObjectClass *oc;
+
+               oc = ldap_str2objectclass( retcode_oc[ i ].desc,
+                               &code, &err, LDAP_SCHEMA_ALLOW_ALL );
+               if ( !oc ) {
+                       fprintf( stderr, "retcode: "
+                               "ObjectClass load failed: %s %s\n",
+                               ldap_scherr2str( code ), err );
+                       return code;
+               }
+
+#if LDAP_VENDOR_VERSION_MINOR == X || LDAP_VENDOR_VERSION_MINOR > 2
+               code = oc_add( oc, 0, NULL, &err );
+#else
+               code = oc_add( oc, &err );
+#endif
+               ldap_memfree(oc);
+               if ( code != LDAP_SUCCESS ) {
+                       fprintf( stderr, "retcode: "
+                               "ObjectClass load failed: %s %s\n",
+                               scherr2str( code ), err );
+                       return code;
+               }
+
+               *retcode_oc[ i ].oc = oc_find( retcode_oc[ i ].name );
+               if ( *retcode_oc[ i ].oc == NULL ) {
+                       fprintf( stderr, "retcode: unable to find "
+                               "objectClass \"%s\"\n",
+                               retcode_oc[ i ].name );
+                       return 1;
+               }
+       }
+
+       retcode.on_bi.bi_type = "retcode";
+
+       retcode.on_bi.bi_db_init = retcode_db_init;
+       retcode.on_bi.bi_db_config = retcode_db_config;
+       retcode.on_bi.bi_db_open = retcode_db_open;
+       retcode.on_bi.bi_db_destroy = retcode_db_destroy;
+
+       retcode.on_bi.bi_op_add = retcode_op_func;
+       retcode.on_bi.bi_op_bind = retcode_op_func;
+       retcode.on_bi.bi_op_compare = retcode_op_func;
+       retcode.on_bi.bi_op_delete = retcode_op_func;
+       retcode.on_bi.bi_op_modify = retcode_op_func;
+       retcode.on_bi.bi_op_modrdn = retcode_op_func;
+       retcode.on_bi.bi_op_search = retcode_op_func;
+
+       retcode.on_bi.bi_extended = retcode_op_func;
+
+       retcode.on_response = retcode_response;
+
+       return overlay_register( &retcode );
+}
+
+#if SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+       return retcode_init();
+}
+#endif /* SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC */
+
+#endif /* SLAPD_OVER_RETCODE */
diff --git a/tests/data/retcode.conf b/tests/data/retcode.conf
new file mode 100644 (file)
index 0000000..7a433db
--- /dev/null
@@ -0,0 +1,115 @@
+# slapo-retcode standard track response codes configuration example
+# $Header$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2005 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+#
+# From "ldap.h", revised as per <draft-ietf-ldapbis-protocol>
+
+retcode-item   "cn=success"                            0x00
+
+retcode-item   "cn=success w/ delay"                   0x00    sleeptime=2
+
+retcode-item   "cn=operationsError"                    0x01
+retcode-item   "cn=protocolError"                      0x02
+retcode-item   "cn=timeLimitExceeded"                  0x03    op=search
+retcode-item   "cn=sizeLimitExceeded"                  0x04    op=search
+retcode-item   "cn=compareFalse"                       0x05    op=compare
+retcode-item   "cn=compareTrue"                        0x06    op=compare
+retcode-item   "cn=authMethodNotSupported"             0x07
+retcode-item   "cn=strongAuthNotSupported"             0x07    text="same as authMethodNotSupported"
+retcode-item   "cn=strongAuthRequired"                 0x08
+retcode-item   "cn=strongerAuthRequired"               0x08    text="same as strongAuthRequired"
+#retcode-item  "cn=partialResults"                     0x09 "LDAPv2+ (not LDAPv3)"
+
+retcode-item   "cn=referral"                           0x0a    text="LDAPv3"
+retcode-item   "cn=adminLimitExceeded"                 0x0b    text="LDAPv3"
+retcode-item   "cn=unavailableCriticalExtension"       0x0c    text="LDAPv3"
+retcode-item   "cn=confidentialityRequired"            0x0d    text="LDAPv3"
+retcode-item   "cn=saslBindInProgress"                 0x0e    text="LDAPv3"
+
+# LDAP_ATTR_ERROR(n)   LDAP_RANGE((n),0x10,0x15) /* 16-21 */
+
+retcode-item   "cn=noSuchAttribute"                    0x10
+retcode-item   "cn=undefinedAttributeType"             0x11
+retcode-item   "cn=inappropriateMatching"              0x12
+retcode-item   "cn=constraintViolation"                0x13
+retcode-item   "cn=attributeOrValueExists"             0x14
+retcode-item   "cn=invalidAttributeSyntax"             0x15
+
+# LDAP_NAME_ERROR(n)   LDAP_RANGE((n),0x20,0x24) /* 32-34,36 */
+
+retcode-item   "cn=noSuchObject"                       0x20
+retcode-item   "cn=aliasProblem"                       0x21
+retcode-item   "cn=invalidDNSyntax"                    0x22
+#retcode-item  "cn=isLeaf"                             0x23    text="not LDAPv3"
+retcode-item   "cn=aliasDereferencingProblem"          0x24
+
+# LDAP_SECURITY_ERROR(n)       LDAP_RANGE((n),0x2F,0x32) /* 47-50 */
+
+retcode-item   "cn=proxyAuthzFailure"                  0x2F    text="LDAPv3 proxy authorization"
+retcode-item   "cn=inappropriateAuthentication"        0x30
+retcode-item   "cn=invalidCredentials"                 0x31
+retcode-item   "cn=insufficientAccessRights"           0x32
+
+# LDAP_SERVICE_ERROR(n)        LDAP_RANGE((n),0x33,0x36) /* 51-54 */
+
+retcode-item   "cn=busy"                               0x33
+retcode-item   "cn=unavailable"                        0x34
+retcode-item   "cn=unwillingToPerform"                 0x35
+retcode-item   "cn=loopDetect"                         0x36
+
+# LDAP_UPDATE_ERROR(n) LDAP_RANGE((n),0x40,0x47) /* 64-69,71 */
+
+retcode-item   "cn=namingViolation"                    0x40
+retcode-item   "cn=objectClassViolation"               0x41
+retcode-item   "cn=notAllowedOnNonleaf"                0x42
+retcode-item   "cn=notAllowedOnRDN"                    0x43
+retcode-item   "cn=entryAlreadyExists"                 0x44
+retcode-item   "cn=objectClassModsProhibited"          0x45
+retcode-item   "cn=resultsTooLarge"                    0x46    text="CLDAP"
+retcode-item   "cn=affectsMultipleDSAs"                0x47    text="LDAPv3"
+
+retcode-item   "cn=other"                              0x50
+
+# /* LCUP operation codes (113-117) - not implemented */
+retcode-item   "cn=cupResourcesExhausted"              0x71
+retcode-item   "cn=cupSecurityViolation"               0x72
+retcode-item   "cn=cupInvalidData"                     0x73
+retcode-item   "cn=cupUnsupportedScheme"               0x74
+retcode-item   "cn=cupReloadRequired"                  0x75
+
+# /* Cancel operation codes (118-121) */
+retcode-item   "cn=cancelled"                          0x76
+retcode-item   "cn=noSuchOperation"                    0x77
+retcode-item   "cn=tooLate"                            0x78
+retcode-item   "cn=cannotCancel"                       0x79
+
+
+# /* Experimental result codes */
+# LDAP_E_ERROR(n)      LDAP_RANGE((n),0x1000,0x3FFF) /* experimental */
+# LDAP_X_ERROR(n)      LDAP_RANGE((n),0x4000,0xFFFF) /* private use */
+
+# /* for the LDAP Sync operation */
+retcode-item   "cn=syncRefreshRequired"                0x4100
+
+# /* for the LDAP No-Op control */
+retcode-item   "cn=noOperation"                        0x410e
+
+# /* for the Assertion control */
+retcode-item   "cn=assertionFailed"                    0x410f
+
+# /* for the Chaining Behavior control (consecutive result codes requested;
+#  * see <draft-sermersheim-ldap-chaining> ) */
+retcode-item   "cn=noReferralsFound"                   0x4110
+retcode-item   "cn=cannotChain"                        0x4111
+
diff --git a/tests/data/slapd-retcode.conf b/tests/data/slapd-retcode.conf
new file mode 100644 (file)
index 0000000..94c608f
--- /dev/null
@@ -0,0 +1,52 @@
+# stand-alone slapd config -- for testing (with indexing)
+# $Header$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2005 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+include ./schema/core.schema
+include ./schema/cosine.schema
+include ./schema/inetorgperson.schema
+include ./schema/openldap.schema
+include ./schema/nis.schema
+include ./testdata/test.schema
+
+#
+pidfile     ./testrun/slapd.1.pid
+argsfile    ./testrun/slapd.1.args
+
+#mod#modulepath        ../servers/slapd/back-@BACKEND@/
+#mod#moduleload        back_@BACKEND@.la
+#monitormod#modulepath ../servers/slapd/back-monitor/
+#monitormod#moduleload back_monitor.la
+#retcodemod#modulepath ../servers/slapd/overlays/
+#retcodemod#moduleload retcode.la
+
+#######################################################################
+# database definitions
+#######################################################################
+
+database       @BACKEND@
+suffix         "dc=example,dc=com"
+directory      ./testrun/db.1.a
+rootdn         "cn=Manager,dc=example,dc=com"
+rootpw         secret
+#bdb#index             objectClass     eq
+#bdb#index             cn,sn,uid       pres,eq,sub
+#ldbm#index            objectClass     eq
+#ldbm#index            cn,sn,uid       pres,eq,sub
+
+overlay                retcode
+retcode-parent "ou=RetCodes,dc=example,dc=com"
+include                testdata/retcode.conf
+
+#monitor#database monitor
diff --git a/tests/scripts/test038-retcode b/tests/scripts/test038-retcode
new file mode 100755 (executable)
index 0000000..a1a0d38
--- /dev/null
@@ -0,0 +1,97 @@
+#! /bin/sh
+# $Header$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2005 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+if test $RETCODE = retcodeno; then 
+       echo "Retcode overlay not available, test skipped"
+       exit 0
+fi 
+
+mkdir -p $TESTDIR $DBDIR1
+
+echo "Running slapadd to build slapd database..."
+. $CONFFILTER $BACKEND $MONITORDB < $MCONF > $ADDCONF
+$SLAPADD -f $ADDCONF -l $LDIFORDERED
+RC=$?
+if test $RC != 0 ; then
+       echo "slapadd failed ($RC)!"
+       exit $RC
+fi
+
+echo "Running slapindex to index slapd database..."
+. $CONFFILTER $BACKEND $MONITORDB < $RETCODECONF > $CONF1
+$SLAPINDEX -f $CONF1
+RC=$?
+if test $RC != 0 ; then
+       echo "warning: slapindex failed ($RC)"
+       echo "  assuming no indexing support"
+fi
+
+echo "Starting slapd on TCP/IP port $PORT1..."
+$SLAPD -f $CONF1 -h $URI1 -d $LVL $TIMING > $LOG1 2>&1 &
+PID=$!
+if test $WAIT != 0 ; then
+    echo PID $PID
+    read foo
+fi
+KILLPIDS="$PID"
+
+echo "Testing slapd searching..."
+for i in 0 1 2 3 4 5; do
+       $LDAPSEARCH -s base -b "$MONITOR" -h $LOCALHOST -p $PORT1 \
+               '(objectclass=*)' > /dev/null 2>&1
+       RC=$?
+       if test $RC = 0 ; then
+               break
+       fi
+       echo "Waiting 5 seconds for slapd to start..."
+       sleep 5
+done
+
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Testing searching for timelimitExceeded..."
+$LDAPSEARCH -b "cn=timelimitExceeded,ou=RetCodes,$BASEDN" \
+       -h $LOCALHOST -p $PORT1 '(objectClass=*)' >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 3 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Testing modifying for unwillingToPerform..."
+$LDAPMODIFY -D "$MANAGERDN" -w $PASSWD \
+       -h $LOCALHOST -p $PORT1 >> $TESTOUT 2>&1 << EOMODS
+dn: cn=unwillingToPerform,ou=RetCodes,$BASEDN
+changetype: delete
+EOMODS
+RC=$?
+if test $RC != 53 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+echo ">>>>> Test succeeded"
+exit 0