summaryrefslogtreecommitdiffstats
path: root/tests/progs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/progs')
-rw-r--r--tests/progs/Makefile.in66
-rw-r--r--tests/progs/ldif-filter.c256
-rw-r--r--tests/progs/slapd-addel.c302
-rw-r--r--tests/progs/slapd-auth.c335
-rw-r--r--tests/progs/slapd-bind.c551
-rw-r--r--tests/progs/slapd-common.c550
-rw-r--r--tests/progs/slapd-common.h92
-rw-r--r--tests/progs/slapd-modify.c225
-rw-r--r--tests/progs/slapd-modrdn.c229
-rw-r--r--tests/progs/slapd-mtread.c724
-rw-r--r--tests/progs/slapd-read.c445
-rw-r--r--tests/progs/slapd-search.c493
-rw-r--r--tests/progs/slapd-tester.c1146
-rw-r--r--tests/progs/slapd-watcher.c809
14 files changed, 6223 insertions, 0 deletions
diff --git a/tests/progs/Makefile.in b/tests/progs/Makefile.in
new file mode 100644
index 0000000..7d6df22
--- /dev/null
+++ b/tests/progs/Makefile.in
@@ -0,0 +1,66 @@
+## Makefile.in for test programs
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2024 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+PROGRAMS = slapd-tester slapd-search slapd-read slapd-addel slapd-modrdn \
+ slapd-modify slapd-bind slapd-mtread ldif-filter slapd-watcher
+
+SRCS = slapd-common.c \
+ slapd-tester.c slapd-search.c slapd-read.c slapd-addel.c \
+ slapd-modrdn.c slapd-modify.c slapd-bind.c slapd-mtread.c \
+ ldif-filter.c slapd-watcher.c
+
+LDAP_INCDIR= ../../include
+LDAP_LIBDIR= ../../libraries
+
+XLIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLUTIL_A) $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+XXLIBS = $(SECURITY_LIBS) $(LUTIL_LIBS)
+XXXLIBS = $(LTHREAD_LIBS)
+
+OBJS = slapd-common.o
+
+# build-tools: FORCE
+# $(MAKE) $(MFLAGS) load-tools
+
+# load-tools: $(PROGRAMS)
+
+slapd-tester: slapd-tester.o $(OBJS) $(XLIBS)
+ $(LTLINK) -o $@ slapd-tester.o $(OBJS) $(LIBS)
+
+slapd-search: slapd-search.o $(OBJS) $(XLIBS)
+ $(LTLINK) -o $@ slapd-search.o $(OBJS) $(LIBS)
+
+slapd-read: slapd-read.o $(OBJS) $(XLIBS)
+ $(LTLINK) -o $@ slapd-read.o $(OBJS) $(LIBS)
+
+slapd-addel: slapd-addel.o $(OBJS) $(XLIBS)
+ $(LTLINK) -o $@ slapd-addel.o $(OBJS) $(LIBS)
+
+slapd-modrdn: slapd-modrdn.o $(OBJS) $(XLIBS)
+ $(LTLINK) -o $@ slapd-modrdn.o $(OBJS) $(LIBS)
+
+slapd-modify: slapd-modify.o $(OBJS) $(XLIBS)
+ $(LTLINK) -o $@ slapd-modify.o $(OBJS) $(LIBS)
+
+slapd-bind: slapd-bind.o $(OBJS) $(XLIBS)
+ $(LTLINK) -o $@ slapd-bind.o $(OBJS) $(LIBS)
+
+ldif-filter: ldif-filter.o $(OBJS) $(XLIBS)
+ $(LTLINK) -o $@ ldif-filter.o $(OBJS) $(LIBS)
+
+slapd-mtread: slapd-mtread.o $(OBJS) $(XLIBS)
+ $(LTLINK) -o $@ slapd-mtread.o $(OBJS) $(LIBS)
+
+slapd-watcher: slapd-watcher.o $(OBJS) $(XLIBS)
+ $(LTLINK) -o $@ slapd-watcher.o $(OBJS) $(LIBS)
diff --git a/tests/progs/ldif-filter.c b/tests/progs/ldif-filter.c
new file mode 100644
index 0000000..728eb6b
--- /dev/null
+++ b/tests/progs/ldif-filter.c
@@ -0,0 +1,256 @@
+/* ldif-filter -- clean up LDIF testdata from stdin */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2009-2024 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 file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/ctype.h>
+#include <ac/stdlib.h>
+#include <ac/string.h>
+#include <ac/unistd.h>
+#ifdef _WIN32
+#include <fcntl.h>
+#endif
+
+#define DEFAULT_SPECS "ndb=a,null=n"
+
+typedef struct { char *val; size_t len, alloc; } String;
+typedef struct { String *val; size_t len, alloc; } Strings;
+
+/* Flags and corresponding program options */
+enum { SORT_ATTRS = 1, SORT_ENTRIES = 2, NO_OUTPUT = 4, DUMMY_FLAG = 8 };
+static const char spec_options[] = "aen"; /* option index = log2(enum flag) */
+
+static const char *progname = "ldif-filter";
+static const String null_string = { NULL, 0, 0 };
+
+static void
+usage( void )
+{
+ fprintf( stderr, "\
+Usage: %s [-b backend] [-s spec[,spec]...]\n\
+Filter standard input by first <spec> matching '[<backend>]=[a][e][n]':\n\
+ - Remove LDIF comments.\n\
+ - 'a': Sort attributes in entries.\n\
+ - 'e': Sort any entries separated by just one empty line.\n\
+ - 'n': Output nothing.\n\
+<backend> defaults to the $BACKEND environment variable.\n\
+Use specs '%s' if no spec on the command line applies.\n",
+ progname, DEFAULT_SPECS );
+ exit( EXIT_FAILURE );
+}
+
+/* Return flags from "backend=flags" in spec; nonzero if backend found */
+static unsigned
+get_flags( const char *backend, const char *spec )
+{
+ size_t len = strlen( backend );
+ unsigned flags = DUMMY_FLAG;
+ const char *end, *tmp;
+
+ for ( ;; spec = end + ( *end != '\0' )) {
+ if ( !*spec )
+ return 0;
+ end = spec + strcspn( spec, "," );
+ if ( !(tmp = memchr( spec, '=', end-spec )))
+ break;
+ if ( tmp-spec == len && !memcmp( spec, backend, len )) {
+ spec = tmp+1;
+ break;
+ }
+ }
+
+ for ( ; spec < end; spec++ ) {
+ if ( (tmp = strchr( spec_options, *spec )) == NULL ) {
+ usage();
+ }
+ flags |= 1U << (tmp - spec_options);
+ }
+ return flags;
+}
+
+#define APPEND(s /* String or Strings */, data, count, isString) do { \
+ size_t slen = (s)->len, salloc = (s)->alloc, sz = sizeof *(s)->val; \
+ if ( salloc <= slen + (count) ) { \
+ (s)->alloc = salloc += salloc + ((count)|7) + 1; \
+ (s)->val = xrealloc( (s)->val, sz * salloc ); \
+ } \
+ memcpy( (s)->val + slen, data, sz * ((count) + !!(isString)) ); \
+ (s)->len = slen + (count); \
+} while (0)
+
+static void *
+xrealloc( void *ptr, size_t len )
+{
+ if ( (ptr = realloc( ptr, len )) == NULL ) {
+ perror( progname );
+ exit( EXIT_FAILURE );
+ }
+ return ptr;
+}
+
+static int
+cmp( const void *s, const void *t )
+{
+ return strcmp( ((const String *) s)->val, ((const String *) t)->val );
+}
+
+static void
+sort_strings( Strings *ss, size_t offset )
+{
+ qsort( ss->val + offset, ss->len - offset, sizeof(*ss->val), cmp );
+}
+
+/* Build entry ss[n] from attrs ss[n...], and free the attrs */
+static void
+build_entry( Strings *ss, size_t n, unsigned flags, size_t new_len )
+{
+ String *vals = ss->val, *e = &vals[n];
+ size_t end = ss->len;
+ char *ptr;
+
+ if ( flags & SORT_ATTRS ) {
+ sort_strings( ss, n + 1 );
+ }
+ e->val = xrealloc( e->val, e->alloc = new_len + 1 );
+ ptr = e->val + e->len;
+ e->len = new_len;
+ ss->len = ++n;
+ for ( ; n < end; free( vals[n++].val )) {
+ ptr = strcpy( ptr, vals[n].val ) + vals[n].len;
+ }
+ assert( ptr == e->val + new_len );
+}
+
+/* Flush entries to stdout and free them */
+static void
+flush_entries( Strings *ss, const char *sep, unsigned flags )
+{
+ size_t i, end = ss->len;
+ const char *prefix = "";
+
+ if ( flags & SORT_ENTRIES ) {
+ sort_strings( ss, 0 );
+ }
+ for ( i = 0; i < end; i++, prefix = sep ) {
+ if ( printf( "%s%s", prefix, ss->val[i].val ) < 0 ) {
+ perror( progname );
+ exit( EXIT_FAILURE );
+ }
+ free( ss->val[i].val );
+ }
+ ss->len = 0;
+}
+
+static void
+filter_stdin( unsigned flags )
+{
+ char line[256];
+ Strings ss = { NULL, 0, 0 }; /* entries + attrs of partial entry */
+ size_t entries = 0, attrs_totlen = 0, line_len;
+ const char *entry_sep = "\n", *sep = "";
+ int comment = 0, eof = 0, eol, prev_eol = 1; /* flags */
+ String *s;
+
+ /* LDIF = Entries ss[..entries-1] + sep + attrs ss[entries..] + line */
+ for ( ; !eof || ss.len || *sep; prev_eol = eol ) {
+ if ( eof || (eof = !fgets( line, sizeof(line), stdin ))) {
+ strcpy( line, prev_eol ? "" : *sep ? sep : "\n" );
+ }
+ line_len = strlen( line );
+ eol = (line_len == 0 || line[line_len - 1] == '\n');
+
+ if ( *line == ' ' ) { /* continuation line? */
+ prev_eol = 0;
+ } else if ( prev_eol ) { /* start of logical line? */
+ comment = (*line == '#');
+ }
+ if ( comment || (flags & NO_OUTPUT) ) {
+ continue;
+ }
+
+ /* Collect attrs for partial entry in ss[entries...] */
+ if ( !prev_eol && attrs_totlen != 0 ) {
+ goto grow_attr;
+ } else if ( line_len > (*line == '\r' ? 2 : 1) ) {
+ APPEND( &ss, &null_string, 1, 0 ); /* new attr */
+ grow_attr:
+ s = &ss.val[ss.len - 1];
+ APPEND( s, line, line_len, 1 ); /* strcat to attr */
+ attrs_totlen += line_len;
+ continue;
+ }
+
+ /* Empty line - consume sep+attrs or entries+sep */
+ if ( attrs_totlen != 0 ) {
+ entry_sep = sep;
+ if ( entries == 0 )
+ fputs( sep, stdout );
+ build_entry( &ss, entries++, flags, attrs_totlen );
+ attrs_totlen = 0;
+ } else {
+ flush_entries( &ss, entry_sep, flags );
+ fputs( sep, stdout );
+ entries = 0;
+ }
+ sep = "\r\n" + 2 - line_len; /* sep = copy(line) */
+ }
+
+ free( ss.val );
+}
+
+int
+main( int argc, char **argv )
+{
+ const char *backend = getenv( "BACKEND" ), *specs = "", *tmp;
+ unsigned flags;
+ int i;
+
+ if ( argc > 0 ) {
+ progname = (tmp = strrchr( argv[0], '/' )) ? tmp+1 : argv[0];
+ }
+
+ while ( (i = getopt( argc, argv, "b:s:" )) != EOF ) {
+ switch ( i ) {
+ case 'b':
+ backend = optarg;
+ break;
+ case 's':
+ specs = optarg;
+ break;
+ default:
+ usage();
+ }
+ }
+ if ( optind < argc ) {
+ usage();
+ }
+ if ( backend == NULL ) {
+ backend = "";
+ }
+
+#ifdef _WIN32
+ _setmode(1, _O_BINARY); /* don't convert \n to \r\n on stdout */
+#endif
+ flags = get_flags( backend, specs );
+ filter_stdin( flags ? flags : get_flags( backend, DEFAULT_SPECS ));
+ if ( fclose( stdout ) == EOF ) {
+ perror( progname );
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/progs/slapd-addel.c b/tests/progs/slapd-addel.c
new file mode 100644
index 0000000..620cd3a
--- /dev/null
+++ b/tests/progs/slapd-addel.c
@@ -0,0 +1,302 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2024 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 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 Kurt Spanier for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include "ac/stdlib.h"
+
+#include "ac/ctype.h"
+#include "ac/param.h"
+#include "ac/socket.h"
+#include "ac/string.h"
+#include "ac/unistd.h"
+#include "ac/wait.h"
+
+#include "ldap.h"
+#include "lutil.h"
+#include "ldif.h"
+
+#include "slapd-common.h"
+
+static LDIFRecord *
+get_add_entry( char *filename );
+
+static void
+do_addel( struct tester_conn_args *config,
+ LDIFRecord *record, int friendly );
+
+static void
+usage( char *name, char opt )
+{
+ if ( opt ) {
+ fprintf( stderr, "%s: unable to handle option \'%c\'\n\n",
+ name, opt );
+ }
+
+ fprintf( stderr, "usage: %s " TESTER_COMMON_HELP
+ "-f <addfile> "
+ "[-F]\n",
+ name );
+ exit( EXIT_FAILURE );
+}
+
+int
+main( int argc, char **argv )
+{
+ int i;
+ char *filename = NULL, *buf = NULL;
+ int friendly = 0;
+ struct LDIFFP *fp;
+ LDIFRecord record = {0};
+ struct tester_conn_args *config;
+ struct berval bv = {0};
+ unsigned long lineno = 0;
+
+ config = tester_init( "slapd-addel", TESTER_ADDEL );
+
+ while ( ( i = getopt( argc, argv, TESTER_COMMON_OPTS "Ff:" ) ) != EOF )
+ {
+ switch ( i ) {
+ case 'F':
+ friendly++;
+ break;
+
+ case 'i':
+ /* ignored (!) by now */
+ break;
+
+ case 'f': /* file with entry search request */
+ filename = optarg;
+ break;
+
+ default:
+ if ( tester_config_opt( config, i, optarg ) == LDAP_SUCCESS ) {
+ break;
+ }
+ usage( argv[0], i );
+ break;
+ }
+ }
+
+ if ( filename == NULL )
+ usage( argv[0], 0 );
+
+ if ( (fp = ldif_open( filename, "r" )) == NULL ) {
+ tester_perror( filename, "while reading ldif file" );
+ exit( EXIT_FAILURE );
+ }
+
+ i = 0;
+ if ( ldif_read_record( fp, &lineno, &buf, &i ) < 0 ) {
+ tester_error( "ldif_read_record failed" );
+ exit( EXIT_FAILURE );
+ }
+ bv.bv_val = buf;
+ bv.bv_len = i;
+
+ if ( ldap_parse_ldif_record( &bv, lineno, &record, "slapd-addel",
+ LDIF_DEFAULT_ADD | LDIF_ENTRIES_ONLY ) ) {
+ tester_error( "ldif_read_record failed" );
+ exit( EXIT_FAILURE );
+ }
+ ldif_close( fp );
+
+ if ( ( record.lr_op != LDAP_REQ_ADD ) || ( !record.lrop_mods ) ) {
+
+ fprintf( stderr, "%s: invalid entry DN in file \"%s\".\n",
+ argv[0], filename );
+ exit( EXIT_FAILURE );
+
+ }
+
+ tester_config_finish( config );
+
+ for ( i = 0; i < config->outerloops; i++ ) {
+ do_addel( config, &record, friendly );
+ }
+
+ free( buf );
+ exit( EXIT_SUCCESS );
+}
+
+
+static void
+addmodifyop( LDAPMod ***pmodsp, int modop, char *attr, char *value, int vlen )
+{
+ LDAPMod **pmods;
+ int i, j;
+ struct berval *bvp;
+
+ pmods = *pmodsp;
+ modop |= LDAP_MOD_BVALUES;
+
+ i = 0;
+ if ( pmods != NULL ) {
+ for ( ; pmods[ i ] != NULL; ++i ) {
+ if ( strcasecmp( pmods[ i ]->mod_type, attr ) == 0 &&
+ pmods[ i ]->mod_op == modop ) {
+ break;
+ }
+ }
+ }
+
+ if ( pmods == NULL || pmods[ i ] == NULL ) {
+ if (( pmods = (LDAPMod **)realloc( pmods, (i + 2) *
+ sizeof( LDAPMod * ))) == NULL ) {
+ tester_perror( "realloc", NULL );
+ exit( EXIT_FAILURE );
+ }
+ *pmodsp = pmods;
+ pmods[ i + 1 ] = NULL;
+ if (( pmods[ i ] = (LDAPMod *)calloc( 1, sizeof( LDAPMod )))
+ == NULL ) {
+ tester_perror( "calloc", NULL );
+ exit( EXIT_FAILURE );
+ }
+ pmods[ i ]->mod_op = modop;
+ if (( pmods[ i ]->mod_type = strdup( attr )) == NULL ) {
+ tester_perror( "strdup", NULL );
+ exit( EXIT_FAILURE );
+ }
+ }
+
+ if ( value != NULL ) {
+ j = 0;
+ if ( pmods[ i ]->mod_bvalues != NULL ) {
+ for ( ; pmods[ i ]->mod_bvalues[ j ] != NULL; ++j ) {
+ ;
+ }
+ }
+ if (( pmods[ i ]->mod_bvalues =
+ (struct berval **)ber_memrealloc( pmods[ i ]->mod_bvalues,
+ (j + 2) * sizeof( struct berval * ))) == NULL ) {
+ tester_perror( "ber_memrealloc", NULL );
+ exit( EXIT_FAILURE );
+ }
+ pmods[ i ]->mod_bvalues[ j + 1 ] = NULL;
+ if (( bvp = (struct berval *)ber_memalloc( sizeof( struct berval )))
+ == NULL ) {
+ tester_perror( "ber_memalloc", NULL );
+ exit( EXIT_FAILURE );
+ }
+ pmods[ i ]->mod_bvalues[ j ] = bvp;
+
+ bvp->bv_len = vlen;
+ if (( bvp->bv_val = (char *)malloc( vlen + 1 )) == NULL ) {
+ tester_perror( "malloc", NULL );
+ exit( EXIT_FAILURE );
+ }
+ AC_MEMCPY( bvp->bv_val, value, vlen );
+ bvp->bv_val[ vlen ] = '\0';
+ }
+}
+
+
+static void
+do_addel(
+ struct tester_conn_args *config,
+ LDIFRecord *record,
+ int friendly )
+{
+ LDAP *ld = NULL;
+ int i = 0, do_retry = config->retries;
+ int rc = LDAP_SUCCESS;
+
+retry:;
+ if ( ld == NULL ) {
+ tester_init_ld( &ld, config, 0 );
+ }
+
+ if ( do_retry == config->retries ) {
+ fprintf( stderr, "PID=%ld - Add/Delete(%d): entry=\"%s\".\n",
+ (long) pid, config->loops, record->lr_dn.bv_val );
+ }
+
+ for ( ; i < config->loops; i++ ) {
+
+ /* add the entry */
+ rc = ldap_add_ext_s( ld, record->lr_dn.bv_val, record->lrop_mods, NULL, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ tester_ldap_error( ld, "ldap_add_ext_s", NULL );
+ switch ( rc ) {
+ case LDAP_ALREADY_EXISTS:
+ /* NOTE: this likely means
+ * the delete failed
+ * during the previous round... */
+ if ( !friendly ) {
+ goto done;
+ }
+ break;
+
+ case LDAP_BUSY:
+ case LDAP_UNAVAILABLE:
+ if ( do_retry > 0 ) {
+ do_retry--;
+ goto retry;
+ }
+ /* fall thru */
+
+ default:
+ goto done;
+ }
+ }
+
+#if 0
+ /* wait a second for the add to really complete */
+ /* This masks some race conditions though. */
+ sleep( 1 );
+#endif
+
+ /* now delete the entry again */
+ rc = ldap_delete_ext_s( ld, record->lr_dn.bv_val, NULL, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ tester_ldap_error( ld, "ldap_delete_ext_s", NULL );
+ switch ( rc ) {
+ case LDAP_NO_SUCH_OBJECT:
+ /* NOTE: this likely means
+ * the add failed
+ * during the previous round... */
+ if ( !friendly ) {
+ goto done;
+ }
+ break;
+
+ case LDAP_BUSY:
+ case LDAP_UNAVAILABLE:
+ if ( do_retry > 0 ) {
+ do_retry--;
+ goto retry;
+ }
+ /* fall thru */
+
+ default:
+ goto done;
+ }
+ }
+ }
+
+done:;
+ fprintf( stderr, " PID=%ld - Add/Delete done (%d).\n", (long) pid, rc );
+
+ ldap_unbind_ext( ld, NULL, NULL );
+}
+
+
diff --git a/tests/progs/slapd-auth.c b/tests/progs/slapd-auth.c
new file mode 100644
index 0000000..9b3d89e
--- /dev/null
+++ b/tests/progs/slapd-auth.c
@@ -0,0 +1,335 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2006-2024 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 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 Howard Chu for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/stdlib.h>
+
+#include <ac/ctype.h>
+#include <ac/param.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/unistd.h>
+#include <ac/wait.h>
+#include <ac/time.h>
+#include <ac/signal.h>
+
+#include <ldap.h>
+#include <ldap_pvt_thread.h>
+#include <lutil.h>
+
+static int
+do_time( );
+
+/* This program is a simplified version of SLAMD's WeightedAuthRate jobclass.
+ * It doesn't offer as much configurability, but it's a good starting point.
+ * When run without the -R option it will behave as a Standard AuthRate job.
+ * Eventually this will grow into a set of C-based load generators for the SLAMD
+ * framework. This code is anywhere from 2 to 10 times more efficient than the
+ * original Java code, allowing servers to be fully loaded without requiring
+ * anywhere near as much load-generation hardware.
+ */
+static void
+usage( char *name )
+{
+ fprintf( stderr, "usage: %s -H <uri> -b <baseDN> -w <passwd> -t <seconds> -r lo:hi\n\t"
+ "[-R %:lo:hi] [-f <filter-template>] [-n <threads>] [-D <bindDN>] [-i <seconds>]\n",
+ name );
+ exit( EXIT_FAILURE );
+}
+
+static char *filter = "(uid=user.%d)";
+
+static char hname[1024];
+static char *uri = "ldap:///";
+static char *base;
+static char *pass;
+static char *binder;
+
+static int tdur, r1per, r1lo, r1hi, r2per, r2lo, r2hi;
+static int threads = 1;
+
+static int interval = 30;
+
+static volatile int *r1binds, *r2binds;
+static int *r1old, *r2old;
+static volatile int finish;
+
+int
+main( int argc, char **argv )
+{
+ int i;
+
+ while ( (i = getopt( argc, argv, "b:D:H:w:f:n:i:t:r:R:" )) != EOF ) {
+ switch( i ) {
+ case 'b': /* base DN of a tree of user DNs */
+ base = optarg;
+ break;
+
+ case 'D':
+ binder = optarg;
+ break;
+
+ case 'H': /* the server uri */
+ uri = optarg;
+ break;
+
+ case 'w':
+ pass = strdup( optarg );
+ break;
+
+ case 't': /* the duration to run */
+ if ( lutil_atoi( &tdur, optarg ) != 0 ) {
+ usage( argv[0] );
+ }
+ break;
+
+ case 'i': /* the time interval */
+ if ( lutil_atoi( &interval, optarg ) != 0 ) {
+ usage( argv[0] );
+ }
+ break;
+
+ case 'r': /* the uid range */
+ if ( sscanf(optarg, "%d:%d", &r1lo, &r1hi) != 2 ) {
+ usage( argv[0] );
+ }
+ break;
+
+ case 'R': /* percentage:2nd uid range */
+ if ( sscanf(optarg, "%d:%d:%d", &r2per, &r2lo, &r2hi) != 3 ) {
+ usage( argv[0] );
+ }
+ break;
+
+ case 'f':
+ filter = optarg;
+ break;
+
+ case 'n':
+ if ( lutil_atoi( &threads, optarg ) != 0 || threads < 1 ) {
+ usage( argv[0] );
+ }
+ break;
+
+ default:
+ usage( argv[0] );
+ break;
+ }
+ }
+
+ if ( tdur == 0 || r1hi <= r1lo )
+ usage( argv[0] );
+
+ r1per = 100 - r2per;
+ if ( r1per < 1 )
+ usage( argv[0] );
+
+ r1binds = calloc( threads*4, sizeof( int ));
+ r2binds = r1binds + threads;
+ r1old = (int *)r2binds + threads;
+ r2old = r1old + threads;
+
+ do_time( );
+
+ exit( EXIT_SUCCESS );
+}
+
+static void *
+my_task( void *my_num )
+{
+ LDAP *ld = NULL, *sld = NULL;
+ ber_int_t msgid;
+ LDAPMessage *res, *msg;
+ char *attrs[] = { "1.1", NULL };
+ int rc = LDAP_SUCCESS;
+ int tid = *(int *)my_num;
+
+ ldap_initialize( &ld, uri );
+ if ( ld == NULL ) {
+ perror( "ldap_initialize" );
+ return NULL;
+ }
+
+ {
+ int version = LDAP_VERSION3;
+ (void) ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION,
+ &version );
+ }
+ (void) ldap_set_option( ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF );
+
+ ldap_initialize( &sld, uri );
+ if ( sld == NULL ) {
+ perror( "ldap_initialize" );
+ return NULL;
+ }
+
+ {
+ int version = LDAP_VERSION3;
+ (void) ldap_set_option( sld, LDAP_OPT_PROTOCOL_VERSION,
+ &version );
+ }
+ (void) ldap_set_option( sld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF );
+ if ( binder ) {
+ rc = ldap_bind_s( sld, binder, pass, LDAP_AUTH_SIMPLE );
+ if ( rc != LDAP_SUCCESS ) {
+ ldap_perror( sld, "ldap_bind" );
+ }
+ }
+
+ r1binds[tid] = 0;
+
+ for (;;) {
+ char dn[BUFSIZ], *ptr, fstr[256];
+ int j, isr1;
+
+ if ( finish )
+ break;
+
+ j = rand() % 100;
+ if ( j < r1per ) {
+ j = rand() % r1hi;
+ isr1 = 1;
+ } else {
+ j = rand() % (r2hi - r2lo + 1 );
+ j += r2lo;
+ isr1 = 0;
+ }
+ sprintf(fstr, filter, j);
+
+ rc = ldap_search_ext( sld, base, LDAP_SCOPE_SUB,
+ fstr, attrs, 0, NULL, NULL, 0, 0, &msgid );
+ if ( rc != LDAP_SUCCESS ) {
+ ldap_perror( sld, "ldap_search_ex" );
+ return NULL;
+ }
+
+ while (( rc=ldap_result( sld, LDAP_RES_ANY, LDAP_MSG_ONE, NULL, &res )) >0){
+ BerElement *ber;
+ struct berval bv;
+ char *ptr;
+ int done = 0;
+
+ for (msg = ldap_first_message( sld, res ); msg;
+ msg = ldap_next_message( sld, msg )) {
+ switch ( ldap_msgtype( msg )) {
+ case LDAP_RES_SEARCH_ENTRY:
+ rc = ldap_get_dn_ber( sld, msg, &ber, &bv );
+ strcpy(dn, bv.bv_val );
+ ber_free( ber, 0 );
+ break;
+ case LDAP_RES_SEARCH_RESULT:
+ done = 1;
+ break;
+ }
+ if ( done )
+ break;
+ }
+ ldap_msgfree( res );
+ if ( done ) break;
+ }
+
+ rc = ldap_bind_s( ld, dn, pass, LDAP_AUTH_SIMPLE );
+ if ( rc != LDAP_SUCCESS ) {
+ ldap_perror( ld, "ldap_bind" );
+ }
+ if ( isr1 )
+ r1binds[tid]++;
+ else
+ r2binds[tid]++;
+ }
+
+ ldap_unbind( sld );
+ ldap_unbind( ld );
+
+ return NULL;
+}
+
+static int
+do_time( )
+{
+ struct timeval tv;
+ time_t now, prevt, start;
+
+ int r1new, r2new;
+ int dt, dr1, dr2, rr1, rr2;
+ int dr10, dr20;
+ int i;
+
+ gethostname(hname, sizeof(hname));
+ printf("%s(tid)\tdeltaT\tauth1\tauth2\trate1\trate2\tRate1+2\n", hname);
+ srand(getpid());
+
+ prevt = start = time(0L);
+
+ for ( i = 0; i<threads; i++ ) {
+ ldap_pvt_thread_t thr;
+ r1binds[i] = i;
+ ldap_pvt_thread_create( &thr, 1, my_task, (void *)&r1binds[i] );
+ }
+
+ for (;;) {
+ tv.tv_sec = interval;
+ tv.tv_usec = 0;
+
+ select(0, NULL, NULL, NULL, &tv);
+
+ now = time(0L);
+
+ dt = now - prevt;
+ prevt = now;
+
+ dr10 = 0;
+ dr20 = 0;
+
+ for ( i = 0; i < threads; i++ ) {
+ r1new = r1binds[i];
+ r2new = r2binds[i];
+
+ dr1 = r1new - r1old[i];
+ dr2 = r2new - r2old[i];
+ rr1 = dr1 / dt;
+ rr2 = dr2 / dt;
+
+ printf("%s(%d)\t%d\t%d\t%d\t%d\t%d\t%d\n",
+ hname, i, dt, dr1, dr2, rr1, rr2, rr1 + rr2);
+
+ dr10 += dr1;
+ dr20 += dr2;
+
+ r1old[i] = r1new;
+ r2old[i] = r2new;
+ }
+ if ( i > 1 ) {
+ rr1 = dr10 / dt;
+ rr2 = dr20 / dt;
+
+ printf("%s(sum)\t%d\t%d\t%d\t%d\t%d\t%d\n",
+ hname, 0, dr10, dr20, rr1, rr2, rr1 + rr2);
+ }
+
+ if ( now - start >= tdur ) {
+ finish = 1;
+ break;
+ }
+ }
+ return 0;
+}
diff --git a/tests/progs/slapd-bind.c b/tests/progs/slapd-bind.c
new file mode 100644
index 0000000..c99db57
--- /dev/null
+++ b/tests/progs/slapd-bind.c
@@ -0,0 +1,551 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2024 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 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 Howard Chu for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include "ac/stdlib.h"
+#include "ac/time.h"
+
+#include "ac/ctype.h"
+#include "ac/param.h"
+#include "ac/socket.h"
+#include "ac/string.h"
+#include "ac/unistd.h"
+#include "ac/wait.h"
+#include "ac/time.h"
+
+#include "ldap.h"
+#include "lutil.h"
+#include "lutil_ldap.h"
+#include "lber_pvt.h"
+#include "ldap_pvt.h"
+
+#include "slapd-common.h"
+
+static int
+do_bind( struct tester_conn_args *config, char *dn, int maxloop, int force,
+ int noinit, LDAP **ldp, struct berval *pass, int action_type, void *action );
+
+static int
+do_base( struct tester_conn_args *config, char *dn, char *base, char *filter, char *pwattr,
+ int force, int noinit, int action_type, void *action );
+
+/* This program can be invoked two ways: if -D is used to specify a Bind DN,
+ * that DN will be used repeatedly for all of the Binds. If instead -b is used
+ * to specify a base DN, a search will be done for all "person" objects under
+ * that base DN. Then DNs from this list will be randomly selected for each
+ * Bind request. All of the users must have identical passwords. Also it is
+ * assumed that the users are all onelevel children of the base.
+ */
+static void
+usage( char *name, char opt )
+{
+ if ( opt ) {
+ fprintf( stderr, "%s: unable to handle option \'%c\'\n\n",
+ name, opt );
+ }
+
+ fprintf( stderr, "usage: %s " TESTER_COMMON_HELP
+ "[-b <baseDN> [-f <searchfilter>] [-a pwattr]] "
+ "[-B <extra>[,...]] "
+ "[-F] "
+ "[-I]\n",
+ name );
+ exit( EXIT_FAILURE );
+}
+
+int
+main( int argc, char **argv )
+{
+ int i;
+ char *base = NULL;
+ char *filter = "(objectClass=person)";
+ char *pwattr = NULL;
+ int force = 0;
+ int noinit = 1;
+ struct tester_conn_args *config;
+
+ /* extra action to do after bind... */
+ struct berval type[] = {
+ BER_BVC( "tester=" ),
+ BER_BVC( "add=" ),
+ BER_BVC( "bind=" ),
+ BER_BVC( "modify=" ),
+ BER_BVC( "modrdn=" ),
+ BER_BVC( "read=" ),
+ BER_BVC( "search=" ),
+ BER_BVNULL
+ };
+
+ LDAPURLDesc *extra_ludp = NULL;
+
+ config = tester_init( "slapd-bind", TESTER_BIND );
+
+ /* by default, tolerate invalid credentials */
+ tester_ignore_str2errlist( "*INVALID_CREDENTIALS" );
+
+ while ( ( i = getopt( argc, argv, TESTER_COMMON_OPTS "a:B:b:Ff:I" ) ) != EOF )
+ {
+ switch ( i ) {
+ case 'a':
+ pwattr = optarg;
+ break;
+
+ case 'b': /* base DN of a tree of user DNs */
+ base = optarg;
+ break;
+
+ case 'B':
+ {
+ int c;
+
+ for ( c = 0; type[c].bv_val; c++ ) {
+ if ( strncasecmp( optarg, type[c].bv_val, type[c].bv_len ) == 0 )
+ {
+ break;
+ }
+ }
+
+ if ( type[c].bv_val == NULL ) {
+ usage( argv[0], 'B' );
+ }
+
+ switch ( c ) {
+ case TESTER_TESTER:
+ case TESTER_BIND:
+ /* invalid */
+ usage( argv[0], 'B' );
+
+ case TESTER_SEARCH:
+ {
+ if ( ldap_url_parse( &optarg[type[c].bv_len], &extra_ludp ) != LDAP_URL_SUCCESS )
+ {
+ usage( argv[0], 'B' );
+ }
+ } break;
+
+ case TESTER_ADDEL:
+ case TESTER_MODIFY:
+ case TESTER_MODRDN:
+ case TESTER_READ:
+ /* nothing to do */
+ break;
+
+ default:
+ assert( 0 );
+ }
+
+ } break;
+
+ case 'f':
+ filter = optarg;
+ break;
+
+ case 'F':
+ force++;
+ break;
+
+ case 'I':
+ /* reuse connection */
+ noinit = 0;
+ break;
+
+ default:
+ if ( tester_config_opt( config, i, optarg ) == LDAP_SUCCESS ) {
+ break;
+ }
+ usage( argv[0], i );
+ break;
+ }
+ }
+
+ tester_config_finish( config );
+
+ for ( i = 0; i < config->outerloops; i++ ) {
+ int rc;
+
+ if ( base != NULL ) {
+ rc = do_base( config, config->binddn, base,
+ filter, pwattr, force, noinit, -1, NULL );
+ } else {
+ rc = do_bind( config, config->binddn,
+ config->loops, force, noinit, NULL, &config->pass, -1, NULL );
+ }
+ if ( rc == LDAP_SERVER_DOWN )
+ break;
+ }
+
+ exit( EXIT_SUCCESS );
+}
+
+
+static int
+do_bind( struct tester_conn_args *config, char *dn, int maxloop, int force,
+ int noinit, LDAP **ldp, struct berval *pass, int action_type, void *action )
+{
+ LDAP *ld = ldp ? *ldp : NULL;
+ char *bindfunc = "ldap_sasl_bind_s";
+ int i, rc = -1;
+
+ /* for internal search */
+ int timelimit = 0;
+ int sizelimit = 0;
+
+ switch ( action_type ) {
+ case -1:
+ break;
+
+ case TESTER_SEARCH:
+ {
+ LDAPURLDesc *ludp = (LDAPURLDesc *)action;
+
+ assert( action != NULL );
+
+ if ( ludp->lud_exts != NULL ) {
+ for ( i = 0; ludp->lud_exts[ i ] != NULL; i++ ) {
+ char *ext = ludp->lud_exts[ i ];
+ int crit = 0;
+
+ if (ext[0] == '!') {
+ crit++;
+ ext++;
+ }
+
+ if ( strncasecmp( ext, "x-timelimit=", STRLENOF( "x-timelimit=" ) ) == 0 ) {
+ if ( lutil_atoi( &timelimit, &ext[ STRLENOF( "x-timelimit=" ) ] ) && crit ) {
+ tester_error( "unable to parse critical extension x-timelimit" );
+ }
+
+ } else if ( strncasecmp( ext, "x-sizelimit=", STRLENOF( "x-sizelimit=" ) ) == 0 ) {
+ if ( lutil_atoi( &sizelimit, &ext[ STRLENOF( "x-sizelimit=" ) ] ) && crit ) {
+ tester_error( "unable to parse critical extension x-sizelimit" );
+ }
+
+ } else if ( crit ) {
+ tester_error( "unknown critical extension" );
+ }
+ }
+ }
+ } break;
+
+ default:
+ /* nothing to do yet */
+ break;
+ }
+
+ if ( maxloop > 1 ) {
+ fprintf( stderr, "PID=%ld - Bind(%d): dn=\"%s\".\n",
+ (long) pid, maxloop, dn );
+ }
+
+ for ( i = 0; i < maxloop; i++ ) {
+ if ( !noinit || ld == NULL ) {
+ tester_init_ld( &ld, config, TESTER_INIT_ONLY );
+
+#ifdef HAVE_CYRUS_SASL
+ if ( config->secprops != NULL ) {
+ rc = ldap_set_option( ld,
+ LDAP_OPT_X_SASL_SECPROPS, config->secprops );
+
+ if( rc != LDAP_OPT_SUCCESS ) {
+ tester_ldap_error( ld, "ldap_set_option(SECPROPS)", NULL );
+ exit( EXIT_FAILURE );
+ }
+ }
+#endif
+ }
+
+ if ( config->authmethod == LDAP_AUTH_SASL ) {
+#ifdef HAVE_CYRUS_SASL
+ bindfunc = "ldap_sasl_interactive_bind_s";
+ rc = ldap_sasl_interactive_bind_s( ld,
+ dn,
+ config->mech,
+ NULL, NULL,
+ LDAP_SASL_QUIET,
+ lutil_sasl_interact,
+ config->defaults );
+#else /* HAVE_CYRUS_SASL */
+ /* caller shouldn't have allowed this */
+ assert(0);
+#endif
+ } else if ( config->authmethod == LDAP_AUTH_SIMPLE ) {
+ bindfunc = "ldap_sasl_bind_s";
+ rc = ldap_sasl_bind_s( ld,
+ dn, LDAP_SASL_SIMPLE,
+ pass, NULL, NULL, NULL );
+ }
+
+ if ( rc ) {
+ int first = tester_ignore_err( rc );
+
+ /* if ignore.. */
+ if ( first ) {
+ /* only log if first occurrence */
+ if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) {
+ tester_ldap_error( ld, bindfunc, NULL );
+ }
+ rc = LDAP_SUCCESS;
+
+ } else {
+ tester_ldap_error( ld, bindfunc, NULL );
+ }
+ }
+
+ switch ( action_type ) {
+ case -1:
+ break;
+
+ case TESTER_SEARCH:
+ {
+ LDAPURLDesc *ludp = (LDAPURLDesc *)action;
+ LDAPMessage *res = NULL;
+ struct timeval tv = { 0 }, *tvp = NULL;
+
+ if ( timelimit ) {
+ tv.tv_sec = timelimit;
+ tvp = &tv;
+ }
+
+ assert( action != NULL );
+
+ rc = ldap_search_ext_s( ld,
+ ludp->lud_dn, ludp->lud_scope,
+ ludp->lud_filter, ludp->lud_attrs, 0,
+ NULL, NULL, tvp, sizelimit, &res );
+ ldap_msgfree( res );
+ } break;
+
+ default:
+ /* nothing to do yet */
+ break;
+ }
+
+ if ( !noinit ) {
+ ldap_unbind_ext( ld, NULL, NULL );
+ ld = NULL;
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ break;
+ }
+ }
+
+ if ( maxloop > 1 ) {
+ fprintf( stderr, " PID=%ld - Bind done (%d).\n", (long) pid, rc );
+ }
+
+ if ( ldp && noinit ) {
+ *ldp = ld;
+
+ } else if ( ld != NULL ) {
+ ldap_unbind_ext( ld, NULL, NULL );
+ }
+
+ return rc;
+}
+
+
+static int
+do_base( struct tester_conn_args *config, char *dn, char *base, char *filter, char *pwattr,
+ int force, int noinit, int action_type, void *action )
+{
+ LDAP *ld = NULL;
+ int i = 0;
+ int rc = LDAP_SUCCESS;
+ ber_int_t msgid;
+ LDAPMessage *res, *msg;
+ char **dns = NULL;
+ struct berval *creds = NULL;
+ char *attrs[] = { LDAP_NO_ATTRS, NULL };
+ int ndns = 0;
+#ifdef _WIN32
+ DWORD beg, end;
+#else
+ struct timeval beg, end;
+#endif
+ char *nullstr = "";
+
+ tester_init_ld( &ld, config, 0 );
+
+ fprintf( stderr, "PID=%ld - Bind(%d): base=\"%s\", filter=\"%s\" attr=\"%s\".\n",
+ (long) pid, config->loops, base, filter, pwattr );
+
+ if ( pwattr != NULL ) {
+ attrs[ 0 ] = pwattr;
+ }
+ rc = ldap_search_ext( ld, base, LDAP_SCOPE_SUBTREE,
+ filter, attrs, 0, NULL, NULL, 0, 0, &msgid );
+ if ( rc != LDAP_SUCCESS ) {
+ tester_ldap_error( ld, "ldap_search_ext", NULL );
+ exit( EXIT_FAILURE );
+ }
+
+ while ( ( rc = ldap_result( ld, LDAP_RES_ANY, LDAP_MSG_ONE, NULL, &res ) ) > 0 )
+ {
+ BerElement *ber;
+ struct berval bv;
+ int done = 0;
+
+ for ( msg = ldap_first_message( ld, res ); msg;
+ msg = ldap_next_message( ld, msg ) )
+ {
+ switch ( ldap_msgtype( msg ) ) {
+ case LDAP_RES_SEARCH_ENTRY:
+ rc = ldap_get_dn_ber( ld, msg, &ber, &bv );
+ dns = realloc( dns, (ndns + 1)*sizeof(char *) );
+ if ( !dns ) {
+ tester_error( "realloc failed" );
+ exit( EXIT_FAILURE );
+ }
+ dns[ndns] = ber_strdup( bv.bv_val );
+ if ( pwattr != NULL ) {
+ struct berval **values = ldap_get_values_len( ld, msg, pwattr );
+
+ creds = realloc( creds, (ndns + 1)*sizeof(struct berval) );
+ if ( !creds ) {
+ tester_error( "realloc failed" );
+ exit( EXIT_FAILURE );
+ }
+ if ( values == NULL ) {
+novals:;
+ creds[ndns].bv_len = 0;
+ creds[ndns].bv_val = nullstr;
+
+ } else {
+ static struct berval cleartext = BER_BVC( "{CLEARTEXT} " );
+ struct berval value = *values[ 0 ];
+
+ if ( value.bv_val[ 0 ] == '{' ) {
+ char *end = ber_bvchr( &value, '}' );
+
+ if ( end ) {
+ if ( ber_bvcmp( &value, &cleartext ) == 0 ) {
+ value.bv_val += cleartext.bv_len;
+ value.bv_len -= cleartext.bv_len;
+
+ } else {
+ ldap_value_free_len( values );
+ goto novals;
+ }
+ }
+
+ }
+
+ ber_dupbv( &creds[ndns], &value );
+ ldap_value_free_len( values );
+ }
+ }
+ ndns++;
+ ber_free( ber, 0 );
+ break;
+
+ case LDAP_RES_SEARCH_RESULT:
+ done = 1;
+ break;
+ }
+ if ( done )
+ break;
+ }
+ ldap_msgfree( res );
+ if ( done ) break;
+ }
+
+#ifdef _WIN32
+ beg = GetTickCount();
+#else
+ gettimeofday( &beg, NULL );
+#endif
+
+ if ( ndns == 0 ) {
+ tester_error( "No DNs" );
+ if ( ld != NULL ) {
+ ldap_unbind_ext( ld, NULL, NULL );
+ }
+ return 1;
+ }
+
+ fprintf( stderr, " PID=%ld - Bind base=\"%s\" filter=\"%s\" got %d values.\n",
+ (long) pid, base, filter, ndns );
+
+ /* Ok, got list of DNs, now start binding to each */
+ for ( i = 0; i < config->loops; i++ ) {
+ struct berval *pass = &config->pass;
+ int j;
+
+#if 0 /* use high-order bits for better randomness (Numerical Recipes in "C") */
+ j = rand() % ndns;
+#endif
+ j = ((double)ndns)*rand()/(RAND_MAX + 1.0);
+
+ if ( creds && !BER_BVISEMPTY( &creds[j] ) ) {
+ pass = &creds[j];
+ }
+
+ if ( do_bind( config, dns[j], 1, force, noinit, &ld, pass,
+ action_type, action ) && !force )
+ {
+ break;
+ }
+ }
+
+ if ( ld != NULL ) {
+ ldap_unbind_ext( ld, NULL, NULL );
+ ld = NULL;
+ }
+
+#ifdef _WIN32
+ end = GetTickCount();
+ end -= beg;
+
+ fprintf( stderr, " PID=%ld - Bind done %d in %d.%03d seconds.\n",
+ (long) pid, i, end / 1000, end % 1000 );
+#else
+ gettimeofday( &end, NULL );
+ end.tv_usec -= beg.tv_usec;
+ if (end.tv_usec < 0 ) {
+ end.tv_usec += 1000000;
+ end.tv_sec -= 1;
+ }
+ end.tv_sec -= beg.tv_sec;
+
+ fprintf( stderr, " PID=%ld - Bind done %d in %ld.%06ld seconds.\n",
+ (long) pid, i, (long) end.tv_sec, (long) end.tv_usec );
+#endif
+
+ if ( dns ) {
+ for ( i = 0; i < ndns; i++ ) {
+ ber_memfree( dns[i] );
+ }
+ free( dns );
+ }
+
+ if ( creds ) {
+ for ( i = 0; i < ndns; i++ ) {
+ if ( creds[i].bv_val != nullstr ) {
+ ber_memfree( creds[i].bv_val );
+ }
+ }
+ free( creds );
+ }
+
+ return 0;
+}
diff --git a/tests/progs/slapd-common.c b/tests/progs/slapd-common.c
new file mode 100644
index 0000000..d8b66a0
--- /dev/null
+++ b/tests/progs/slapd-common.c
@@ -0,0 +1,550 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2024 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 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 Howard Chu for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include "ac/stdlib.h"
+#include "ac/unistd.h"
+#include "ac/string.h"
+#include "ac/errno.h"
+
+#include "ldap.h"
+
+#include "lutil.h"
+#include "lutil_ldap.h"
+#include "ldap_pvt.h"
+#include "slapd-common.h"
+
+/* global vars */
+pid_t pid;
+int debug;
+
+/* static vars */
+static char progname[ BUFSIZ ];
+tester_t progtype;
+
+/*
+ * ignore_count[] is indexed by result code:
+ * negative for OpenLDAP client-side errors, positive for protocol codes.
+ */
+#define TESTER_CLIENT_FIRST LDAP_REFERRAL_LIMIT_EXCEEDED /* negative */
+#define TESTER_SERVER_LAST LDAP_OTHER
+static int ignore_base [ -TESTER_CLIENT_FIRST + TESTER_SERVER_LAST + 1 ];
+#define ignore_count (ignore_base - TESTER_CLIENT_FIRST)
+
+static const struct {
+ const char *name;
+ int err;
+} ignore_str2err[] = {
+ { "OPERATIONS_ERROR", LDAP_OPERATIONS_ERROR },
+ { "PROTOCOL_ERROR", LDAP_PROTOCOL_ERROR },
+ { "TIMELIMIT_EXCEEDED", LDAP_TIMELIMIT_EXCEEDED },
+ { "SIZELIMIT_EXCEEDED", LDAP_SIZELIMIT_EXCEEDED },
+ { "COMPARE_FALSE", LDAP_COMPARE_FALSE },
+ { "COMPARE_TRUE", LDAP_COMPARE_TRUE },
+ { "AUTH_METHOD_NOT_SUPPORTED", LDAP_AUTH_METHOD_NOT_SUPPORTED },
+ { "STRONG_AUTH_NOT_SUPPORTED", LDAP_STRONG_AUTH_NOT_SUPPORTED },
+ { "STRONG_AUTH_REQUIRED", LDAP_STRONG_AUTH_REQUIRED },
+ { "STRONGER_AUTH_REQUIRED", LDAP_STRONGER_AUTH_REQUIRED },
+ { "PARTIAL_RESULTS", LDAP_PARTIAL_RESULTS },
+
+ { "REFERRAL", LDAP_REFERRAL },
+ { "ADMINLIMIT_EXCEEDED", LDAP_ADMINLIMIT_EXCEEDED },
+ { "UNAVAILABLE_CRITICAL_EXTENSION", LDAP_UNAVAILABLE_CRITICAL_EXTENSION },
+ { "CONFIDENTIALITY_REQUIRED", LDAP_CONFIDENTIALITY_REQUIRED },
+ { "SASL_BIND_IN_PROGRESS", LDAP_SASL_BIND_IN_PROGRESS },
+
+ { "NO_SUCH_ATTRIBUTE", LDAP_NO_SUCH_ATTRIBUTE },
+ { "UNDEFINED_TYPE", LDAP_UNDEFINED_TYPE },
+ { "INAPPROPRIATE_MATCHING", LDAP_INAPPROPRIATE_MATCHING },
+ { "CONSTRAINT_VIOLATION", LDAP_CONSTRAINT_VIOLATION },
+ { "TYPE_OR_VALUE_EXISTS", LDAP_TYPE_OR_VALUE_EXISTS },
+ { "INVALID_SYNTAX", LDAP_INVALID_SYNTAX },
+
+ { "NO_SUCH_OBJECT", LDAP_NO_SUCH_OBJECT },
+ { "ALIAS_PROBLEM", LDAP_ALIAS_PROBLEM },
+ { "INVALID_DN_SYNTAX", LDAP_INVALID_DN_SYNTAX },
+ { "IS_LEAF", LDAP_IS_LEAF },
+ { "ALIAS_DEREF_PROBLEM", LDAP_ALIAS_DEREF_PROBLEM },
+
+ /* obsolete */
+ { "PROXY_AUTHZ_FAILURE", LDAP_X_PROXY_AUTHZ_FAILURE },
+ { "INAPPROPRIATE_AUTH", LDAP_INAPPROPRIATE_AUTH },
+ { "INVALID_CREDENTIALS", LDAP_INVALID_CREDENTIALS },
+ { "INSUFFICIENT_ACCESS", LDAP_INSUFFICIENT_ACCESS },
+
+ { "BUSY", LDAP_BUSY },
+ { "UNAVAILABLE", LDAP_UNAVAILABLE },
+ { "UNWILLING_TO_PERFORM", LDAP_UNWILLING_TO_PERFORM },
+ { "LOOP_DETECT", LDAP_LOOP_DETECT },
+
+ { "NAMING_VIOLATION", LDAP_NAMING_VIOLATION },
+ { "OBJECT_CLASS_VIOLATION", LDAP_OBJECT_CLASS_VIOLATION },
+ { "NOT_ALLOWED_ON_NONLEAF", LDAP_NOT_ALLOWED_ON_NONLEAF },
+ { "NOT_ALLOWED_ON_RDN", LDAP_NOT_ALLOWED_ON_RDN },
+ { "ALREADY_EXISTS", LDAP_ALREADY_EXISTS },
+ { "NO_OBJECT_CLASS_MODS", LDAP_NO_OBJECT_CLASS_MODS },
+ { "RESULTS_TOO_LARGE", LDAP_RESULTS_TOO_LARGE },
+ { "AFFECTS_MULTIPLE_DSAS", LDAP_AFFECTS_MULTIPLE_DSAS },
+
+ { "OTHER", LDAP_OTHER },
+
+ { "SERVER_DOWN", LDAP_SERVER_DOWN },
+ { "LOCAL_ERROR", LDAP_LOCAL_ERROR },
+ { "ENCODING_ERROR", LDAP_ENCODING_ERROR },
+ { "DECODING_ERROR", LDAP_DECODING_ERROR },
+ { "TIMEOUT", LDAP_TIMEOUT },
+ { "AUTH_UNKNOWN", LDAP_AUTH_UNKNOWN },
+ { "FILTER_ERROR", LDAP_FILTER_ERROR },
+ { "USER_CANCELLED", LDAP_USER_CANCELLED },
+ { "PARAM_ERROR", LDAP_PARAM_ERROR },
+ { "NO_MEMORY", LDAP_NO_MEMORY },
+ { "CONNECT_ERROR", LDAP_CONNECT_ERROR },
+ { "NOT_SUPPORTED", LDAP_NOT_SUPPORTED },
+ { "CONTROL_NOT_FOUND", LDAP_CONTROL_NOT_FOUND },
+ { "NO_RESULTS_RETURNED", LDAP_NO_RESULTS_RETURNED },
+ { "MORE_RESULTS_TO_RETURN", LDAP_MORE_RESULTS_TO_RETURN },
+ { "CLIENT_LOOP", LDAP_CLIENT_LOOP },
+ { "REFERRAL_LIMIT_EXCEEDED", LDAP_REFERRAL_LIMIT_EXCEEDED },
+
+ { NULL }
+};
+
+#define UNKNOWN_ERR (1234567890)
+
+#define RETRIES 0
+#define LOOPS 100
+
+static int
+tester_ignore_str2err( const char *err )
+{
+ int i, ignore = 1;
+
+ if ( strcmp( err, "ALL" ) == 0 ) {
+ for ( i = 0; ignore_str2err[ i ].name != NULL; i++ ) {
+ ignore_count[ ignore_str2err[ i ].err ] = 1;
+ }
+ ignore_count[ LDAP_SUCCESS ] = 0;
+
+ return 0;
+ }
+
+ if ( err[ 0 ] == '!' ) {
+ ignore = 0;
+ err++;
+
+ } else if ( err[ 0 ] == '*' ) {
+ ignore = -1;
+ err++;
+ }
+
+ for ( i = 0; ignore_str2err[ i ].name != NULL; i++ ) {
+ if ( strcmp( err, ignore_str2err[ i ].name ) == 0 ) {
+ int err = ignore_str2err[ i ].err;
+
+ if ( err != LDAP_SUCCESS ) {
+ ignore_count[ err ] = ignore;
+ }
+
+ return err;
+ }
+ }
+
+ return UNKNOWN_ERR;
+}
+
+int
+tester_ignore_str2errlist( const char *err )
+{
+ int i;
+ char **errs = ldap_str2charray( err, "," );
+
+ for ( i = 0; errs[ i ] != NULL; i++ ) {
+ /* TODO: allow <err>:<prog> to ignore <err> only when <prog> */
+ (void)tester_ignore_str2err( errs[ i ] );
+ }
+
+ ldap_charray_free( errs );
+
+ return 0;
+}
+
+int
+tester_ignore_err( int err )
+{
+ int rc = 1;
+
+ if ( err && TESTER_CLIENT_FIRST <= err && err <= TESTER_SERVER_LAST ) {
+ rc = ignore_count[ err ];
+ if ( rc != 0 ) {
+ ignore_count[ err ] = rc + (rc > 0 ? 1 : -1);
+ }
+ }
+
+ /* SUCCESS is always "ignored" */
+ return rc;
+}
+
+struct tester_conn_args *
+tester_init( const char *pname, tester_t ptype )
+{
+ static struct tester_conn_args config = {
+ .authmethod = -1,
+ .retries = RETRIES,
+ .loops = LOOPS,
+ .outerloops = 1,
+
+ .uri = NULL,
+ };
+
+ pid = getpid();
+ srand( pid );
+ snprintf( progname, sizeof( progname ), "%s PID=%d", pname, pid );
+ progtype = ptype;
+
+ return &config;
+}
+
+void
+tester_ldap_error( LDAP *ld, const char *fname, const char *msg )
+{
+ int err;
+ char *text = NULL;
+ LDAPControl **ctrls = NULL;
+
+ ldap_get_option( ld, LDAP_OPT_RESULT_CODE, (void *)&err );
+ if ( err != LDAP_SUCCESS ) {
+ ldap_get_option( ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void *)&text );
+ }
+
+ fprintf( stderr, "%s: %s: %s (%d) %s %s\n",
+ progname, fname, ldap_err2string( err ), err,
+ text == NULL ? "" : text,
+ msg ? msg : "" );
+
+ if ( text ) {
+ ldap_memfree( text );
+ text = NULL;
+ }
+
+ ldap_get_option( ld, LDAP_OPT_MATCHED_DN, (void *)&text );
+ if ( text != NULL ) {
+ if ( text[ 0 ] != '\0' ) {
+ fprintf( stderr, "\tmatched: %s\n", text );
+ }
+ ldap_memfree( text );
+ text = NULL;
+ }
+
+ ldap_get_option( ld, LDAP_OPT_SERVER_CONTROLS, (void *)&ctrls );
+ if ( ctrls != NULL ) {
+ int i;
+
+ fprintf( stderr, "\tcontrols:\n" );
+ for ( i = 0; ctrls[ i ] != NULL; i++ ) {
+ fprintf( stderr, "\t\t%s\n", ctrls[ i ]->ldctl_oid );
+ }
+ ldap_controls_free( ctrls );
+ ctrls = NULL;
+ }
+
+ if ( err == LDAP_REFERRAL ) {
+ char **refs = NULL;
+
+ ldap_get_option( ld, LDAP_OPT_REFERRAL_URLS, (void *)&refs );
+
+ if ( refs ) {
+ int i;
+
+ fprintf( stderr, "\treferral:\n" );
+ for ( i = 0; refs[ i ] != NULL; i++ ) {
+ fprintf( stderr, "\t\t%s\n", refs[ i ] );
+ }
+
+ ber_memvfree( (void **)refs );
+ }
+ }
+}
+
+void
+tester_perror( const char *fname, const char *msg )
+{
+ int save_errno = errno;
+ char buf[ BUFSIZ ];
+
+ fprintf( stderr, "%s: %s: (%d) %s %s\n",
+ progname, fname, save_errno,
+ AC_STRERROR_R( save_errno, buf, sizeof( buf ) ),
+ msg ? msg : "" );
+}
+
+int
+tester_config_opt( struct tester_conn_args *config, char opt, char *optarg )
+{
+ switch ( opt ) {
+ case 'C':
+ config->chaserefs++;
+ break;
+
+ case 'D':
+ config->binddn = optarg;
+ break;
+
+ case 'd':
+ {
+ if ( lutil_atoi( &debug, optarg ) != 0 ) {
+ return -1;
+ }
+
+ if ( ber_set_option( NULL, LBER_OPT_DEBUG_LEVEL, &debug )
+ != LBER_OPT_SUCCESS )
+ {
+ fprintf( stderr,
+ "Could not set LBER_OPT_DEBUG_LEVEL %d\n", debug );
+ }
+
+ if ( ldap_set_option( NULL, LDAP_OPT_DEBUG_LEVEL, &debug )
+ != LDAP_OPT_SUCCESS )
+ {
+ fprintf( stderr,
+ "Could not set LDAP_OPT_DEBUG_LEVEL %d\n", debug );
+ }
+ break;
+ }
+
+ case 'H':
+ config->uri = optarg;
+ break;
+
+ case 'i':
+ tester_ignore_str2errlist( optarg );
+ break;
+
+ case 'L':
+ if ( lutil_atoi( &config->outerloops, optarg ) != 0 ) {
+ return -1;
+ }
+ break;
+
+ case 'l':
+ if ( lutil_atoi( &config->loops, optarg ) != 0 ) {
+ return -1;
+ }
+ break;
+
+#ifdef HAVE_CYRUS_SASL
+ case 'O':
+ if ( config->secprops != NULL ) {
+ return -1;
+ }
+ if ( config->authmethod != -1 && config->authmethod != LDAP_AUTH_SASL ) {
+ return -1;
+ }
+ config->authmethod = LDAP_AUTH_SASL;
+ config->secprops = optarg;
+ break;
+
+ case 'R':
+ if ( config->realm != NULL ) {
+ return -1;
+ }
+ if ( config->authmethod != -1 && config->authmethod != LDAP_AUTH_SASL ) {
+ return -1;
+ }
+ config->authmethod = LDAP_AUTH_SASL;
+ config->realm = optarg;
+ break;
+
+ case 'U':
+ if ( config->authc_id != NULL ) {
+ return -1;
+ }
+ if ( config->authmethod != -1 && config->authmethod != LDAP_AUTH_SASL ) {
+ return -1;
+ }
+ config->authmethod = LDAP_AUTH_SASL;
+ config->authc_id = optarg;
+ break;
+
+ case 'X':
+ if ( config->authz_id != NULL ) {
+ return -1;
+ }
+ if ( config->authmethod != -1 && config->authmethod != LDAP_AUTH_SASL ) {
+ return -1;
+ }
+ config->authmethod = LDAP_AUTH_SASL;
+ config->authz_id = optarg;
+ break;
+
+ case 'Y':
+ if ( config->mech != NULL ) {
+ return -1;
+ }
+ if ( config->authmethod != -1 && config->authmethod != LDAP_AUTH_SASL ) {
+ return -1;
+ }
+ config->authmethod = LDAP_AUTH_SASL;
+ config->mech = optarg;
+ break;
+#endif
+
+ case 'r':
+ if ( lutil_atoi( &config->retries, optarg ) != 0 ) {
+ return -1;
+ }
+ break;
+
+ case 't':
+ if ( lutil_atoi( &config->delay, optarg ) != 0 ) {
+ return -1;
+ }
+ break;
+
+ case 'w':
+ config->pass.bv_val = strdup( optarg );
+ config->pass.bv_len = strlen( optarg );
+ memset( optarg, '*', config->pass.bv_len );
+ break;
+
+ case 'x':
+ if ( config->authmethod != -1 && config->authmethod != LDAP_AUTH_SIMPLE ) {
+ return -1;
+ }
+ config->authmethod = LDAP_AUTH_SIMPLE;
+ break;
+
+ default:
+ return -1;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+void
+tester_config_finish( struct tester_conn_args *config )
+{
+ if ( config->authmethod == -1 ) {
+#ifdef HAVE_CYRUS_SASL
+ if ( config->binddn != NULL ) {
+ config->authmethod = LDAP_AUTH_SIMPLE;
+ } else {
+ config->authmethod = LDAP_AUTH_SASL;
+ }
+#else
+ config->authmethod = LDAP_AUTH_SIMPLE;
+#endif
+ }
+
+#ifdef HAVE_CYRUS_SASL
+ if ( config->authmethod == LDAP_AUTH_SASL ) {
+ config->defaults = lutil_sasl_defaults( NULL,
+ config->mech,
+ config->realm,
+ config->authc_id,
+ config->pass.bv_val,
+ config->authz_id );
+
+ if ( config->defaults == NULL ) {
+ tester_error( "unable to prepare SASL defaults" );
+ exit( EXIT_FAILURE );
+ }
+ }
+#endif
+}
+
+void
+tester_init_ld( LDAP **ldp, struct tester_conn_args *config, int flags )
+{
+ LDAP *ld;
+ int rc, do_retry = config->retries;
+ int version = LDAP_VERSION3;
+
+retry:;
+ ldap_initialize( &ld, config->uri );
+ if ( ld == NULL ) {
+ tester_perror( "ldap_initialize", NULL );
+ exit( EXIT_FAILURE );
+ }
+
+ (void) ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version );
+ (void) ldap_set_option( ld, LDAP_OPT_REFERRALS,
+ config->chaserefs ? LDAP_OPT_ON: LDAP_OPT_OFF );
+
+ if ( !( flags & TESTER_INIT_ONLY ) ) {
+ if ( config->authmethod == LDAP_AUTH_SASL ) {
+#ifdef HAVE_CYRUS_SASL
+ if ( config->secprops != NULL ) {
+ rc = ldap_set_option( ld,
+ LDAP_OPT_X_SASL_SECPROPS, config->secprops );
+
+ if ( rc != LDAP_OPT_SUCCESS ) {
+ tester_ldap_error( ld, "ldap_set_option(SECPROPS)", NULL );
+ ldap_unbind_ext( ld, NULL, NULL );
+ exit( EXIT_FAILURE );
+ }
+ }
+
+ rc = ldap_sasl_interactive_bind_s( ld,
+ config->binddn,
+ config->mech,
+ NULL, NULL,
+ LDAP_SASL_QUIET,
+ lutil_sasl_interact,
+ config->defaults );
+#else /* HAVE_CYRUS_SASL */
+ /* caller shouldn't have allowed this */
+ assert(0);
+#endif
+ } else if ( config->authmethod == LDAP_AUTH_SIMPLE ) {
+ rc = ldap_sasl_bind_s( ld,
+ config->binddn, LDAP_SASL_SIMPLE,
+ &config->pass, NULL, NULL, NULL );
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ tester_ldap_error( ld, "ldap_sasl_bind_s", NULL );
+ ldap_unbind_ext( ld, NULL, NULL );
+ ld = NULL;
+ switch ( rc ) {
+ case LDAP_BUSY:
+ case LDAP_UNAVAILABLE:
+ if ( do_retry > 0 ) {
+ do_retry--;
+ if ( config->delay > 0 ) {
+ sleep( config->delay );
+ }
+ goto retry;
+ }
+ }
+ if ( !( flags & TESTER_INIT_NOEXIT ))
+ exit( EXIT_FAILURE );
+ }
+ }
+
+ *ldp = ld;
+}
+
+void
+tester_error( const char *msg )
+{
+ fprintf( stderr, "%s: %s\n", progname, msg );
+}
diff --git a/tests/progs/slapd-common.h b/tests/progs/slapd-common.h
new file mode 100644
index 0000000..44d4755
--- /dev/null
+++ b/tests/progs/slapd-common.h
@@ -0,0 +1,92 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2024 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 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 Howard Chu for inclusion
+ * in OpenLDAP Software.
+ */
+
+#ifndef SLAPD_COMMON_H
+#define SLAPD_COMMON_H
+
+typedef enum {
+ TESTER_TESTER,
+ TESTER_ADDEL,
+ TESTER_BIND,
+ TESTER_MODIFY,
+ TESTER_MODRDN,
+ TESTER_READ,
+ TESTER_SEARCH,
+ TESTER_LAST
+} tester_t;
+
+extern struct tester_conn_args * tester_init( const char *pname, tester_t ptype );
+extern char * tester_uri( char *uri );
+extern void tester_error( const char *msg );
+extern void tester_perror( const char *fname, const char *msg );
+extern void tester_ldap_error( LDAP *ld, const char *fname, const char *msg );
+extern int tester_ignore_str2errlist( const char *err );
+extern int tester_ignore_err( int err );
+
+struct tester_conn_args {
+ char *uri;
+
+ int outerloops;
+ int loops;
+ int retries;
+ int delay;
+
+ int chaserefs;
+
+ int authmethod;
+
+ char *binddn;
+ struct berval pass;
+
+#ifdef HAVE_CYRUS_SASL
+ char *mech;
+ char *realm;
+ char *authz_id;
+ char *authc_id;
+ char *secprops;
+ void *defaults;
+#endif
+};
+
+#define TESTER_INIT_ONLY (1 << 0)
+#define TESTER_INIT_NOEXIT (1 << 1)
+#define TESTER_COMMON_OPTS "CD:d:H:L:l:i:O:R:U:X:Y:r:t:w:x"
+#define TESTER_COMMON_HELP \
+ "[-C] " \
+ "[-D <dn> [-w <passwd>]] " \
+ "[-d <level>] " \
+ "[-H <uri>]" \
+ "[-i <ignore>] " \
+ "[-l <loops>] " \
+ "[-L <outerloops>] " \
+ "[-r <maxretries>] " \
+ "[-t <delay>] " \
+ "[-O <SASL secprops>] " \
+ "[-R <SASL realm>] " \
+ "[-U <SASL authcid> [-X <SASL authzid>]] " \
+ "[-x | -Y <SASL mech>] "
+
+extern int tester_config_opt( struct tester_conn_args *config, char opt, char *optarg );
+extern void tester_config_finish( struct tester_conn_args *config );
+extern void tester_init_ld( LDAP **ldp, struct tester_conn_args *conf, int flags );
+
+extern pid_t pid;
+extern int debug;
+
+#endif /* SLAPD_COMMON_H */
diff --git a/tests/progs/slapd-modify.c b/tests/progs/slapd-modify.c
new file mode 100644
index 0000000..d556765
--- /dev/null
+++ b/tests/progs/slapd-modify.c
@@ -0,0 +1,225 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2024 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 file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include "ac/stdlib.h"
+
+#include "ac/ctype.h"
+#include "ac/param.h"
+#include "ac/socket.h"
+#include "ac/string.h"
+#include "ac/unistd.h"
+#include "ac/wait.h"
+
+#include "ldap.h"
+#include "lutil.h"
+
+#include "slapd-common.h"
+
+#define LOOPS 100
+
+static void
+do_modify( struct tester_conn_args *config, char *entry,
+ char *attr, char *value, int friendly );
+
+
+static void
+usage( char *name, int opt )
+{
+ if ( opt ) {
+ fprintf( stderr, "%s: unable to handle option \'%c\'\n\n",
+ name, opt );
+ }
+
+ fprintf( stderr, "usage: %s " TESTER_COMMON_HELP
+ "-a <attr:val> "
+ "-e <entry> "
+ "[-F]\n",
+ name );
+ exit( EXIT_FAILURE );
+}
+
+int
+main( int argc, char **argv )
+{
+ int i;
+ char *entry = NULL;
+ char *ava = NULL;
+ char *value = NULL;
+ int friendly = 0;
+ struct tester_conn_args *config;
+
+ config = tester_init( "slapd-modify", TESTER_MODIFY );
+
+ while ( ( i = getopt( argc, argv, TESTER_COMMON_OPTS "a:e:F" ) ) != EOF )
+ {
+ switch ( i ) {
+ case 'F':
+ friendly++;
+ break;
+
+ case 'i':
+ /* ignored (!) by now */
+ break;
+
+ case 'e': /* entry to modify */
+ entry = optarg;
+ break;
+
+ case 'a':
+ ava = optarg;
+ break;
+
+ default:
+ if ( tester_config_opt( config, i, optarg ) == LDAP_SUCCESS ) {
+ break;
+ }
+ usage( argv[0], i );
+ break;
+ }
+ }
+
+ if (( entry == NULL ) || ( ava == NULL ))
+ usage( argv[0], 0 );
+
+ if ( *entry == '\0' ) {
+
+ fprintf( stderr, "%s: invalid EMPTY entry DN.\n",
+ argv[0] );
+ exit( EXIT_FAILURE );
+
+ }
+ if ( *ava == '\0' ) {
+ fprintf( stderr, "%s: invalid EMPTY AVA.\n",
+ argv[0] );
+ exit( EXIT_FAILURE );
+ }
+
+ if ( !( value = strchr( ava, ':' ))) {
+ fprintf( stderr, "%s: invalid AVA.\n",
+ argv[0] );
+ exit( EXIT_FAILURE );
+ }
+ *value++ = '\0';
+ while ( *value && isspace( (unsigned char) *value ))
+ value++;
+
+ tester_config_finish( config );
+
+ for ( i = 0; i < config->outerloops; i++ ) {
+ do_modify( config, entry, ava, value, friendly );
+ }
+
+ exit( EXIT_SUCCESS );
+}
+
+
+static void
+do_modify( struct tester_conn_args *config,
+ char *entry, char* attr, char* value, int friendly )
+{
+ LDAP *ld = NULL;
+ int i = 0, do_retry = config->retries;
+ int rc = LDAP_SUCCESS;
+
+ struct ldapmod mod;
+ struct ldapmod *mods[2];
+ char *values[2];
+
+ values[0] = value;
+ values[1] = NULL;
+ mod.mod_op = LDAP_MOD_ADD;
+ mod.mod_type = attr;
+ mod.mod_values = values;
+ mods[0] = &mod;
+ mods[1] = NULL;
+
+retry:;
+ if ( ld == NULL ) {
+ tester_init_ld( &ld, config, 0 );
+ }
+
+ if ( do_retry == config->retries ) {
+ fprintf( stderr, "PID=%ld - Modify(%d): entry=\"%s\".\n",
+ (long) pid, config->loops, entry );
+ }
+
+ for ( ; i < config->loops; i++ ) {
+ mod.mod_op = LDAP_MOD_ADD;
+ rc = ldap_modify_ext_s( ld, entry, mods, NULL, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ tester_ldap_error( ld, "ldap_modify_ext_s", NULL );
+ switch ( rc ) {
+ case LDAP_TYPE_OR_VALUE_EXISTS:
+ /* NOTE: this likely means
+ * the second modify failed
+ * during the previous round... */
+ if ( !friendly ) {
+ goto done;
+ }
+ break;
+
+ case LDAP_BUSY:
+ case LDAP_UNAVAILABLE:
+ if ( do_retry > 0 ) {
+ do_retry--;
+ goto retry;
+ }
+ /* fall thru */
+
+ default:
+ goto done;
+ }
+ }
+
+ mod.mod_op = LDAP_MOD_DELETE;
+ rc = ldap_modify_ext_s( ld, entry, mods, NULL, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ tester_ldap_error( ld, "ldap_modify_ext_s", NULL );
+ switch ( rc ) {
+ case LDAP_NO_SUCH_ATTRIBUTE:
+ /* NOTE: this likely means
+ * the first modify failed
+ * during the previous round... */
+ if ( !friendly ) {
+ goto done;
+ }
+ break;
+
+ case LDAP_BUSY:
+ case LDAP_UNAVAILABLE:
+ if ( do_retry > 0 ) {
+ do_retry--;
+ goto retry;
+ }
+ /* fall thru */
+
+ default:
+ goto done;
+ }
+ }
+
+ }
+
+done:;
+ fprintf( stderr, " PID=%ld - Modify done (%d).\n", (long) pid, rc );
+
+ ldap_unbind_ext( ld, NULL, NULL );
+}
+
+
diff --git a/tests/progs/slapd-modrdn.c b/tests/progs/slapd-modrdn.c
new file mode 100644
index 0000000..adf6a0c
--- /dev/null
+++ b/tests/progs/slapd-modrdn.c
@@ -0,0 +1,229 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2024 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 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 Howard Chu, based in part
+ * on other OpenLDAP test tools, for inclusion in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include "ac/stdlib.h"
+
+#include "ac/ctype.h"
+#include "ac/param.h"
+#include "ac/socket.h"
+#include "ac/string.h"
+#include "ac/unistd.h"
+#include "ac/wait.h"
+
+#include "ldap.h"
+#include "lutil.h"
+
+#include "slapd-common.h"
+
+#define LOOPS 100
+#define RETRIES 0
+
+static void
+do_modrdn( struct tester_conn_args *config,
+ char *entry, int friendly );
+
+static void
+usage( char *name, char opt )
+{
+ if ( opt ) {
+ fprintf( stderr, "%s: unable to handle option \'%c\'\n\n",
+ name, opt );
+ }
+
+ fprintf( stderr, "usage: %s " TESTER_COMMON_HELP
+ "-e <entry> "
+ "[-F]\n",
+ name );
+ exit( EXIT_FAILURE );
+}
+
+int
+main( int argc, char **argv )
+{
+ int i;
+ char *entry = NULL;
+ int friendly = 0;
+ struct tester_conn_args *config;
+
+ config = tester_init( "slapd-modrdn", TESTER_MODRDN );
+
+ while ( ( i = getopt( argc, argv, TESTER_COMMON_OPTS "e:F" ) ) != EOF )
+ {
+ switch ( i ) {
+ case 'F':
+ friendly++;
+ break;
+
+ case 'i':
+ /* ignored (!) by now */
+ break;
+
+ case 'e': /* entry to rename */
+ entry = optarg;
+ break;
+
+ default:
+ if ( tester_config_opt( config, i, optarg ) == LDAP_SUCCESS ) {
+ break;
+ }
+ usage( argv[0], i );
+ break;
+ }
+ }
+
+ if ( entry == NULL )
+ usage( argv[0], 0 );
+
+ if ( *entry == '\0' ) {
+
+ fprintf( stderr, "%s: invalid EMPTY entry DN.\n",
+ argv[0] );
+ exit( EXIT_FAILURE );
+
+ }
+
+ tester_config_finish( config );
+
+ for ( i = 0; i < config->outerloops; i++ ) {
+ do_modrdn( config, entry, friendly );
+ }
+
+ exit( EXIT_SUCCESS );
+}
+
+
+static void
+do_modrdn( struct tester_conn_args *config,
+ char *entry, int friendly )
+{
+ LDAP *ld = NULL;
+ int i, do_retry = config->retries;
+ char *DNs[2];
+ char *rdns[2];
+ int rc = LDAP_SUCCESS;
+ char *p1, *p2;
+
+ DNs[0] = entry;
+ DNs[1] = strdup( entry );
+ if ( DNs[1] == NULL ) {
+ tester_error( "strdup failed" );
+ exit( EXIT_FAILURE );
+ }
+
+ /* reverse the RDN, make new DN */
+ p1 = strchr( entry, '=' ) + 1;
+ p2 = strchr( p1, ',' );
+
+ *p2 = '\0';
+ rdns[1] = strdup( entry );
+ if ( rdns[1] == NULL ) {
+ tester_error( "strdup failed" );
+ exit( EXIT_FAILURE );
+ }
+ *p2-- = ',';
+
+ for (i = p1 - entry;p2 >= p1;)
+ DNs[1][i++] = *p2--;
+
+ DNs[1][i] = '\0';
+ rdns[0] = strdup( DNs[1] );
+ if ( rdns[0] == NULL ) {
+ tester_error( "strdup failed" );
+ exit( EXIT_FAILURE );
+ }
+ DNs[1][i] = ',';
+
+ i = 0;
+
+retry:;
+ if ( ld == NULL ) {
+ tester_init_ld( &ld, config, 0 );
+ }
+
+ if ( do_retry == config->retries ) {
+ fprintf( stderr, "PID=%ld - Modrdn(%d): entry=\"%s\".\n",
+ (long) pid, config->loops, entry );
+ }
+
+ for ( ; i < config->loops; i++ ) {
+ rc = ldap_rename_s( ld, DNs[0], rdns[0], NULL, 0, NULL, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ tester_ldap_error( ld, "ldap_rename_s", NULL );
+ switch ( rc ) {
+ case LDAP_NO_SUCH_OBJECT:
+ /* NOTE: this likely means
+ * the second modrdn failed
+ * during the previous round... */
+ if ( !friendly ) {
+ goto done;
+ }
+ break;
+
+ case LDAP_BUSY:
+ case LDAP_UNAVAILABLE:
+ if ( do_retry > 0 ) {
+ do_retry--;
+ goto retry;
+ }
+ /* fall thru */
+
+ default:
+ goto done;
+ }
+ }
+ rc = ldap_rename_s( ld, DNs[1], rdns[1], NULL, 1, NULL, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ tester_ldap_error( ld, "ldap_rename_s", NULL );
+ switch ( rc ) {
+ case LDAP_NO_SUCH_OBJECT:
+ /* NOTE: this likely means
+ * the first modrdn failed
+ * during the previous round... */
+ if ( !friendly ) {
+ goto done;
+ }
+ break;
+
+ case LDAP_BUSY:
+ case LDAP_UNAVAILABLE:
+ if ( do_retry > 0 ) {
+ do_retry--;
+ goto retry;
+ }
+ /* fall thru */
+
+ default:
+ goto done;
+ }
+ }
+ }
+
+done:;
+ fprintf( stderr, " PID=%ld - Modrdn done (%d).\n", (long) pid, rc );
+
+ ldap_unbind_ext( ld, NULL, NULL );
+
+ free( DNs[1] );
+ free( rdns[0] );
+ free( rdns[1] );
+}
diff --git a/tests/progs/slapd-mtread.c b/tests/progs/slapd-mtread.c
new file mode 100644
index 0000000..aa58ee5
--- /dev/null
+++ b/tests/progs/slapd-mtread.c
@@ -0,0 +1,724 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2024 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 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 Kurt Spanier for inclusion
+ * in OpenLDAP Software.
+ */
+
+/*
+ * This tool is a MT reader. It behaves like slapd-read however
+ * with one or more threads simultaneously using the same connection.
+ * If -M is enabled, then M threads will also perform write operations.
+ */
+
+#include "portable.h"
+
+/* Requires libldap with threads */
+#ifndef NO_THREADS
+
+#include <stdio.h>
+#include "ldap_pvt_thread.h"
+
+#include "ac/stdlib.h"
+
+#include "ac/ctype.h"
+#include "ac/param.h"
+#include "ac/socket.h"
+#include "ac/string.h"
+#include "ac/unistd.h"
+#include "ac/wait.h"
+
+#include "ldap.h"
+#include "lutil.h"
+
+#include "ldap_pvt.h"
+
+#include "slapd-common.h"
+
+#define MAXCONN 512
+#define LOOPS 100
+#define RETRIES 0
+#define DEFAULT_BASE "ou=people,dc=example,dc=com"
+
+static void
+do_read( LDAP *ld, char *entry,
+ char **attrs, int noattrs, int nobind, int maxloop,
+ int force, int idx );
+
+static void
+do_random( LDAP *ld,
+ char *sbase, char *filter, char **attrs, int noattrs, int nobind,
+ int force, int idx );
+
+static void
+do_random2( LDAP *ld,
+ char *sbase, char *filter, char **attrs, int noattrs, int nobind,
+ int force, int idx );
+
+static void *
+do_onethread( void *arg );
+
+static void *
+do_onerwthread( void *arg );
+
+#define MAX_THREAD 1024
+/* Use same array for readers and writers, offset writers by MAX_THREAD */
+int rt_pass[MAX_THREAD*2];
+int rt_fail[MAX_THREAD*2];
+int *rwt_pass = rt_pass + MAX_THREAD;
+int *rwt_fail = rt_fail + MAX_THREAD;
+ldap_pvt_thread_t rtid[MAX_THREAD*2], *rwtid = rtid + MAX_THREAD;
+
+/*
+ * Shared globals (command line args)
+ */
+LDAP *ld = NULL;
+struct tester_conn_args *config;
+char *entry = NULL;
+char *filter = NULL;
+int force = 0;
+char *srchattrs[] = { "1.1", NULL };
+char **attrs = srchattrs;
+int noattrs = 0;
+int nobind = 0;
+int threads = 1;
+int rwthreads = 0;
+int verbose = 0;
+
+int noconns = 1;
+LDAP **lds = NULL;
+
+static void
+thread_error(int idx, char *string)
+{
+ char thrstr[BUFSIZ];
+
+ snprintf(thrstr, BUFSIZ, "error on tidx: %d: %s", idx, string);
+ tester_error( thrstr );
+}
+
+static void
+thread_output(int idx, char *string)
+{
+ char thrstr[BUFSIZ];
+
+ snprintf(thrstr, BUFSIZ, "tidx: %d says: %s", idx, string);
+ tester_error( thrstr );
+}
+
+static void
+thread_verbose(int idx, char *string)
+{
+ char thrstr[BUFSIZ];
+
+ if (!verbose)
+ return;
+ snprintf(thrstr, BUFSIZ, "tidx: %d says: %s", idx, string);
+ tester_error( thrstr );
+}
+
+static void
+usage( char *name, char opt )
+{
+ if ( opt ) {
+ fprintf( stderr, "%s: unable to handle option \'%c\'\n\n",
+ name, opt );
+ }
+
+ fprintf( stderr, "usage: %s " TESTER_COMMON_HELP
+ "-e <entry> "
+ "[-A] "
+ "[-F] "
+ "[-N] "
+ "[-v] "
+ "[-c connections] "
+ "[-f filter] "
+ "[-m threads] "
+ "[-M threads] "
+ "[-T <attrs>] "
+ "[<attrs>] "
+ "\n",
+ name );
+ exit( EXIT_FAILURE );
+}
+
+int
+main( int argc, char **argv )
+{
+ int i;
+ char *uri = NULL;
+ char *manager = NULL;
+ struct berval passwd = { 0, NULL };
+ char outstr[BUFSIZ];
+ int ptpass;
+ int testfail = 0;
+
+ config = tester_init( "slapd-mtread", TESTER_READ );
+
+ /* by default, tolerate referrals and no such object */
+ tester_ignore_str2errlist( "REFERRAL,NO_SUCH_OBJECT" );
+
+ while ( (i = getopt( argc, argv, TESTER_COMMON_OPTS "Ac:e:Ff:M:m:NT:v" )) != EOF ) {
+ switch ( i ) {
+ case 'A':
+ noattrs++;
+ break;
+
+ case 'N':
+ nobind = TESTER_INIT_ONLY;
+ break;
+
+ case 'v':
+ verbose++;
+ break;
+
+ case 'c': /* the number of connections */
+ if ( lutil_atoi( &noconns, optarg ) != 0 ) {
+ usage( argv[0], i );
+ }
+ break;
+
+ case 'e': /* DN to search for */
+ entry = optarg;
+ break;
+
+ case 'f': /* the search request */
+ filter = optarg;
+ break;
+
+ case 'F':
+ force++;
+ break;
+
+ case 'M': /* the number of R/W threads */
+ if ( lutil_atoi( &rwthreads, optarg ) != 0 ) {
+ usage( argv[0], i );
+ }
+ if (rwthreads > MAX_THREAD)
+ rwthreads = MAX_THREAD;
+ break;
+
+ case 'm': /* the number of threads */
+ if ( lutil_atoi( &threads, optarg ) != 0 ) {
+ usage( argv[0], i );
+ }
+ if (threads > MAX_THREAD)
+ threads = MAX_THREAD;
+ break;
+
+ case 'T':
+ attrs = ldap_str2charray( optarg, "," );
+ if ( attrs == NULL ) {
+ usage( argv[0], i );
+ }
+ break;
+
+ default:
+ if ( tester_config_opt( config, i, optarg ) == LDAP_SUCCESS ) {
+ break;
+ }
+ usage( argv[0], i );
+ break;
+ }
+ }
+
+ if ( entry == NULL )
+ usage( argv[0], 0 );
+
+ if ( *entry == '\0' ) {
+ fprintf( stderr, "%s: invalid EMPTY entry DN.\n",
+ argv[0] );
+ exit( EXIT_FAILURE );
+ }
+
+ if ( argv[optind] != NULL ) {
+ attrs = &argv[optind];
+ }
+
+ if (noconns < 1)
+ noconns = 1;
+ if (noconns > MAXCONN)
+ noconns = MAXCONN;
+ lds = (LDAP **) calloc( sizeof(LDAP *), noconns);
+ if (lds == NULL) {
+ fprintf( stderr, "%s: Memory error: calloc noconns.\n",
+ argv[0] );
+ exit( EXIT_FAILURE );
+ }
+
+ tester_config_finish( config );
+ ldap_pvt_thread_initialize();
+
+ for (i = 0; i < noconns; i++) {
+ tester_init_ld( &lds[i], config, nobind );
+ }
+
+ snprintf(outstr, BUFSIZ, "MT Test Start: conns: %d (%s)", noconns, uri);
+ tester_error(outstr);
+ snprintf(outstr, BUFSIZ, "Threads: RO: %d RW: %d", threads, rwthreads);
+ tester_error(outstr);
+
+ /* Set up read only threads */
+ for ( i = 0; i < threads; i++ ) {
+ ldap_pvt_thread_create( &rtid[i], 0, do_onethread, &rtid[i]);
+ snprintf(outstr, BUFSIZ, "Created RO thread %d", i);
+ thread_verbose(-1, outstr);
+ }
+ /* Set up read/write threads */
+ for ( i = 0; i < rwthreads; i++ ) {
+ ldap_pvt_thread_create( &rwtid[i], 0, do_onerwthread, &rwtid[i]);
+ snprintf(outstr, BUFSIZ, "Created RW thread %d", i + MAX_THREAD);
+ thread_verbose(-1, outstr);
+ }
+
+ ptpass = config->outerloops * config->loops;
+
+ /* wait for read only threads to complete */
+ for ( i = 0; i < threads; i++ )
+ ldap_pvt_thread_join(rtid[i], NULL);
+ /* wait for read/write threads to complete */
+ for ( i = 0; i < rwthreads; i++ )
+ ldap_pvt_thread_join(rwtid[i], NULL);
+
+ for(i = 0; i < noconns; i++) {
+ if ( lds[i] != NULL ) {
+ ldap_unbind_ext( lds[i], NULL, NULL );
+ }
+ }
+ free( lds );
+
+ for ( i = 0; i < threads; i++ ) {
+ snprintf(outstr, BUFSIZ, "RO thread %d pass=%d fail=%d", i,
+ rt_pass[i], rt_fail[i]);
+ tester_error(outstr);
+ if (rt_fail[i] != 0 || rt_pass[i] != ptpass) {
+ snprintf(outstr, BUFSIZ, "FAIL RO thread %d", i);
+ tester_error(outstr);
+ testfail++;
+ }
+ }
+ for ( i = 0; i < rwthreads; i++ ) {
+ snprintf(outstr, BUFSIZ, "RW thread %d pass=%d fail=%d", i + MAX_THREAD,
+ rwt_pass[i], rwt_fail[i]);
+ tester_error(outstr);
+ if (rwt_fail[i] != 0 || rwt_pass[i] != ptpass) {
+ snprintf(outstr, BUFSIZ, "FAIL RW thread %d", i);
+ tester_error(outstr);
+ testfail++;
+ }
+ }
+ snprintf(outstr, BUFSIZ, "MT Test complete" );
+ tester_error(outstr);
+
+ if (testfail)
+ exit( EXIT_FAILURE );
+ exit( EXIT_SUCCESS );
+}
+
+static void *
+do_onethread( void *arg )
+{
+ int i, j, thisconn;
+ LDAP **mlds;
+ char thrstr[BUFSIZ];
+ int rc, refcnt = 0;
+ int idx = (ldap_pvt_thread_t *)arg - rtid;
+
+ mlds = (LDAP **) calloc( sizeof(LDAP *), noconns);
+ if (mlds == NULL) {
+ thread_error( idx, "Memory error: thread calloc for noconns" );
+ exit( EXIT_FAILURE );
+ }
+
+ for ( j = 0; j < config->outerloops; j++ ) {
+ for(i = 0; i < noconns; i++) {
+ mlds[i] = ldap_dup(lds[i]);
+ if (mlds[i] == NULL) {
+ thread_error( idx, "ldap_dup error" );
+ }
+ }
+ rc = ldap_get_option(mlds[0], LDAP_OPT_SESSION_REFCNT, &refcnt);
+ snprintf(thrstr, BUFSIZ,
+ "RO Thread conns: %d refcnt: %d (rc = %d)",
+ noconns, refcnt, rc);
+ thread_verbose(idx, thrstr);
+
+ thisconn = (idx + j) % noconns;
+ if (thisconn < 0 || thisconn >= noconns)
+ thisconn = 0;
+ if (mlds[thisconn] == NULL) {
+ thread_error( idx, "(failed to dup)");
+ tester_perror( "ldap_dup", "(failed to dup)" );
+ exit( EXIT_FAILURE );
+ }
+ snprintf(thrstr, BUFSIZ, "Using conn %d", thisconn);
+ thread_verbose(idx, thrstr);
+ if ( filter != NULL ) {
+ if (strchr(filter, '['))
+ do_random2( mlds[thisconn], entry, filter, attrs,
+ noattrs, nobind, force, idx );
+ else
+ do_random( mlds[thisconn], entry, filter, attrs,
+ noattrs, nobind, force, idx );
+
+ } else {
+ do_read( mlds[thisconn], entry, attrs, noattrs,
+ nobind, config->loops, force, idx );
+ }
+ for(i = 0; i < noconns; i++) {
+ (void) ldap_destroy(mlds[i]);
+ mlds[i] = NULL;
+ }
+ }
+ free( mlds );
+ return( NULL );
+}
+
+static void *
+do_onerwthread( void *arg )
+{
+ int i, j, thisconn;
+ LDAP **mlds, *ld;
+ char thrstr[BUFSIZ];
+ char dn[256], uids[32], cns[32], *base;
+ LDAPMod *attrp[5], attrs[4];
+ char *oc_vals[] = { "top", "OpenLDAPperson", NULL };
+ char *cn_vals[] = { NULL, NULL };
+ char *sn_vals[] = { NULL, NULL };
+ char *uid_vals[] = { NULL, NULL };
+ int ret;
+ int adds = 0;
+ int dels = 0;
+ int rc, refcnt = 0;
+ int idx = (ldap_pvt_thread_t *)arg - rtid;
+
+ mlds = (LDAP **) calloc( sizeof(LDAP *), noconns);
+ if (mlds == NULL) {
+ thread_error( idx, "Memory error: thread calloc for noconns" );
+ exit( EXIT_FAILURE );
+ }
+
+ snprintf(uids, sizeof(uids), "rwtest%04d", idx);
+ snprintf(cns, sizeof(cns), "rwtest%04d", idx);
+ /* add setup */
+ for (i = 0; i < 4; i++) {
+ attrp[i] = &attrs[i];
+ attrs[i].mod_op = 0;
+ }
+ attrp[4] = NULL;
+ attrs[0].mod_type = "objectClass";
+ attrs[0].mod_values = oc_vals;
+ attrs[1].mod_type = "cn";
+ attrs[1].mod_values = cn_vals;
+ cn_vals[0] = &cns[0];
+ attrs[2].mod_type = "sn";
+ attrs[2].mod_values = sn_vals;
+ sn_vals[0] = &cns[0];
+ attrs[3].mod_type = "uid";
+ attrs[3].mod_values = uid_vals;
+ uid_vals[0] = &uids[0];
+
+ for ( j = 0; j < config->outerloops; j++ ) {
+ for(i = 0; i < noconns; i++) {
+ mlds[i] = ldap_dup(lds[i]);
+ if (mlds[i] == NULL) {
+ thread_error( idx, "ldap_dup error" );
+ }
+ }
+ rc = ldap_get_option(mlds[0], LDAP_OPT_SESSION_REFCNT, &refcnt);
+ snprintf(thrstr, BUFSIZ,
+ "RW Thread conns: %d refcnt: %d (rc = %d)",
+ noconns, refcnt, rc);
+ thread_verbose(idx, thrstr);
+
+ thisconn = (idx + j) % noconns;
+ if (thisconn < 0 || thisconn >= noconns)
+ thisconn = 0;
+ if (mlds[thisconn] == NULL) {
+ thread_error( idx, "(failed to dup)");
+ tester_perror( "ldap_dup", "(failed to dup)" );
+ exit( EXIT_FAILURE );
+ }
+ snprintf(thrstr, BUFSIZ, "START RW Thread using conn %d", thisconn);
+ thread_verbose(idx, thrstr);
+
+ ld = mlds[thisconn];
+ if (entry != NULL)
+ base = entry;
+ else
+ base = DEFAULT_BASE;
+ snprintf(dn, 256, "cn=%s,%s", cns, base);
+
+ adds = 0;
+ dels = 0;
+ for (i = 0; i < config->loops; i++) {
+ ret = ldap_add_ext_s(ld, dn, &attrp[0], NULL, NULL);
+ if (ret == LDAP_SUCCESS) {
+ adds++;
+ ret = ldap_delete_ext_s(ld, dn, NULL, NULL);
+ if (ret == LDAP_SUCCESS) {
+ dels++;
+ rt_pass[idx]++;
+ } else {
+ thread_output(idx, ldap_err2string(ret));
+ rt_fail[idx]++;
+ }
+ } else {
+ thread_output(idx, ldap_err2string(ret));
+ rt_fail[idx]++;
+ }
+ }
+
+ snprintf(thrstr, BUFSIZ,
+ "INNER STOP RW Thread using conn %d (%d/%d)",
+ thisconn, adds, dels);
+ thread_verbose(idx, thrstr);
+
+ for(i = 0; i < noconns; i++) {
+ (void) ldap_destroy(mlds[i]);
+ mlds[i] = NULL;
+ }
+ }
+
+ free( mlds );
+ return( NULL );
+}
+
+static void
+do_random( LDAP *ld,
+ char *sbase, char *filter, char **srchattrs, int noattrs, int nobind,
+ int force, int idx )
+{
+ int i = 0, do_retry = config->retries;
+ char *attrs[ 2 ];
+ int rc = LDAP_SUCCESS;
+ int nvalues = 0;
+ char **values = NULL;
+ LDAPMessage *res = NULL, *e = NULL;
+ char thrstr[BUFSIZ];
+
+ attrs[ 0 ] = LDAP_NO_ATTRS;
+ attrs[ 1 ] = NULL;
+
+ snprintf( thrstr, BUFSIZ,
+ "Read(%d): base=\"%s\", filter=\"%s\".\n",
+ config->loops, sbase, filter );
+ thread_verbose( idx, thrstr );
+
+ rc = ldap_search_ext_s( ld, sbase, LDAP_SCOPE_SUBTREE,
+ filter, attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &res );
+ switch ( rc ) {
+ case LDAP_SIZELIMIT_EXCEEDED:
+ case LDAP_TIMELIMIT_EXCEEDED:
+ case LDAP_SUCCESS:
+ nvalues = ldap_count_entries( ld, res );
+ if ( nvalues == 0 ) {
+ if ( rc ) {
+ tester_ldap_error( ld, "ldap_search_ext_s", NULL );
+ }
+ ldap_msgfree( res );
+ break;
+ }
+
+ values = malloc( ( nvalues + 1 ) * sizeof( char * ) );
+ if (values == NULL) {
+ thread_error( idx, "(failed to malloc)");
+ exit( EXIT_FAILURE );
+ }
+ for ( i = 0, e = ldap_first_entry( ld, res ); e != NULL; i++, e = ldap_next_entry( ld, e ) )
+ {
+ values[ i ] = ldap_get_dn( ld, e );
+ }
+ values[ i ] = NULL;
+
+ ldap_msgfree( res );
+
+ if ( do_retry == config->retries ) {
+ snprintf( thrstr, BUFSIZ,
+ "Read base=\"%s\" filter=\"%s\" got %d values.\n",
+ sbase, filter, nvalues );
+ thread_verbose( idx, thrstr );
+ }
+
+ for ( i = 0; i < config->loops; i++ ) {
+ int r = ((double)nvalues)*rand()/(RAND_MAX + 1.0);
+
+ do_read( ld, values[ r ],
+ srchattrs, noattrs, nobind, 1, force, idx );
+ }
+ for( i = 0; i < nvalues; i++) {
+ if (values[i] != NULL)
+ ldap_memfree( values[i] );
+ }
+ free( values );
+ break;
+
+ default:
+ tester_ldap_error( ld, "ldap_search_ext_s", NULL );
+ ldap_msgfree( res );
+ break;
+ }
+
+ snprintf( thrstr, BUFSIZ, "Search done (%d).\n", rc );
+ thread_verbose( idx, thrstr );
+}
+
+/* substitute a generated int into the filter */
+static void
+do_random2( LDAP *ld,
+ char *sbase, char *filter, char **srchattrs, int noattrs, int nobind,
+ int force, int idx )
+{
+ int i = 0, do_retry = config->retries;
+ int rc = LDAP_SUCCESS;
+ int lo, hi, range;
+ int flen;
+ LDAPMessage *res = NULL;
+ char *ptr, *ftail;
+ char thrstr[BUFSIZ];
+ char fbuf[BUFSIZ];
+
+ snprintf( thrstr, BUFSIZ,
+ "Read(%d): base=\"%s\", filter=\"%s\".\n",
+ config->loops, sbase, filter );
+ thread_verbose( idx, thrstr );
+
+ ptr = strchr(filter, '[');
+ if (!ptr)
+ return;
+ ftail = strchr(filter, ']');
+ if (!ftail || ftail < ptr)
+ return;
+
+ sscanf(ptr, "[%d-%d]", &lo, &hi);
+ range = hi - lo + 1;
+
+ flen = ptr - filter;
+ ftail++;
+
+ for ( i = 0; i < config->loops; i++ ) {
+ int r = ((double)range)*rand()/(RAND_MAX + 1.0);
+ sprintf(fbuf, "%.*s%d%s", flen, filter, r, ftail);
+
+ rc = ldap_search_ext_s( ld, sbase, LDAP_SCOPE_SUBTREE,
+ fbuf, srchattrs, noattrs, NULL, NULL, NULL,
+ LDAP_NO_LIMIT, &res );
+ if ( res != NULL ) {
+ ldap_msgfree( res );
+ }
+ if ( rc == 0 ) {
+ rt_pass[idx]++;
+ } else {
+ int first = tester_ignore_err( rc );
+ char buf[ BUFSIZ ];
+
+ rt_fail[idx]++;
+ snprintf( buf, sizeof( buf ), "ldap_search_ext_s(%s)", entry );
+
+ /* if ignore.. */
+ if ( first ) {
+ /* only log if first occurrence */
+ if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) {
+ tester_ldap_error( ld, buf, NULL );
+ }
+ continue;
+ }
+
+ /* busy needs special handling */
+ tester_ldap_error( ld, buf, NULL );
+ if ( rc == LDAP_BUSY && do_retry > 0 ) {
+ do_retry--;
+ continue;
+ }
+ break;
+ }
+ }
+
+ snprintf( thrstr, BUFSIZ, "Search done (%d).\n", rc );
+ thread_verbose( idx, thrstr );
+}
+
+static void
+do_read( LDAP *ld, char *entry,
+ char **attrs, int noattrs, int nobind, int maxloop,
+ int force, int idx )
+{
+ int i = 0, do_retry = config->retries;
+ int rc = LDAP_SUCCESS;
+ char thrstr[BUFSIZ];
+
+retry:;
+ if ( do_retry == config->retries ) {
+ snprintf( thrstr, BUFSIZ, "Read(%d): entry=\"%s\".\n",
+ maxloop, entry );
+ thread_verbose( idx, thrstr );
+ }
+
+ snprintf(thrstr, BUFSIZ, "LD %p cnt: %d (retried %d) (%s)", \
+ (void *) ld, maxloop, (do_retry - config->retries), entry);
+ thread_verbose( idx, thrstr );
+
+ for ( ; i < maxloop; i++ ) {
+ LDAPMessage *res = NULL;
+
+ rc = ldap_search_ext_s( ld, entry, LDAP_SCOPE_BASE,
+ NULL, attrs, noattrs, NULL, NULL, NULL,
+ LDAP_NO_LIMIT, &res );
+ if ( res != NULL ) {
+ ldap_msgfree( res );
+ }
+
+ if ( rc == 0 ) {
+ rt_pass[idx]++;
+ } else {
+ int first = tester_ignore_err( rc );
+ char buf[ BUFSIZ ];
+
+ rt_fail[idx]++;
+ snprintf( buf, sizeof( buf ), "ldap_search_ext_s(%s)", entry );
+
+ /* if ignore.. */
+ if ( first ) {
+ /* only log if first occurrence */
+ if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) {
+ tester_ldap_error( ld, buf, NULL );
+ }
+ continue;
+ }
+
+ /* busy needs special handling */
+ tester_ldap_error( ld, buf, NULL );
+ if ( rc == LDAP_BUSY && do_retry > 0 ) {
+ do_retry--;
+ goto retry;
+ }
+ break;
+ }
+ }
+}
+
+#else /* NO_THREADS */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+main( int argc, char **argv )
+{
+ fprintf( stderr, "%s: not available when configured --without-threads\n", argv[0] );
+ exit( EXIT_FAILURE );
+}
+
+#endif /* NO_THREADS */
diff --git a/tests/progs/slapd-read.c b/tests/progs/slapd-read.c
new file mode 100644
index 0000000..a88baad
--- /dev/null
+++ b/tests/progs/slapd-read.c
@@ -0,0 +1,445 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2024 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 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 Kurt Spanier for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include "ac/stdlib.h"
+
+#include "ac/ctype.h"
+#include "ac/param.h"
+#include "ac/socket.h"
+#include "ac/string.h"
+#include "ac/unistd.h"
+#include "ac/wait.h"
+
+#include "ldap.h"
+#include "lutil.h"
+
+#include "ldap_pvt.h"
+
+#include "slapd-common.h"
+
+#define LOOPS 100
+#define RETRIES 0
+
+static void
+do_read( struct tester_conn_args *config, char *entry, LDAP **ld,
+ char **attrs, int noattrs, int nobind, int maxloop, int force );
+
+static void
+do_random( struct tester_conn_args *config, char *sbase,
+ char *filter, char **attrs, int noattrs, int nobind, int force );
+
+static void
+usage( char *name, int opt )
+{
+ if ( opt ) {
+ fprintf( stderr, "%s: unable to handle option \'%c\'\n\n",
+ name, opt );
+ }
+
+ fprintf( stderr, "usage: %s " TESTER_COMMON_HELP
+ "-e <entry> "
+ "[-A] "
+ "[-F] "
+ "[-N] "
+ "[-S[S[S]]] "
+ "[-f filter] "
+ "[-T <attrs>] "
+ "[<attrs>] "
+ "\n",
+ name );
+ exit( EXIT_FAILURE );
+}
+
+/* -S: just send requests without reading responses
+ * -SS: send all requests asynchronous and immediately start reading responses
+ * -SSS: send all requests asynchronous; then read responses
+ */
+static int swamp;
+
+int
+main( int argc, char **argv )
+{
+ int i;
+ char *entry = NULL;
+ char *filter = NULL;
+ int force = 0;
+ char *srchattrs[] = { "1.1", NULL };
+ char **attrs = srchattrs;
+ int noattrs = 0;
+ int nobind = 0;
+ struct tester_conn_args *config;
+
+ config = tester_init( "slapd-read", TESTER_READ );
+
+ /* by default, tolerate referrals and no such object */
+ tester_ignore_str2errlist( "REFERRAL,NO_SUCH_OBJECT" );
+
+ while ( (i = getopt( argc, argv, TESTER_COMMON_OPTS "Ae:Ff:NST:" )) != EOF ) {
+ switch ( i ) {
+ case 'A':
+ noattrs++;
+ break;
+
+ case 'N':
+ nobind = TESTER_INIT_ONLY;
+ break;
+
+ case 'e': /* DN to search for */
+ entry = optarg;
+ break;
+
+ case 'f': /* the search request */
+ filter = optarg;
+ break;
+
+ case 'F':
+ force++;
+ break;
+
+ case 'S':
+ swamp++;
+ break;
+
+ case 'T':
+ attrs = ldap_str2charray( optarg, "," );
+ if ( attrs == NULL ) {
+ usage( argv[0], i );
+ }
+ break;
+
+ default:
+ if ( tester_config_opt( config, i, optarg ) == LDAP_SUCCESS ) {
+ break;
+ }
+ usage( argv[0], i );
+ break;
+ }
+ }
+
+ if ( entry == NULL )
+ usage( argv[0], 0 );
+
+ if ( *entry == '\0' ) {
+ fprintf( stderr, "%s: invalid EMPTY entry DN.\n",
+ argv[0] );
+ exit( EXIT_FAILURE );
+ }
+
+ if ( argv[optind] != NULL ) {
+ attrs = &argv[optind];
+ }
+
+ tester_config_finish( config );
+
+ for ( i = 0; i < config->outerloops; i++ ) {
+ if ( filter != NULL ) {
+ do_random( config, entry, filter, attrs,
+ noattrs, nobind, force );
+
+ } else {
+ do_read( config, entry, NULL, attrs,
+ noattrs, nobind, config->loops, force );
+ }
+ }
+
+ exit( EXIT_SUCCESS );
+}
+
+static void
+do_random( struct tester_conn_args *config, char *sbase, char *filter,
+ char **srchattrs, int noattrs, int nobind, int force )
+{
+ LDAP *ld = NULL;
+ int i = 0, do_retry = config->retries;
+ char *attrs[ 2 ];
+ int rc = LDAP_SUCCESS;
+ int nvalues = 0;
+ char **values = NULL;
+ LDAPMessage *res = NULL, *e = NULL;
+
+ attrs[ 0 ] = LDAP_NO_ATTRS;
+ attrs[ 1 ] = NULL;
+
+ tester_init_ld( &ld, config, nobind );
+
+ if ( do_retry == config->retries ) {
+ fprintf( stderr, "PID=%ld - Read(%d): base=\"%s\", filter=\"%s\".\n",
+ (long) pid, config->loops, sbase, filter );
+ }
+
+ rc = ldap_search_ext_s( ld, sbase, LDAP_SCOPE_SUBTREE,
+ filter, attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &res );
+ switch ( rc ) {
+ case LDAP_SIZELIMIT_EXCEEDED:
+ case LDAP_TIMELIMIT_EXCEEDED:
+ case LDAP_SUCCESS:
+ nvalues = ldap_count_entries( ld, res );
+ if ( nvalues == 0 ) {
+ if ( rc ) {
+ tester_ldap_error( ld, "ldap_search_ext_s", NULL );
+ }
+ break;
+ }
+
+ values = malloc( ( nvalues + 1 ) * sizeof( char * ) );
+ if ( !values ) {
+ tester_error( "malloc failed" );
+ exit( EXIT_FAILURE );
+ }
+ for ( i = 0, e = ldap_first_entry( ld, res ); e != NULL; i++, e = ldap_next_entry( ld, e ) )
+ {
+ values[ i ] = ldap_get_dn( ld, e );
+ }
+ values[ i ] = NULL;
+
+ ldap_msgfree( res );
+
+ if ( do_retry == config->retries ) {
+ fprintf( stderr, " PID=%ld - Read base=\"%s\" filter=\"%s\" got %d values.\n",
+ (long) pid, sbase, filter, nvalues );
+ }
+
+ for ( i = 0; i < config->loops; i++ ) {
+#if 0 /* use high-order bits for better randomness (Numerical Recipes in "C") */
+ int r = rand() % nvalues;
+#endif
+ int r = ((double)nvalues)*rand()/(RAND_MAX + 1.0);
+
+ do_read( config, values[ r ], &ld,
+ srchattrs, noattrs, nobind, 1, force );
+ }
+ free( values );
+ break;
+
+ default:
+ tester_ldap_error( ld, "ldap_search_ext_s", NULL );
+ break;
+ }
+
+ fprintf( stderr, " PID=%ld - Read done (%d).\n", (long) pid, rc );
+
+ if ( ld != NULL ) {
+ ldap_unbind_ext( ld, NULL, NULL );
+ }
+}
+
+static void
+do_read( struct tester_conn_args *config, char *entry, LDAP **ldp,
+ char **attrs, int noattrs, int nobind, int maxloop, int force )
+{
+ LDAP *ld = ldp ? *ldp : NULL;
+ int i = 0, do_retry = config->retries;
+ int rc = LDAP_SUCCESS;
+ int *msgids = NULL, active = 0;
+
+ /* make room for msgid */
+ if ( swamp > 1 ) {
+ msgids = (int *)calloc( sizeof(int), maxloop );
+ if ( !msgids ) {
+ tester_error( "calloc failed" );
+ exit( EXIT_FAILURE );
+ }
+ }
+
+retry:;
+ if ( ld == NULL ) {
+ tester_init_ld( &ld, config, nobind );
+ }
+
+ if ( do_retry == config->retries ) {
+ fprintf( stderr, "PID=%ld - Read(%d): entry=\"%s\".\n",
+ (long) pid, maxloop, entry );
+ }
+
+ if ( swamp > 1 ) {
+ do {
+ LDAPMessage *res = NULL;
+ int j, msgid;
+
+ if ( i < maxloop ) {
+ rc = ldap_search_ext( ld, entry, LDAP_SCOPE_BASE,
+ NULL, attrs, noattrs, NULL, NULL,
+ NULL, LDAP_NO_LIMIT, &msgids[i] );
+
+ active++;
+#if 0
+ fprintf( stderr,
+ ">>> PID=%ld - Read maxloop=%d cnt=%d active=%d msgid=%d: "
+ "entry=\"%s\"\n",
+ (long) pid, maxloop, i, active, msgids[i],
+ entry );
+#endif
+ i++;
+
+ if ( rc ) {
+ char buf[BUFSIZ];
+ int first = tester_ignore_err( rc );
+ /* if ignore.. */
+ if ( first ) {
+ /* only log if first occurrence */
+ if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) {
+ tester_ldap_error( ld, "ldap_search_ext", NULL );
+ }
+ continue;
+ }
+
+ /* busy needs special handling */
+ snprintf( buf, sizeof( buf ), "entry=\"%s\"\n", entry );
+ tester_ldap_error( ld, "ldap_search_ext", buf );
+ if ( rc == LDAP_BUSY && do_retry > 0 ) {
+ ldap_unbind_ext( ld, NULL, NULL );
+ ld = NULL;
+ do_retry--;
+ goto retry;
+ }
+ break;
+ }
+
+ if ( swamp > 2 ) {
+ continue;
+ }
+ }
+
+ rc = ldap_result( ld, LDAP_RES_ANY, 0, NULL, &res );
+ switch ( rc ) {
+ case -1:
+ /* gone really bad */
+#if 0
+ fprintf( stderr,
+ ">>> PID=%ld - Read maxloop=%d cnt=%d active=%d: "
+ "entry=\"%s\" ldap_result()=%d\n",
+ (long) pid, maxloop, i, active, entry, rc );
+#endif
+ goto cleanup;
+
+ case 0:
+ /* timeout (impossible) */
+ break;
+
+ case LDAP_RES_SEARCH_ENTRY:
+ case LDAP_RES_SEARCH_REFERENCE:
+ /* ignore */
+ break;
+
+ case LDAP_RES_SEARCH_RESULT:
+ /* just remove, no error checking (TODO?) */
+ msgid = ldap_msgid( res );
+ ldap_parse_result( ld, res, &rc, NULL, NULL, NULL, NULL, 1 );
+ res = NULL;
+
+ /* linear search, bah */
+ for ( j = 0; j < i; j++ ) {
+ if ( msgids[ j ] == msgid ) {
+ msgids[ j ] = -1;
+ active--;
+#if 0
+ fprintf( stderr,
+ "<<< PID=%ld - ReadDone maxloop=%d cnt=%d active=%d msgid=%d: "
+ "entry=\"%s\"\n",
+ (long) pid, maxloop, j, active, msgid, entry );
+#endif
+ break;
+ }
+ }
+ break;
+
+ default:
+ /* other messages unexpected */
+ fprintf( stderr,
+ "### PID=%ld - Read(%d): "
+ "entry=\"%s\" attrs=%s%s. unexpected response tag=%d\n",
+ (long) pid, maxloop,
+ entry, attrs[0], attrs[1] ? " (more...)" : "", rc );
+ break;
+ }
+
+ if ( res != NULL ) {
+ ldap_msgfree( res );
+ }
+ } while ( i < maxloop || active > 0 );
+
+ } else {
+ for ( ; i < maxloop; i++ ) {
+ LDAPMessage *res = NULL;
+
+ if (swamp) {
+ int msgid;
+ rc = ldap_search_ext( ld, entry, LDAP_SCOPE_BASE,
+ NULL, attrs, noattrs, NULL, NULL,
+ NULL, LDAP_NO_LIMIT, &msgid );
+ if ( rc == LDAP_SUCCESS ) continue;
+ else break;
+ }
+
+ rc = ldap_search_ext_s( ld, entry, LDAP_SCOPE_BASE,
+ NULL, attrs, noattrs, NULL, NULL, NULL,
+ LDAP_NO_LIMIT, &res );
+ if ( res != NULL ) {
+ ldap_msgfree( res );
+ }
+
+ if ( rc ) {
+ int first = tester_ignore_err( rc );
+ char buf[ BUFSIZ ];
+
+ snprintf( buf, sizeof( buf ), "ldap_search_ext_s(%s)", entry );
+
+ /* if ignore.. */
+ if ( first ) {
+ /* only log if first occurrence */
+ if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) {
+ tester_ldap_error( ld, buf, NULL );
+ }
+ continue;
+ }
+
+ /* busy needs special handling */
+ tester_ldap_error( ld, buf, NULL );
+ if ( rc == LDAP_BUSY && do_retry > 0 ) {
+ ldap_unbind_ext( ld, NULL, NULL );
+ ld = NULL;
+ do_retry--;
+ goto retry;
+ }
+ break;
+ }
+ }
+ }
+
+cleanup:;
+ if ( msgids != NULL ) {
+ free( msgids );
+ }
+
+ if ( ldp != NULL ) {
+ *ldp = ld;
+
+ } else {
+ fprintf( stderr, " PID=%ld - Read done (%d).\n", (long) pid, rc );
+
+ if ( ld != NULL ) {
+ ldap_unbind_ext( ld, NULL, NULL );
+ }
+ }
+}
+
diff --git a/tests/progs/slapd-search.c b/tests/progs/slapd-search.c
new file mode 100644
index 0000000..d86afd9
--- /dev/null
+++ b/tests/progs/slapd-search.c
@@ -0,0 +1,493 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2024 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 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 Kurt Spanier for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include "ac/stdlib.h"
+
+#include "ac/ctype.h"
+#include "ac/param.h"
+#include "ac/socket.h"
+#include "ac/string.h"
+#include "ac/unistd.h"
+#include "ac/wait.h"
+
+#include "ldap.h"
+#include "lutil.h"
+#include "ldap_pvt.h"
+
+#include "slapd-common.h"
+
+#define LOOPS 100
+#define RETRIES 0
+
+static void
+do_search( struct tester_conn_args *config,
+ char *sbase, int scope, char *filter, LDAP **ldp,
+ char **attrs, int noattrs, int nobind,
+ int innerloop, int force );
+
+static void
+do_random( struct tester_conn_args *config,
+ char *sbase, int scope, char *filter, char *attr,
+ char **attrs, int noattrs, int nobind, int force );
+
+static void
+usage( char *name, char opt )
+{
+ if ( opt != '\0' ) {
+ fprintf( stderr, "unknown/incorrect option \"%c\"\n", opt );
+ }
+
+ fprintf( stderr, "usage: %s " TESTER_COMMON_HELP
+ "-b <searchbase> "
+ "-s <scope> "
+ "-f <searchfilter> "
+ "[-a <attr>] "
+ "[-A] "
+ "[-F] "
+ "[-N] "
+ "[-S[S[S]]] "
+ "[<attrs>] "
+ "\n",
+ name );
+ exit( EXIT_FAILURE );
+}
+
+/* -S: just send requests without reading responses
+ * -SS: send all requests asynchronous and immediately start reading responses
+ * -SSS: send all requests asynchronous; then read responses
+ */
+static int swamp;
+
+int
+main( int argc, char **argv )
+{
+ int i;
+ char *sbase = NULL;
+ int scope = LDAP_SCOPE_SUBTREE;
+ char *filter = NULL;
+ char *attr = NULL;
+ char *srchattrs[] = { "cn", "sn", NULL };
+ char **attrs = srchattrs;
+ int force = 0;
+ int noattrs = 0;
+ int nobind = 0;
+ struct tester_conn_args *config;
+
+ config = tester_init( "slapd-search", TESTER_SEARCH );
+
+ /* by default, tolerate referrals and no such object */
+ tester_ignore_str2errlist( "REFERRAL,NO_SUCH_OBJECT" );
+
+ while ( ( i = getopt( argc, argv, TESTER_COMMON_OPTS "Aa:b:f:FNSs:T:" ) ) != EOF )
+ {
+ switch ( i ) {
+ case 'A':
+ noattrs++;
+ break;
+
+ case 'N':
+ nobind = TESTER_INIT_ONLY;
+ break;
+
+ case 'a':
+ attr = optarg;
+ break;
+
+ case 'b': /* file with search base */
+ sbase = optarg;
+ break;
+
+ case 'f': /* the search request */
+ filter = optarg;
+ break;
+
+ case 'F':
+ force++;
+ break;
+
+ case 'T':
+ attrs = ldap_str2charray( optarg, "," );
+ if ( attrs == NULL ) {
+ usage( argv[0], i );
+ }
+ break;
+
+ case 'S':
+ swamp++;
+ break;
+
+ case 's':
+ scope = ldap_pvt_str2scope( optarg );
+ if ( scope == -1 ) {
+ usage( argv[0], i );
+ }
+ break;
+
+ default:
+ if ( tester_config_opt( config, i, optarg ) == LDAP_SUCCESS ) {
+ break;
+ }
+ usage( argv[0], i );
+ break;
+ }
+ }
+
+ if (( sbase == NULL ) || ( filter == NULL ))
+ usage( argv[0], 0 );
+
+ if ( *filter == '\0' ) {
+
+ fprintf( stderr, "%s: invalid EMPTY search filter.\n",
+ argv[0] );
+ exit( EXIT_FAILURE );
+
+ }
+
+ if ( argv[optind] != NULL ) {
+ attrs = &argv[optind];
+ }
+
+ tester_config_finish( config );
+
+ for ( i = 0; i < config->outerloops; i++ ) {
+ if ( attr != NULL ) {
+ do_random( config,
+ sbase, scope, filter, attr,
+ attrs, noattrs, nobind, force );
+
+ } else {
+ do_search( config, sbase, scope, filter,
+ NULL, attrs, noattrs, nobind,
+ config->loops, force );
+ }
+ }
+
+ exit( EXIT_SUCCESS );
+}
+
+
+static void
+do_random( struct tester_conn_args *config,
+ char *sbase, int scope, char *filter, char *attr,
+ char **srchattrs, int noattrs, int nobind, int force )
+{
+ LDAP *ld = NULL;
+ int i = 0, do_retry = config->retries;
+ char *attrs[ 2 ];
+ int rc = LDAP_SUCCESS;
+ int nvalues = 0;
+ char **values = NULL;
+ LDAPMessage *res = NULL, *e = NULL;
+
+ attrs[ 0 ] = attr;
+ attrs[ 1 ] = NULL;
+
+ tester_init_ld( &ld, config, nobind );
+
+ rc = ldap_search_ext_s( ld, sbase, LDAP_SCOPE_SUBTREE,
+ filter, attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &res );
+ switch ( rc ) {
+ case LDAP_SIZELIMIT_EXCEEDED:
+ case LDAP_TIMELIMIT_EXCEEDED:
+ case LDAP_SUCCESS:
+ if ( ldap_count_entries( ld, res ) == 0 ) {
+ if ( rc ) {
+ tester_ldap_error( ld, "ldap_search_ext_s", NULL );
+ }
+ ldap_msgfree( res );
+ break;
+ }
+
+ for ( e = ldap_first_entry( ld, res ); e != NULL; e = ldap_next_entry( ld, e ) )
+ {
+ struct berval **v = ldap_get_values_len( ld, e, attr );
+
+ if ( v != NULL ) {
+ int n = ldap_count_values_len( v );
+ int j;
+
+ values = realloc( values, ( nvalues + n + 1 )*sizeof( char * ) );
+ if ( !values ) {
+ tester_error( "realloc failed" );
+ exit( EXIT_FAILURE );
+ }
+ for ( j = 0; j < n; j++ ) {
+ values[ nvalues + j ] = strdup( v[ j ]->bv_val );
+ }
+ values[ nvalues + j ] = NULL;
+ nvalues += n;
+ ldap_value_free_len( v );
+ }
+ }
+
+ ldap_msgfree( res );
+
+ if ( !values ) {
+ fprintf( stderr, " PID=%ld - Search base=\"%s\" filter=\"%s\" got %d values.\n",
+ (long) pid, sbase, filter, nvalues );
+ exit(EXIT_FAILURE);
+ }
+
+ if ( do_retry == config->retries ) {
+ fprintf( stderr, " PID=%ld - Search base=\"%s\" filter=\"%s\" got %d values.\n",
+ (long) pid, sbase, filter, nvalues );
+ }
+
+ for ( i = 0; i < config->loops; i++ ) {
+ char buf[ BUFSIZ ];
+#if 0 /* use high-order bits for better randomness (Numerical Recipes in "C") */
+ int r = rand() % nvalues;
+#endif
+ int r = ((double)nvalues)*rand()/(RAND_MAX + 1.0);
+
+ snprintf( buf, sizeof( buf ), "(%s=%s)", attr, values[ r ] );
+
+ do_search( config,
+ sbase, scope, buf, &ld,
+ srchattrs, noattrs, nobind,
+ 1, force );
+ }
+ break;
+
+ default:
+ tester_ldap_error( ld, "ldap_search_ext_s", NULL );
+ ldap_msgfree( res );
+ break;
+ }
+
+ fprintf( stderr, " PID=%ld - Search done (%d).\n", (long) pid, rc );
+
+ if ( values ) {
+ for ( i = 0; i < nvalues; i++ ) {
+ free( values[i] );
+ }
+ free( values );
+ }
+
+ if ( ld != NULL ) {
+ ldap_unbind_ext( ld, NULL, NULL );
+ }
+}
+
+static void
+do_search( struct tester_conn_args *config,
+ char *sbase, int scope, char *filter, LDAP **ldp,
+ char **attrs, int noattrs, int nobind,
+ int innerloop, int force )
+{
+ LDAP *ld = ldp ? *ldp : NULL;
+ int i = 0, do_retry = config->retries;
+ int rc = LDAP_SUCCESS;
+ char buf[ BUFSIZ ];
+ int *msgids = NULL, active = 0;
+
+ /* make room for msgid */
+ if ( swamp > 1 ) {
+ msgids = (int *)calloc( sizeof(int), innerloop );
+ if ( !msgids ) {
+ tester_error( "calloc failed" );
+ exit( EXIT_FAILURE );
+ }
+ }
+
+retry:;
+ if ( ld == NULL ) {
+ fprintf( stderr,
+ "PID=%ld - Search(%d): "
+ "base=\"%s\" scope=%s filter=\"%s\" "
+ "attrs=%s%s.\n",
+ (long) pid, innerloop,
+ sbase, ldap_pvt_scope2str( scope ), filter,
+ attrs[0], attrs[1] ? " (more...)" : "" );
+
+ tester_init_ld( &ld, config, nobind );
+ }
+
+ if ( swamp > 1 ) {
+ do {
+ LDAPMessage *res = NULL;
+ int j, msgid;
+
+ if ( i < innerloop ) {
+ rc = ldap_search_ext( ld, sbase, scope,
+ filter, NULL, noattrs, NULL, NULL,
+ NULL, LDAP_NO_LIMIT, &msgids[i] );
+
+ active++;
+#if 0
+ fprintf( stderr,
+ ">>> PID=%ld - Search maxloop=%d cnt=%d active=%d msgid=%d: "
+ "base=\"%s\" scope=%s filter=\"%s\"\n",
+ (long) pid, innerloop, i, active, msgids[i],
+ sbase, ldap_pvt_scope2str( scope ), filter );
+#endif
+ i++;
+
+ if ( rc ) {
+ int first = tester_ignore_err( rc );
+ /* if ignore.. */
+ if ( first ) {
+ /* only log if first occurrence */
+ if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) {
+ tester_ldap_error( ld, "ldap_search_ext", NULL );
+ }
+ continue;
+ }
+
+ /* busy needs special handling */
+ snprintf( buf, sizeof( buf ),
+ "base=\"%s\" filter=\"%s\"\n",
+ sbase, filter );
+ tester_ldap_error( ld, "ldap_search_ext", buf );
+ if ( rc == LDAP_BUSY && do_retry > 0 ) {
+ ldap_unbind_ext( ld, NULL, NULL );
+ ld = NULL;
+ do_retry--;
+ goto retry;
+ }
+ break;
+ }
+
+ if ( swamp > 2 ) {
+ continue;
+ }
+ }
+
+ rc = ldap_result( ld, LDAP_RES_ANY, 0, NULL, &res );
+ switch ( rc ) {
+ case -1:
+ /* gone really bad */
+ goto cleanup;
+
+ case 0:
+ /* timeout (impossible) */
+ break;
+
+ case LDAP_RES_SEARCH_ENTRY:
+ case LDAP_RES_SEARCH_REFERENCE:
+ /* ignore */
+ break;
+
+ case LDAP_RES_SEARCH_RESULT:
+ /* just remove, no error checking (TODO?) */
+ msgid = ldap_msgid( res );
+ ldap_parse_result( ld, res, &rc, NULL, NULL, NULL, NULL, 1 );
+ res = NULL;
+
+ /* linear search, bah */
+ for ( j = 0; j < i; j++ ) {
+ if ( msgids[ j ] == msgid ) {
+ msgids[ j ] = -1;
+ active--;
+#if 0
+ fprintf( stderr,
+ "<<< PID=%ld - SearchDone maxloop=%d cnt=%d active=%d msgid=%d: "
+ "base=\"%s\" scope=%s filter=\"%s\"\n",
+ (long) pid, innerloop, j, active, msgid,
+ sbase, ldap_pvt_scope2str( scope ), filter );
+#endif
+ break;
+ }
+ }
+ break;
+
+ default:
+ /* other messages unexpected */
+ fprintf( stderr,
+ "### PID=%ld - Search(%d): "
+ "base=\"%s\" scope=%s filter=\"%s\" "
+ "attrs=%s%s. unexpected response tag=%d\n",
+ (long) pid, innerloop,
+ sbase, ldap_pvt_scope2str( scope ), filter,
+ attrs[0], attrs[1] ? " (more...)" : "", rc );
+ break;
+ }
+
+ if ( res != NULL ) {
+ ldap_msgfree( res );
+ }
+ } while ( i < innerloop || active > 0 );
+
+ } else {
+ for ( ; i < innerloop; i++ ) {
+ LDAPMessage *res = NULL;
+
+ if (swamp) {
+ int msgid;
+ rc = ldap_search_ext( ld, sbase, scope,
+ filter, NULL, noattrs, NULL, NULL,
+ NULL, LDAP_NO_LIMIT, &msgid );
+ if ( rc == LDAP_SUCCESS ) continue;
+ else break;
+ }
+
+ rc = ldap_search_ext_s( ld, sbase, scope,
+ filter, attrs, noattrs, NULL, NULL,
+ NULL, LDAP_NO_LIMIT, &res );
+ if ( res != NULL ) {
+ ldap_msgfree( res );
+ }
+
+ if ( rc ) {
+ int first = tester_ignore_err( rc );
+ /* if ignore.. */
+ if ( first ) {
+ /* only log if first occurrence */
+ if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) {
+ tester_ldap_error( ld, "ldap_search_ext_s", NULL );
+ }
+ continue;
+ }
+
+ /* busy needs special handling */
+ snprintf( buf, sizeof( buf ),
+ "base=\"%s\" filter=\"%s\"\n",
+ sbase, filter );
+ tester_ldap_error( ld, "ldap_search_ext_s", buf );
+ if ( rc == LDAP_BUSY && do_retry > 0 ) {
+ ldap_unbind_ext( ld, NULL, NULL );
+ ld = NULL;
+ do_retry--;
+ goto retry;
+ }
+ break;
+ }
+ }
+ }
+
+cleanup:;
+ if ( msgids != NULL ) {
+ free( msgids );
+ }
+
+ if ( ldp != NULL ) {
+ *ldp = ld;
+
+ } else {
+ fprintf( stderr, " PID=%ld - Search done (%d).\n", (long) pid, rc );
+
+ if ( ld != NULL ) {
+ ldap_unbind_ext( ld, NULL, NULL );
+ }
+ }
+}
diff --git a/tests/progs/slapd-tester.c b/tests/progs/slapd-tester.c
new file mode 100644
index 0000000..22ee87a
--- /dev/null
+++ b/tests/progs/slapd-tester.c
@@ -0,0 +1,1146 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2024 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 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 Kurt Spanier for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include "ac/stdlib.h"
+
+#include "ac/ctype.h"
+#include "ac/dirent.h"
+#include "ac/param.h"
+#include "ac/socket.h"
+#include "ac/string.h"
+#include "ac/unistd.h"
+#include "ac/wait.h"
+
+
+#include "ldap_defaults.h"
+#include "lutil.h"
+
+#include "ldap.h"
+#include "ldap_pvt.h"
+#include "lber_pvt.h"
+#include "slapd-common.h"
+
+#ifdef _WIN32
+#define EXE ".exe"
+#else
+#define EXE
+#endif
+
+#define SEARCHCMD "slapd-search" EXE
+#define READCMD "slapd-read" EXE
+#define ADDCMD "slapd-addel" EXE
+#define MODRDNCMD "slapd-modrdn" EXE
+#define MODIFYCMD "slapd-modify" EXE
+#define BINDCMD "slapd-bind" EXE
+#define MAXARGS 100
+#define MAXREQS 5000
+#define LOOPS 100
+#define OUTERLOOPS "1"
+#define RETRIES "0"
+
+#define TSEARCHFILE "do_search.0"
+#define TREADFILE "do_read.0"
+#define TADDFILE "do_add."
+#define TMODRDNFILE "do_modrdn.0"
+#define TMODIFYFILE "do_modify.0"
+#define TBINDFILE "do_bind.0"
+
+static char *get_file_name( char *dirname, char *filename );
+static int get_search_filters( char *filename, char *filters[], char *attrs[], char *bases[], LDAPURLDesc *luds[] );
+static int get_read_entries( char *filename, char *entries[], char *filters[] );
+static void fork_child( char *prog, char **args );
+static void wait4kids( int nkidval );
+
+static int maxkids = 20;
+static int nkids;
+
+#ifdef HAVE_WINSOCK
+static HANDLE *children;
+static char argbuf[BUFSIZ];
+#define ArgDup(x) strdup(strcat(strcat(strcpy(argbuf,"\""),x),"\""))
+#else
+#define ArgDup(x) strdup(x)
+#endif
+
+static void
+usage( char *name, char opt )
+{
+ if ( opt ) {
+ fprintf( stderr, "%s: unable to handle option \'%c\'\n\n",
+ name, opt );
+ }
+
+ fprintf( stderr,
+ "usage: %s "
+ "-H <uri> "
+ "-D <manager> "
+ "-w <passwd> "
+ "-d <datadir> "
+ "[-i <ignore>] "
+ "[-j <maxchild>] "
+ "[-l {<loops>|<type>=<loops>[,...]}] "
+ "[-L <outerloops>] "
+ "-P <progdir> "
+ "[-r <maxretries>] "
+ "[-t <delay>] "
+ "[-C] "
+ "[-F] "
+ "[-I] "
+ "[-N]\n",
+ name );
+ exit( EXIT_FAILURE );
+}
+
+int
+main( int argc, char **argv )
+{
+ int i, j;
+ char *uri = NULL;
+ char *manager = NULL;
+ char *passwd = NULL;
+ char *dirname = NULL;
+ char *progdir = NULL;
+ int loops = LOOPS;
+ char *outerloops = OUTERLOOPS;
+ char *retries = RETRIES;
+ char *delay = "0";
+ DIR *datadir;
+ struct dirent *file;
+ int friendly = 0;
+ int chaserefs = 0;
+ int noattrs = 0;
+ int nobind = 0;
+ int noinit = 1;
+ char *ignore = NULL;
+ /* search */
+ char *sfile = NULL;
+ char *sreqs[MAXREQS];
+ char *sattrs[MAXREQS];
+ char *sbase[MAXREQS];
+ LDAPURLDesc *slud[MAXREQS];
+ int snum = 0;
+ char *sargs[MAXARGS];
+ int sanum;
+ int sextra_args = 0;
+ char scmd[MAXPATHLEN];
+ int swamp = 0;
+ char swampopt[sizeof("-SSS")];
+ /* static so that its address can be used in initializer below. */
+ static char sloops[LDAP_PVT_INTTYPE_CHARS(unsigned long)];
+ /* read */
+ char *rfile = NULL;
+ char *rreqs[MAXREQS];
+ int rnum = 0;
+ char *rargs[MAXARGS];
+ char *rflts[MAXREQS];
+ int ranum;
+ int rextra_args = 0;
+ char rcmd[MAXPATHLEN];
+ static char rloops[LDAP_PVT_INTTYPE_CHARS(unsigned long)];
+ /* addel */
+ char *afiles[MAXREQS];
+ int anum = 0;
+ char *aargs[MAXARGS];
+ int aanum;
+ char acmd[MAXPATHLEN];
+ static char aloops[LDAP_PVT_INTTYPE_CHARS(unsigned long)];
+ /* modrdn */
+ char *nfile = NULL;
+ char *nreqs[MAXREQS];
+ int nnum = 0;
+ char *nargs[MAXARGS];
+ int nanum;
+ char ncmd[MAXPATHLEN];
+ static char nloops[LDAP_PVT_INTTYPE_CHARS(unsigned long)];
+ /* modify */
+ char *mfile = NULL;
+ char *mreqs[MAXREQS];
+ char *mdn[MAXREQS];
+ int mnum = 0;
+ char *margs[MAXARGS];
+ int manum;
+ char mcmd[MAXPATHLEN];
+ static char mloops[LDAP_PVT_INTTYPE_CHARS(unsigned long)];
+ /* bind */
+ char *bfile = NULL;
+ char *breqs[MAXREQS];
+ char *bcreds[MAXREQS];
+ char *battrs[MAXREQS];
+ int bnum = 0;
+ char *bargs[MAXARGS];
+ int banum;
+ char bcmd[MAXPATHLEN];
+ static char bloops[LDAP_PVT_INTTYPE_CHARS(unsigned long)];
+ char **bargs_extra = NULL;
+
+ char *friendlyOpt = NULL;
+ int pw_ask = 0;
+ char *pw_file = NULL;
+
+ /* extra action to do after bind... */
+ typedef struct extra_t {
+ char *action;
+ struct extra_t *next;
+ } extra_t;
+
+ extra_t *extra = NULL;
+ int nextra = 0;
+
+ tester_init( "slapd-tester", TESTER_TESTER );
+
+ sloops[0] = '\0';
+ rloops[0] = '\0';
+ aloops[0] = '\0';
+ nloops[0] = '\0';
+ mloops[0] = '\0';
+ bloops[0] = '\0';
+
+ while ( ( i = getopt( argc, argv, "AB:CD:d:FH:h:Ii:j:L:l:NP:p:r:St:Ww:y:" ) ) != EOF )
+ {
+ switch ( i ) {
+ case 'A':
+ noattrs++;
+ break;
+
+ case 'B': {
+ char **p,
+ **b = ldap_str2charray( optarg, "," );
+ extra_t **epp;
+
+ for ( epp = &extra; *epp; epp = &(*epp)->next )
+ ;
+
+ for ( p = b; p[0]; p++ ) {
+ *epp = calloc( 1, sizeof( extra_t ) );
+ (*epp)->action = p[0];
+ epp = &(*epp)->next;
+ nextra++;
+ }
+
+ ldap_memfree( b );
+ } break;
+
+ case 'C':
+ chaserefs++;
+ break;
+
+ case 'D': /* slapd manager */
+ manager = ArgDup( optarg );
+ break;
+
+ case 'd': /* data directory */
+ dirname = optarg;
+ break;
+
+ case 'F':
+ friendly++;
+ break;
+
+ case 'H': /* slapd uri */
+ uri = optarg;
+ break;
+
+ case 'I':
+ noinit = 0;
+ break;
+
+ case 'i':
+ ignore = optarg;
+ break;
+
+ case 'j': /* the number of parallel clients */
+ if ( lutil_atoi( &maxkids, optarg ) != 0 ) {
+ usage( argv[0], 'j' );
+ }
+ break;
+
+ case 'l': /* the number of loops per client */
+ if ( !isdigit( (unsigned char) optarg[0] ) ) {
+ char **p,
+ **l = ldap_str2charray( optarg, "," );
+
+ for ( p = l; p[0]; p++) {
+ struct {
+ struct berval type;
+ char *buf;
+ } types[] = {
+ { BER_BVC( "add=" ), aloops },
+ { BER_BVC( "bind=" ), bloops },
+ { BER_BVC( "modify=" ), mloops },
+ { BER_BVC( "modrdn=" ), nloops },
+ { BER_BVC( "read=" ), rloops },
+ { BER_BVC( "search=" ), sloops },
+ { BER_BVNULL, NULL }
+ };
+ int c, n;
+
+ for ( c = 0; types[c].type.bv_val; c++ ) {
+ if ( strncasecmp( p[0], types[c].type.bv_val, types[c].type.bv_len ) == 0 ) {
+ break;
+ }
+ }
+
+ if ( types[c].type.bv_val == NULL ) {
+ usage( argv[0], 'l' );
+ }
+
+ if ( lutil_atoi( &n, &p[0][types[c].type.bv_len] ) != 0 ) {
+ usage( argv[0], 'l' );
+ }
+
+ snprintf( types[c].buf, sizeof( aloops ), "%d", n );
+ }
+
+ ldap_charray_free( l );
+
+ } else if ( lutil_atoi( &loops, optarg ) != 0 ) {
+ usage( argv[0], 'l' );
+ }
+ break;
+
+ case 'L': /* the number of outerloops per client */
+ outerloops = optarg;
+ break;
+
+ case 'N':
+ nobind++;
+ break;
+
+ case 'P': /* prog directory */
+ progdir = optarg;
+ break;
+
+ case 'r': /* the number of retries in case of error */
+ retries = optarg;
+ break;
+
+ case 'S':
+ swamp++;
+ break;
+
+ case 't': /* the delay in seconds between each retry */
+ delay = optarg;
+ break;
+
+ case 'w': /* the managers passwd */
+ passwd = ArgDup( optarg );
+ memset( optarg, '*', strlen( optarg ) );
+ break;
+
+ case 'W':
+ pw_ask++;
+ break;
+
+ case 'y':
+ pw_file = optarg;
+ break;
+
+ default:
+ usage( argv[0], '\0' );
+ break;
+ }
+ }
+
+ if (( dirname == NULL ) || ( uri == NULL ) ||
+ ( manager == NULL ) || ( passwd == NULL ) || ( progdir == NULL ))
+ {
+ usage( argv[0], '\0' );
+ }
+
+#ifdef HAVE_WINSOCK
+ children = malloc( maxkids * sizeof(HANDLE) );
+#endif
+ /* get the file list */
+ if ( ( datadir = opendir( dirname )) == NULL ) {
+ fprintf( stderr, "%s: couldn't open data directory \"%s\".\n",
+ argv[0], dirname );
+ exit( EXIT_FAILURE );
+ }
+
+ /* look for search, read, modrdn, and add/delete files */
+ for ( file = readdir( datadir ); file; file = readdir( datadir )) {
+
+ if ( !strcasecmp( file->d_name, TSEARCHFILE )) {
+ sfile = get_file_name( dirname, file->d_name );
+ continue;
+ } else if ( !strcasecmp( file->d_name, TREADFILE )) {
+ rfile = get_file_name( dirname, file->d_name );
+ continue;
+ } else if ( !strcasecmp( file->d_name, TMODRDNFILE )) {
+ nfile = get_file_name( dirname, file->d_name );
+ continue;
+ } else if ( !strcasecmp( file->d_name, TMODIFYFILE )) {
+ mfile = get_file_name( dirname, file->d_name );
+ continue;
+ } else if ( !strncasecmp( file->d_name, TADDFILE, strlen( TADDFILE ))
+ && ( anum < MAXREQS )) {
+ afiles[anum++] = get_file_name( dirname, file->d_name );
+ continue;
+ } else if ( !strcasecmp( file->d_name, TBINDFILE )) {
+ bfile = get_file_name( dirname, file->d_name );
+ continue;
+ }
+ }
+
+ closedir( datadir );
+
+ if ( pw_ask ) {
+ passwd = getpassphrase( _("Enter LDAP Password: ") );
+ if ( passwd == NULL ) { /* Allow EOF to exit. */
+ exit( EXIT_FAILURE );
+ }
+
+ } else if ( pw_file ) {
+ struct berval pw;
+
+ if ( lutil_get_filed_password( pw_file, &pw ) ) {
+ exit( EXIT_FAILURE );
+ }
+
+ passwd = pw.bv_val;
+ }
+
+ if ( !sfile && !rfile && !nfile && !mfile && !bfile && !anum ) {
+ fprintf( stderr, "no data files found.\n" );
+ exit( EXIT_FAILURE );
+ }
+
+ /* look for search requests */
+ if ( sfile ) {
+ snum = get_search_filters( sfile, sreqs, sattrs, sbase, slud );
+ if ( snum < 0 ) {
+ fprintf( stderr,
+ "unable to parse file \"%s\" line %d\n",
+ sfile, -2*(snum + 1));
+ exit( EXIT_FAILURE );
+ }
+ }
+
+ /* look for read requests */
+ if ( rfile ) {
+ rnum = get_read_entries( rfile, rreqs, rflts );
+ if ( rnum < 0 ) {
+ fprintf( stderr,
+ "unable to parse file \"%s\" line %d\n",
+ rfile, -2*(rnum + 1) );
+ exit( EXIT_FAILURE );
+ }
+ }
+
+ /* look for modrdn requests */
+ if ( nfile ) {
+ nnum = get_read_entries( nfile, nreqs, NULL );
+ if ( nnum < 0 ) {
+ fprintf( stderr,
+ "unable to parse file \"%s\" line %d\n",
+ nfile, -2*(nnum + 1) );
+ exit( EXIT_FAILURE );
+ }
+ }
+
+ /* look for modify requests */
+ if ( mfile ) {
+ mnum = get_search_filters( mfile, mreqs, NULL, mdn, NULL );
+ if ( mnum < 0 ) {
+ fprintf( stderr,
+ "unable to parse file \"%s\" line %d\n",
+ mfile, -2*(mnum + 1) );
+ exit( EXIT_FAILURE );
+ }
+ }
+
+ /* look for bind requests */
+ if ( bfile ) {
+ bnum = get_search_filters( bfile, bcreds, battrs, breqs, NULL );
+ if ( bnum < 0 ) {
+ fprintf( stderr,
+ "unable to parse file \"%s\" line %d\n",
+ bfile, -2*(bnum + 1) );
+ exit( EXIT_FAILURE );
+ }
+ }
+
+ /* setup friendly option */
+ switch ( friendly ) {
+ case 0:
+ break;
+
+ case 1:
+ friendlyOpt = "-F";
+ break;
+
+ default:
+ /* NOTE: right now we don't need it more than twice */
+ case 2:
+ friendlyOpt = "-FF";
+ break;
+ }
+
+ /* setup swamp option */
+ if ( swamp ) {
+ swampopt[0] = '-';
+ if ( swamp > 3 ) swamp = 3;
+ swampopt[swamp + 1] = '\0';
+ for ( ; swamp-- > 0; ) swampopt[swamp + 1] = 'S';
+ }
+
+ /* setup loop options */
+ if ( sloops[0] == '\0' ) snprintf( sloops, sizeof( sloops ), "%d", 10 * loops );
+ if ( rloops[0] == '\0' ) snprintf( rloops, sizeof( rloops ), "%d", 20 * loops );
+ if ( aloops[0] == '\0' ) snprintf( aloops, sizeof( aloops ), "%d", loops );
+ if ( nloops[0] == '\0' ) snprintf( nloops, sizeof( nloops ), "%d", loops );
+ if ( mloops[0] == '\0' ) snprintf( mloops, sizeof( mloops ), "%d", loops );
+ if ( bloops[0] == '\0' ) snprintf( bloops, sizeof( bloops ), "%d", 20 * loops );
+
+ /*
+ * generate the search clients
+ */
+
+ sanum = 0;
+ snprintf( scmd, sizeof scmd, "%s" LDAP_DIRSEP SEARCHCMD,
+ progdir );
+ sargs[sanum++] = scmd;
+ sargs[sanum++] = "-H";
+ sargs[sanum++] = uri;
+ sargs[sanum++] = "-D";
+ sargs[sanum++] = manager;
+ sargs[sanum++] = "-w";
+ sargs[sanum++] = passwd;
+ sargs[sanum++] = "-l";
+ sargs[sanum++] = sloops;
+ sargs[sanum++] = "-L";
+ sargs[sanum++] = outerloops;
+ sargs[sanum++] = "-r";
+ sargs[sanum++] = retries;
+ sargs[sanum++] = "-t";
+ sargs[sanum++] = delay;
+ if ( friendly ) {
+ sargs[sanum++] = friendlyOpt;
+ }
+ if ( chaserefs ) {
+ sargs[sanum++] = "-C";
+ }
+ if ( noattrs ) {
+ sargs[sanum++] = "-A";
+ }
+ if ( nobind ) {
+ sargs[sanum++] = "-N";
+ }
+ if ( ignore ) {
+ sargs[sanum++] = "-i";
+ sargs[sanum++] = ignore;
+ }
+ if ( swamp ) {
+ sargs[sanum++] = swampopt;
+ }
+ sargs[sanum++] = "-b";
+ sargs[sanum++] = NULL; /* will hold the search base */
+ sargs[sanum++] = "-s";
+ sargs[sanum++] = NULL; /* will hold the search scope */
+ sargs[sanum++] = "-f";
+ sargs[sanum++] = NULL; /* will hold the search request */
+
+ sargs[sanum++] = NULL;
+ sargs[sanum++] = NULL; /* might hold the "attr" request */
+ sextra_args += 2;
+
+ sargs[sanum] = NULL;
+
+ /*
+ * generate the read clients
+ */
+
+ ranum = 0;
+ snprintf( rcmd, sizeof rcmd, "%s" LDAP_DIRSEP READCMD,
+ progdir );
+ rargs[ranum++] = rcmd;
+ rargs[ranum++] = "-H";
+ rargs[ranum++] = uri;
+ rargs[ranum++] = "-D";
+ rargs[ranum++] = manager;
+ rargs[ranum++] = "-w";
+ rargs[ranum++] = passwd;
+ rargs[ranum++] = "-l";
+ rargs[ranum++] = rloops;
+ rargs[ranum++] = "-L";
+ rargs[ranum++] = outerloops;
+ rargs[ranum++] = "-r";
+ rargs[ranum++] = retries;
+ rargs[ranum++] = "-t";
+ rargs[ranum++] = delay;
+ if ( friendly ) {
+ rargs[ranum++] = friendlyOpt;
+ }
+ if ( chaserefs ) {
+ rargs[ranum++] = "-C";
+ }
+ if ( noattrs ) {
+ rargs[ranum++] = "-A";
+ }
+ if ( ignore ) {
+ rargs[ranum++] = "-i";
+ rargs[ranum++] = ignore;
+ }
+ if ( swamp ) {
+ rargs[ranum++] = swampopt;
+ }
+ rargs[ranum++] = "-e";
+ rargs[ranum++] = NULL; /* will hold the read entry */
+
+ rargs[ranum++] = NULL;
+ rargs[ranum++] = NULL; /* might hold the filter arg */
+ rextra_args += 2;
+
+ rargs[ranum] = NULL;
+
+ /*
+ * generate the modrdn clients
+ */
+
+ nanum = 0;
+ snprintf( ncmd, sizeof ncmd, "%s" LDAP_DIRSEP MODRDNCMD,
+ progdir );
+ nargs[nanum++] = ncmd;
+ nargs[nanum++] = "-H";
+ nargs[nanum++] = uri;
+ nargs[nanum++] = "-D";
+ nargs[nanum++] = manager;
+ nargs[nanum++] = "-w";
+ nargs[nanum++] = passwd;
+ nargs[nanum++] = "-l";
+ nargs[nanum++] = nloops;
+ nargs[nanum++] = "-L";
+ nargs[nanum++] = outerloops;
+ nargs[nanum++] = "-r";
+ nargs[nanum++] = retries;
+ nargs[nanum++] = "-t";
+ nargs[nanum++] = delay;
+ if ( friendly ) {
+ nargs[nanum++] = friendlyOpt;
+ }
+ if ( chaserefs ) {
+ nargs[nanum++] = "-C";
+ }
+ if ( ignore ) {
+ nargs[nanum++] = "-i";
+ nargs[nanum++] = ignore;
+ }
+ nargs[nanum++] = "-e";
+ nargs[nanum++] = NULL; /* will hold the modrdn entry */
+ nargs[nanum] = NULL;
+
+ /*
+ * generate the modify clients
+ */
+
+ manum = 0;
+ snprintf( mcmd, sizeof mcmd, "%s" LDAP_DIRSEP MODIFYCMD,
+ progdir );
+ margs[manum++] = mcmd;
+ margs[manum++] = "-H";
+ margs[manum++] = uri;
+ margs[manum++] = "-D";
+ margs[manum++] = manager;
+ margs[manum++] = "-w";
+ margs[manum++] = passwd;
+ margs[manum++] = "-l";
+ margs[manum++] = mloops;
+ margs[manum++] = "-L";
+ margs[manum++] = outerloops;
+ margs[manum++] = "-r";
+ margs[manum++] = retries;
+ margs[manum++] = "-t";
+ margs[manum++] = delay;
+ if ( friendly ) {
+ margs[manum++] = friendlyOpt;
+ }
+ if ( chaserefs ) {
+ margs[manum++] = "-C";
+ }
+ if ( ignore ) {
+ margs[manum++] = "-i";
+ margs[manum++] = ignore;
+ }
+ margs[manum++] = "-e";
+ margs[manum++] = NULL; /* will hold the modify entry */
+ margs[manum++] = "-a";;
+ margs[manum++] = NULL; /* will hold the ava */
+ margs[manum] = NULL;
+
+ /*
+ * generate the add/delete clients
+ */
+
+ aanum = 0;
+ snprintf( acmd, sizeof acmd, "%s" LDAP_DIRSEP ADDCMD,
+ progdir );
+ aargs[aanum++] = acmd;
+ aargs[aanum++] = "-H";
+ aargs[aanum++] = uri;
+ aargs[aanum++] = "-D";
+ aargs[aanum++] = manager;
+ aargs[aanum++] = "-w";
+ aargs[aanum++] = passwd;
+ aargs[aanum++] = "-l";
+ aargs[aanum++] = aloops;
+ aargs[aanum++] = "-L";
+ aargs[aanum++] = outerloops;
+ aargs[aanum++] = "-r";
+ aargs[aanum++] = retries;
+ aargs[aanum++] = "-t";
+ aargs[aanum++] = delay;
+ if ( friendly ) {
+ aargs[aanum++] = friendlyOpt;
+ }
+ if ( chaserefs ) {
+ aargs[aanum++] = "-C";
+ }
+ if ( ignore ) {
+ aargs[aanum++] = "-i";
+ aargs[aanum++] = ignore;
+ }
+ aargs[aanum++] = "-f";
+ aargs[aanum++] = NULL; /* will hold the add data file */
+ aargs[aanum] = NULL;
+
+ /*
+ * generate the bind clients
+ */
+
+ banum = 0;
+ snprintf( bcmd, sizeof bcmd, "%s" LDAP_DIRSEP BINDCMD,
+ progdir );
+ bargs[banum++] = bcmd;
+ if ( !noinit ) {
+ bargs[banum++] = "-I"; /* init on each bind */
+ }
+ bargs[banum++] = "-H";
+ bargs[banum++] = uri;
+ bargs[banum++] = "-l";
+ bargs[banum++] = bloops;
+ bargs[banum++] = "-L";
+ bargs[banum++] = outerloops;
+ bargs[banum++] = "-r";
+ bargs[banum++] = retries;
+ bargs[banum++] = "-t";
+ bargs[banum++] = delay;
+ if ( friendly ) {
+ bargs[banum++] = friendlyOpt;
+ }
+ if ( chaserefs ) {
+ bargs[banum++] = "-C";
+ }
+ if ( ignore ) {
+ bargs[banum++] = "-i";
+ bargs[banum++] = ignore;
+ }
+ if ( nextra ) {
+ bargs[banum++] = "-B";
+ bargs_extra = &bargs[banum++];
+ }
+ bargs[banum++] = "-D";
+ bargs[banum++] = NULL;
+ bargs[banum++] = "-w";
+ bargs[banum++] = NULL;
+ bargs[banum] = NULL;
+
+#define DOREQ(n,j) ((n) && ((maxkids > (n)) ? ((j) < maxkids ) : ((j) < (n))))
+
+ for ( j = 0; j < MAXREQS; j++ ) {
+ /* search */
+ if ( DOREQ( snum, j ) ) {
+ int jj = j % snum;
+ int x = sanum - sextra_args;
+
+ /* base */
+ if ( sbase[jj] != NULL ) {
+ sargs[sanum - 7] = sbase[jj];
+
+ } else {
+ sargs[sanum - 7] = slud[jj]->lud_dn;
+ }
+
+ /* scope */
+ if ( slud[jj] != NULL ) {
+ sargs[sanum - 5] = (char *)ldap_pvt_scope2str( slud[jj]->lud_scope );
+
+ } else {
+ sargs[sanum - 5] = "sub";
+ }
+
+ /* filter */
+ if ( sreqs[jj] != NULL ) {
+ sargs[sanum - 3] = sreqs[jj];
+
+ } else if ( slud[jj]->lud_filter != NULL ) {
+ sargs[sanum - 3] = slud[jj]->lud_filter;
+
+ } else {
+ sargs[sanum - 3] = "(objectClass=*)";
+ }
+
+ /* extras */
+ sargs[x] = NULL;
+
+ /* attr */
+ if ( sattrs[jj] != NULL ) {
+ sargs[x++] = "-a";
+ sargs[x++] = sattrs[jj];
+ }
+
+ /* attrs */
+ if ( slud[jj] != NULL && slud[jj]->lud_attrs != NULL ) {
+ int i;
+
+ for ( i = 0; slud[jj]->lud_attrs[ i ] != NULL && x + i < MAXARGS - 1; i++ ) {
+ sargs[x + i] = slud[jj]->lud_attrs[ i ];
+ }
+ sargs[x + i] = NULL;
+ }
+
+ fork_child( scmd, sargs );
+ }
+
+ /* read */
+ if ( DOREQ( rnum, j ) ) {
+ int jj = j % rnum;
+ int x = ranum - rextra_args;
+
+ rargs[ranum - 3] = rreqs[jj];
+ if ( rflts[jj] != NULL ) {
+ rargs[x++] = "-f";
+ rargs[x++] = rflts[jj];
+ }
+ rargs[x] = NULL;
+ fork_child( rcmd, rargs );
+ }
+
+ /* rename */
+ if ( j < nnum ) {
+ nargs[nanum - 1] = nreqs[j];
+ fork_child( ncmd, nargs );
+ }
+
+ /* modify */
+ if ( j < mnum ) {
+ margs[manum - 3] = mdn[j];
+ margs[manum - 1] = mreqs[j];
+ fork_child( mcmd, margs );
+ }
+
+ /* add/delete */
+ if ( j < anum ) {
+ aargs[aanum - 1] = afiles[j];
+ fork_child( acmd, aargs );
+ }
+
+ /* bind */
+ if ( DOREQ( bnum, j ) ) {
+ int jj = j % bnum;
+
+ if ( nextra ) {
+ int n = ((double)nextra)*rand()/(RAND_MAX + 1.0);
+ extra_t *e;
+
+ for ( e = extra; n-- > 0; e = e->next )
+ ;
+ *bargs_extra = e->action;
+ }
+
+ if ( battrs[jj] != NULL ) {
+ bargs[banum - 3] = manager ? manager : "";
+ bargs[banum - 1] = passwd ? passwd : "";
+
+ bargs[banum + 0] = "-b";
+ bargs[banum + 1] = breqs[jj];
+ bargs[banum + 2] = "-f";
+ bargs[banum + 3] = bcreds[jj];
+ bargs[banum + 4] = "-a";
+ bargs[banum + 5] = battrs[jj];
+ bargs[banum + 6] = NULL;
+
+ } else {
+ bargs[banum - 3] = breqs[jj];
+ bargs[banum - 1] = bcreds[jj];
+ bargs[banum] = NULL;
+ }
+
+ fork_child( bcmd, bargs );
+ bargs[banum] = NULL;
+ }
+ }
+
+ wait4kids( -1 );
+
+ exit( EXIT_SUCCESS );
+}
+
+static char *
+get_file_name( char *dirname, char *filename )
+{
+ char buf[MAXPATHLEN];
+
+ snprintf( buf, sizeof buf, "%s" LDAP_DIRSEP "%s",
+ dirname, filename );
+ return( strdup( buf ));
+}
+
+
+static int
+get_search_filters( char *filename, char *filters[], char *attrs[], char *bases[], LDAPURLDesc *luds[] )
+{
+ FILE *fp;
+ int filter = 0;
+
+ if ( (fp = fopen( filename, "r" )) != NULL ) {
+ char line[BUFSIZ];
+
+ while (( filter < MAXREQS ) && ( fgets( line, BUFSIZ, fp ))) {
+ char *nl;
+ int got_URL = 0;
+
+ if (( nl = strchr( line, '\r' )) || ( nl = strchr( line, '\n' )))
+ *nl = '\0';
+
+ if ( luds ) luds[filter] = NULL;
+
+ if ( luds && strncmp( line, "ldap:///", STRLENOF( "ldap:///" ) ) == 0 ) {
+ LDAPURLDesc *lud;
+
+ got_URL = 1;
+ bases[filter] = NULL;
+ if ( ldap_url_parse( line, &lud ) != LDAP_URL_SUCCESS ) {
+ filter = -filter - 1;
+ break;
+ }
+
+ if ( lud->lud_dn == NULL || lud->lud_exts != NULL ) {
+ filter = -filter - 1;
+ ldap_free_urldesc( lud );
+ break;
+ }
+
+ luds[filter] = lud;
+
+ } else {
+ bases[filter] = ArgDup( line );
+ }
+ if ( fgets( line, BUFSIZ, fp ) == NULL )
+ *line = '\0';
+ if (( nl = strchr( line, '\r' )) || ( nl = strchr( line, '\n' )))
+ *nl = '\0';
+
+ filters[filter] = ArgDup( line );
+ if ( attrs ) {
+ if ( filters[filter][0] == '+') {
+ char *sep = strchr( filters[filter], ':' );
+
+ attrs[ filter ] = &filters[ filter ][ 1 ];
+ if ( sep != NULL ) {
+ sep[ 0 ] = '\0';
+ /* NOTE: don't free this! */
+ filters[ filter ] = &sep[ 1 ];
+ }
+
+ } else {
+ attrs[ filter ] = NULL;
+ }
+ }
+ filter++;
+
+ }
+ fclose( fp );
+ }
+
+ return filter;
+}
+
+
+static int
+get_read_entries( char *filename, char *entries[], char *filters[] )
+{
+ FILE *fp;
+ int entry = 0;
+
+ if ( (fp = fopen( filename, "r" )) != NULL ) {
+ char line[BUFSIZ];
+
+ while (( entry < MAXREQS ) && ( fgets( line, BUFSIZ, fp ))) {
+ char *nl;
+
+ if (( nl = strchr( line, '\r' )) || ( nl = strchr( line, '\n' )))
+ *nl = '\0';
+ if ( filters != NULL && line[0] == '+' ) {
+ LDAPURLDesc *lud;
+
+ if ( ldap_url_parse( &line[1], &lud ) != LDAP_URL_SUCCESS ) {
+ entry = -entry - 1;
+ break;
+ }
+
+ if ( lud->lud_dn == NULL || lud->lud_dn[ 0 ] == '\0' ) {
+ ldap_free_urldesc( lud );
+ entry = -entry - 1;
+ break;
+ }
+
+ entries[entry] = ArgDup( lud->lud_dn );
+
+ if ( lud->lud_filter ) {
+ filters[entry] = ArgDup( lud->lud_filter );
+
+ } else {
+ filters[entry] = ArgDup( "(objectClass=*)" );
+ }
+ ldap_free_urldesc( lud );
+
+ } else {
+ if ( filters != NULL )
+ filters[entry] = NULL;
+
+ entries[entry] = ArgDup( line );
+ }
+
+ entry++;
+
+ }
+ fclose( fp );
+ }
+
+ return( entry );
+}
+
+#ifndef HAVE_WINSOCK
+static void
+fork_child( char *prog, char **args )
+{
+ /* note: obscures global pid var; intended */
+ pid_t pid;
+
+ wait4kids( maxkids );
+
+ switch ( pid = fork() ) {
+ case 0: /* child */
+#ifdef HAVE_EBCDIC
+ /* The __LIBASCII execvp only handles ASCII "prog",
+ * we still need to translate the arg vec ourselves.
+ */
+ { char *arg2[MAXREQS];
+ int i;
+
+ for (i=0; args[i]; i++) {
+ arg2[i] = ArgDup(args[i]);
+ __atoe(arg2[i]);
+ }
+ arg2[i] = NULL;
+ args = arg2; }
+#endif
+ execvp( prog, args );
+ tester_perror( "execvp", NULL );
+ { int i;
+ for (i=0; args[i]; i++);
+ fprintf(stderr,"%d args\n", i);
+ for (i=0; args[i]; i++)
+ fprintf(stderr,"%d %s\n", i, args[i]);
+ }
+
+ exit( EXIT_FAILURE );
+ break;
+
+ case -1: /* trouble */
+ tester_perror( "fork", NULL );
+ break;
+
+ default: /* parent */
+ nkids++;
+ break;
+ }
+}
+
+static void
+wait4kids( int nkidval )
+{
+ int status;
+
+ while ( nkids >= nkidval ) {
+ pid_t pid = wait( &status );
+
+ if ( WIFSTOPPED(status) ) {
+ fprintf( stderr,
+ "stopping: child PID=%ld stopped with signal %d\n",
+ (long) pid, (int) WSTOPSIG(status) );
+
+ } else if ( WIFSIGNALED(status) ) {
+ fprintf( stderr,
+ "stopping: child PID=%ld terminated with signal %d%s\n",
+ (long) pid, (int) WTERMSIG(status),
+#ifdef WCOREDUMP
+ WCOREDUMP(status) ? ", core dumped" : ""
+#else
+ ""
+#endif
+ );
+ exit( WEXITSTATUS(status) );
+
+ } else if ( WEXITSTATUS(status) != 0 ) {
+ fprintf( stderr,
+ "stopping: child PID=%ld exited with status %d\n",
+ (long) pid, (int) WEXITSTATUS(status) );
+ exit( WEXITSTATUS(status) );
+
+ } else {
+ nkids--;
+ }
+ }
+}
+#else
+
+static void
+wait4kids( int nkidval )
+{
+ int rc, i;
+
+ while ( nkids >= nkidval ) {
+ rc = WaitForMultipleObjects( nkids, children, FALSE, INFINITE );
+ for ( i=rc - WAIT_OBJECT_0; i<nkids-1; i++)
+ children[i] = children[i+1];
+ nkids--;
+ }
+}
+
+static void
+fork_child( char *prog, char **args )
+{
+ int rc;
+
+ wait4kids( maxkids );
+
+ rc = _spawnvp( _P_NOWAIT, prog, args );
+
+ if ( rc == -1 ) {
+ tester_perror( "_spawnvp", NULL );
+ } else {
+ children[nkids++] = (HANDLE)rc;
+ }
+}
+#endif
diff --git a/tests/progs/slapd-watcher.c b/tests/progs/slapd-watcher.c
new file mode 100644
index 0000000..2dd5579
--- /dev/null
+++ b/tests/progs/slapd-watcher.c
@@ -0,0 +1,809 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2024 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 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 Howard Chu for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include "ac/signal.h"
+#include "ac/stdlib.h"
+#include "ac/time.h"
+
+#include "ac/ctype.h"
+#include "ac/param.h"
+#include "ac/socket.h"
+#include "ac/string.h"
+#include "ac/unistd.h"
+#include "ac/wait.h"
+#include "ac/time.h"
+
+#include "ldap.h"
+#include "lutil.h"
+#include "lutil_ldap.h"
+#include "lber_pvt.h"
+#include "ldap_pvt.h"
+
+#include "slapd-common.h"
+
+#define SLAP_SYNC_SID_MAX 4095
+
+#define HAS_MONITOR 1
+#define HAS_BASE 2
+#define HAS_ENTRIES 4
+#define HAS_SREPL 8
+#define HAS_ALL (HAS_MONITOR|HAS_BASE|HAS_ENTRIES|HAS_SREPL)
+
+
+#define WAS_LATE 0x100
+#define WAS_DOWN 0x200
+#define WAS_INIT 0x400
+
+#define MONFILTER "(objectClass=monitorOperation)"
+
+static const char *default_monfilter = MONFILTER;
+
+typedef enum {
+ SLAP_OP_BIND = 0,
+ SLAP_OP_UNBIND,
+ SLAP_OP_SEARCH,
+ SLAP_OP_COMPARE,
+ SLAP_OP_MODIFY,
+ SLAP_OP_MODRDN,
+ SLAP_OP_ADD,
+ SLAP_OP_DELETE,
+ SLAP_OP_ABANDON,
+ SLAP_OP_EXTENDED,
+ SLAP_OP_LAST
+} slap_op_t;
+
+struct opname {
+ struct berval rdn;
+ char *display;
+} opnames[] = {
+ { BER_BVC("cn=Bind"), "Bind" },
+ { BER_BVC("cn=Unbind"), "Unbind" },
+ { BER_BVC("cn=Search"), "Search" },
+ { BER_BVC("cn=Compare"), "Compare" },
+ { BER_BVC("cn=Modify"), "Modify" },
+ { BER_BVC("cn=Modrdn"), "ModDN" },
+ { BER_BVC("cn=Add"), "Add" },
+ { BER_BVC("cn=Delete"), "Delete" },
+ { BER_BVC("cn=Abandon"), "Abandon" },
+ { BER_BVC("cn=Extended"), "Extended" },
+ { BER_BVNULL, NULL }
+};
+
+typedef struct counters {
+ struct timeval time;
+ unsigned long entries;
+ unsigned long ops[SLAP_OP_LAST];
+} counters;
+
+typedef struct csns {
+ struct berval *vals;
+ struct timeval *tvs;
+} csns;
+
+typedef struct activity {
+ time_t active;
+ time_t idle;
+ time_t maxlag;
+ time_t lag;
+} activity;
+
+typedef struct server {
+ char *url;
+ LDAP *ld;
+ int flags;
+ int sid;
+ struct berval monitorbase;
+ char *monitorfilter;
+ time_t late;
+ time_t down;
+ counters c_prev;
+ counters c_curr;
+ csns csn_prev;
+ csns csn_curr;
+ activity *times;
+} server;
+
+static void
+usage( char *name, char opt )
+{
+ if ( opt ) {
+ fprintf( stderr, "%s: unable to handle option \'%c\'\n\n",
+ name, opt );
+ }
+
+ fprintf( stderr, "usage: %s "
+ "[-D <dn> [ -w <passwd> ]] "
+ "[-d <level>] "
+ "[-O <SASL secprops>] "
+ "[-R <SASL realm>] "
+ "[-U <SASL authcid> [-X <SASL authzid>]] "
+ "[-x | -Y <SASL mech>] "
+ "[-i <interval>] "
+ "[-s <sids>] "
+ "[-c <contextDN>] "
+ "[-b <baseDN> ] URI[...]\n",
+ name );
+ exit( EXIT_FAILURE );
+}
+
+struct berval base, cbase;
+int interval = 10;
+int numservers;
+server *servers;
+char *monfilter;
+
+struct berval at_namingContexts = BER_BVC("namingContexts");
+struct berval at_monitorOpCompleted = BER_BVC("monitorOpCompleted");
+struct berval at_olmMDBEntries = BER_BVC("olmMDBEntries");
+struct berval at_contextCSN = BER_BVC("contextCSN");
+
+void timestamp(time_t *tt)
+{
+ struct tm *tm = gmtime(tt);
+ printf("%d-%02d-%02d %02d:%02d:%02d",
+ tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+}
+
+void deltat(time_t *tt)
+{
+ struct tm *tm = gmtime(tt);
+ if (tm->tm_mday-1)
+ printf("%02d+", tm->tm_mday-1);
+ printf("%02d:%02d:%02d",
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+}
+
+static char *clearscreen = "\033[H\033[2J";
+
+void rotate_stats( server *sv )
+{
+ if ( sv->flags & HAS_MONITOR )
+ sv->c_prev = sv->c_curr;
+ if ( sv->flags & HAS_BASE ) {
+ int i;
+
+ for (i=0; i<numservers; i++) {
+ if ( sv->csn_curr.vals[i].bv_len ) {
+ ber_bvreplace(&sv->csn_prev.vals[i],
+ &sv->csn_curr.vals[i]);
+ sv->csn_prev.tvs[i] = sv->csn_curr.tvs[i];
+ } else {
+ if ( sv->csn_prev.vals[i].bv_val )
+ sv->csn_prev.vals[i].bv_val[0] = '\0';
+ }
+ }
+ }
+}
+
+void display()
+{
+ int i, j;
+ struct timeval now;
+ time_t now_t;
+
+ gettimeofday(&now, NULL);
+ now_t = now.tv_sec;
+ printf("%s", clearscreen);
+ timestamp(&now_t);
+ printf("\n");
+
+ for (i=0; i<numservers; i++) {
+ printf("\n%s", servers[i].url );
+ if ( !(servers[i].flags & WAS_INIT )) {
+ printf(", unknown");
+ }
+ if ( servers[i].flags & WAS_DOWN ) {
+ printf(", down@");
+ timestamp( &servers[i].down );
+ }
+ if ( servers[i].flags & WAS_LATE ) {
+ printf(", late@");
+ timestamp( &servers[i].late );
+ }
+ printf("\n");
+ if ( servers[i].flags & HAS_MONITOR ) {
+ struct timeval tv;
+ double rate, duration;
+ long delta;
+ printf(" ");
+ if ( servers[i].flags & HAS_ENTRIES )
+ printf(" Entries ");
+ for ( j = 0; j<SLAP_OP_LAST; j++ )
+ printf(" %9s ", opnames[j].display);
+ printf("\n");
+ printf("Num ");
+ if ( servers[i].flags & HAS_ENTRIES )
+ printf("%10lu ", servers[i].c_curr.entries);
+ for ( j = 0; j<SLAP_OP_LAST; j++ )
+ printf("%10lu ", servers[i].c_curr.ops[j]);
+ printf("\n");
+ printf("Num/s ");
+ tv.tv_usec = now.tv_usec - servers[i].c_prev.time.tv_usec;
+ tv.tv_sec = now.tv_sec - servers[i].c_prev.time.tv_sec;
+ if ( tv.tv_usec < 0 ) {
+ tv.tv_usec += 1000000;
+ tv.tv_sec--;
+ }
+ duration = tv.tv_sec + (tv.tv_usec / (double)1000000);
+ if ( servers[i].flags & HAS_ENTRIES ) {
+ delta = servers[i].c_curr.entries - servers[i].c_prev.entries;
+ rate = delta / duration;
+ printf("%10.2f ", rate);
+ }
+ for ( j = 0; j<SLAP_OP_LAST; j++ ) {
+ delta = servers[i].c_curr.ops[j] - servers[i].c_prev.ops[j];
+ rate = delta / duration;
+ printf("%10.2f ", rate);
+ }
+ printf("\n");
+ }
+ if ( servers[i].flags & HAS_BASE ) {
+ for (j=0; j<numservers; j++) {
+ /* skip empty CSNs */
+ if (!servers[i].csn_curr.vals[j].bv_len ||
+ !servers[i].csn_curr.vals[j].bv_val[0])
+ continue;
+ printf("contextCSN: %s", servers[i].csn_curr.vals[j].bv_val );
+ if (ber_bvcmp(&servers[i].csn_curr.vals[j],
+ &servers[i].csn_prev.vals[j])) {
+ /* a difference */
+ if (servers[i].times[j].idle) {
+ servers[i].times[j].idle = 0;
+ servers[i].times[j].active = 0;
+ servers[i].times[j].maxlag = 0;
+ servers[i].times[j].lag = 0;
+ }
+active:
+ if (!servers[i].times[j].active)
+ servers[i].times[j].active = now_t;
+ printf(" actv@");
+ timestamp(&servers[i].times[j].active);
+ } else if ( servers[i].times[j].lag || ( servers[i].flags & WAS_LATE )) {
+ goto active;
+ } else {
+ if (servers[i].times[j].active && !servers[i].times[j].idle)
+ servers[i].times[j].idle = now_t;
+ if (servers[i].times[j].active) {
+ printf(" actv@");
+ timestamp(&servers[i].times[j].active);
+ printf(", idle@");
+ timestamp(&servers[i].times[j].idle);
+ } else {
+ printf(" idle");
+ }
+ }
+ if (i != j) {
+ if (ber_bvcmp(&servers[i].csn_curr.vals[j],
+ &servers[j].csn_curr.vals[j])) {
+ struct timeval delta;
+ int ahead = 0;
+ time_t deltatt;
+ delta.tv_sec = servers[j].csn_curr.tvs[j].tv_sec -
+ servers[i].csn_curr.tvs[j].tv_sec;
+ delta.tv_usec = servers[j].csn_curr.tvs[j].tv_usec -
+ servers[i].csn_curr.tvs[j].tv_usec;
+ if (delta.tv_usec < 0) {
+ delta.tv_usec += 1000000;
+ delta.tv_sec--;
+ }
+ if (delta.tv_sec < 0) {
+ delta.tv_sec = -delta.tv_sec;
+ ahead = 1;
+ }
+ deltatt = delta.tv_sec;
+ if (ahead)
+ printf(", ahead ");
+ else
+ printf(", behind ");
+ deltat( &deltatt );
+ servers[i].times[j].lag = deltatt;
+ if (deltatt > servers[i].times[j].maxlag)
+ servers[i].times[j].maxlag = deltatt;
+ } else {
+ servers[i].times[j].lag = 0;
+ printf(", sync'd");
+ }
+ if (servers[i].times[j].maxlag) {
+ printf(", max delta ");
+ deltat( &servers[i].times[j].maxlag );
+ }
+ }
+ printf("\n");
+ }
+ }
+ if ( !( servers[i].flags & WAS_LATE ))
+ rotate_stats( &servers[i] );
+ }
+}
+
+void get_counters(
+ LDAP *ld,
+ LDAPMessage *e,
+ BerElement *ber,
+ counters *c )
+{
+ int rc;
+ slap_op_t op = SLAP_OP_BIND;
+ struct berval dn, bv, *bvals, **bvp = &bvals;
+
+ do {
+ int done = 0;
+ for ( rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp );
+ rc == LDAP_SUCCESS;
+ rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp )) {
+
+ if ( bv.bv_val == NULL ) break;
+ if ( !ber_bvcmp( &bv, &at_monitorOpCompleted ) && bvals ) {
+ c->ops[op] = strtoul( bvals[0].bv_val, NULL, 0 );
+ done = 1;
+ }
+ if ( bvals ) {
+ ber_memfree( bvals );
+ bvals = NULL;
+ }
+ if ( done )
+ break;
+ }
+ ber_free( ber, 0 );
+ e = ldap_next_entry( ld, e );
+ if ( !e )
+ break;
+ ldap_get_dn_ber( ld, e, &ber, &dn );
+ op++;
+ } while ( op < SLAP_OP_LAST );
+}
+
+int
+slap_parse_csn_sid( struct berval *csnp )
+{
+ char *p, *q;
+ struct berval csn = *csnp;
+ int i;
+
+ p = ber_bvchr( &csn, '#' );
+ if ( !p )
+ return -1;
+ p++;
+ csn.bv_len -= p - csn.bv_val;
+ csn.bv_val = p;
+
+ p = ber_bvchr( &csn, '#' );
+ if ( !p )
+ return -1;
+ p++;
+ csn.bv_len -= p - csn.bv_val;
+ csn.bv_val = p;
+
+ q = ber_bvchr( &csn, '#' );
+ if ( !q )
+ return -1;
+
+ csn.bv_len = q - p;
+
+ i = strtol( p, &q, 16 );
+ if ( p == q || q != p + csn.bv_len || i < 0 || i > SLAP_SYNC_SID_MAX ) {
+ i = -1;
+ }
+
+ return i;
+}
+
+void get_csns(
+ csns *c,
+ struct berval *bvs
+)
+{
+ int i, j;
+
+ /* clear old values if any */
+ for (i=0; i<numservers; i++)
+ if ( c->vals[i].bv_val )
+ c->vals[i].bv_val[0] = '\0';
+
+ for (i=0; bvs[i].bv_val; i++) {
+ struct lutil_tm tm;
+ struct lutil_timet tt;
+ int sid = slap_parse_csn_sid( &bvs[i] );
+ for (j=0; j<numservers; j++)
+ if (sid == servers[j].sid) break;
+ if (j < numservers) {
+ ber_bvreplace( &c->vals[j], &bvs[i] );
+ lutil_parsetime(bvs[i].bv_val, &tm);
+ c->tvs[j].tv_usec = tm.tm_nsec / 1000;
+ lutil_tm2time( &tm, &tt );
+ c->tvs[j].tv_sec = tt.tt_sec;
+ }
+ }
+}
+
+int
+setup_server( struct tester_conn_args *config, server *sv )
+{
+ config->uri = sv->url;
+ tester_init_ld( &sv->ld, config, TESTER_INIT_NOEXIT );
+ if ( !sv->ld )
+ return -1;
+
+ sv->flags &= ~HAS_ALL;
+ {
+ char *attrs[] = { at_namingContexts.bv_val, at_monitorOpCompleted.bv_val,
+ at_olmMDBEntries.bv_val, NULL };
+ LDAPMessage *res = NULL, *e = NULL;
+ BerElement *ber = NULL;
+ LDAP *ld = sv->ld;
+ struct berval dn, bv, *bvals, **bvp = &bvals;
+ int j, rc;
+
+ rc = ldap_search_ext_s( ld, "cn=monitor", LDAP_SCOPE_SUBTREE, monfilter,
+ attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &res );
+ switch(rc) {
+ case LDAP_SIZELIMIT_EXCEEDED:
+ case LDAP_TIMELIMIT_EXCEEDED:
+ case LDAP_SUCCESS:
+ gettimeofday( &sv->c_curr.time, 0 );
+ sv->flags |= HAS_MONITOR;
+ for ( e = ldap_first_entry( ld, res ); e; e = ldap_next_entry( ld, e )) {
+ ldap_get_dn_ber( ld, e, &ber, &dn );
+ if ( !strncasecmp( dn.bv_val, "cn=Database", sizeof("cn=Database")-1 ) ||
+ !strncasecmp( dn.bv_val, "cn=Frontend", sizeof("cn=Frontend")-1 )) {
+ int matched = 0;
+ for ( rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp );
+ rc == LDAP_SUCCESS;
+ rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp )) {
+ if ( bv.bv_val == NULL ) break;
+ if (!ber_bvcmp( &bv, &at_namingContexts ) && bvals ) {
+ for (j=0; bvals[j].bv_val; j++) {
+ if ( !ber_bvstrcasecmp( &base, &bvals[j] )) {
+ matched = 1;
+ break;
+ }
+ }
+ if (!matched) {
+ ber_memfree( bvals );
+ bvals = NULL;
+ break;
+ }
+ }
+ if (!ber_bvcmp( &bv, &at_olmMDBEntries )) {
+ ber_bvreplace( &sv->monitorbase, &dn );
+ sv->flags |= HAS_ENTRIES;
+ sv->c_curr.entries = strtoul( bvals[0].bv_val, NULL, 0 );
+ }
+ ber_memfree( bvals );
+ bvals = NULL;
+ }
+ } else if (!strncasecmp( dn.bv_val, opnames[0].rdn.bv_val,
+ opnames[0].rdn.bv_len )) {
+ get_counters( ld, e, ber, &sv->c_curr );
+ break;
+ }
+ if ( ber )
+ ber_free( ber, 0 );
+ }
+ break;
+
+ case LDAP_NO_SUCH_OBJECT:
+ /* no cn=monitor */
+ break;
+
+ default:
+ tester_ldap_error( ld, "ldap_search_ext_s(cn=Monitor)", sv->url );
+ }
+ ldap_msgfree( res );
+
+ if ( cbase.bv_val ) {
+ char *attr2[] = { at_contextCSN.bv_val, NULL };
+ rc = ldap_search_ext_s( ld, cbase.bv_val, LDAP_SCOPE_BASE, "(objectClass=*)",
+ attr2, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &res );
+ switch(rc) {
+ case LDAP_SUCCESS:
+ e = ldap_first_entry( ld, res );
+ if ( e ) {
+ sv->flags |= HAS_BASE;
+ ldap_get_dn_ber( ld, e, &ber, &dn );
+ for ( rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp );
+ rc == LDAP_SUCCESS;
+ rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp )) {
+ int done = 0;
+ if ( bv.bv_val == NULL ) break;
+ if ( bvals ) {
+ if ( !ber_bvcmp( &bv, &at_contextCSN )) {
+ get_csns( &sv->csn_curr, bvals );
+ done = 1;
+ }
+ ber_memfree( bvals );
+ bvals = NULL;
+ if ( done )
+ break;
+ }
+ }
+ }
+ break;
+
+ default:
+ tester_ldap_error( ld, "ldap_search_ext_s(baseDN)", sv->url );
+ }
+ ldap_msgfree( res );
+ }
+ }
+
+ if ( sv->monitorfilter != default_monfilter )
+ free( sv->monitorfilter );
+ if ( sv->flags & HAS_ENTRIES ) {
+ int len = sv->monitorbase.bv_len + sizeof("(|(entryDN=)" MONFILTER ")");
+ char *ptr = malloc(len);
+ sprintf(ptr, "(|(entryDN=%s)" MONFILTER ")", sv->monitorbase.bv_val );
+ sv->monitorfilter = ptr;
+ } else if ( sv->flags & HAS_MONITOR ) {
+ sv->monitorfilter = (char *)default_monfilter;
+ }
+ if ( !( sv->flags & WAS_INIT )) {
+ sv->flags |= WAS_INIT;
+ rotate_stats( sv );
+ }
+ return 0;
+}
+
+int
+main( int argc, char **argv )
+{
+ int i, rc, *msg1, *msg2;
+ char **sids = NULL;
+ struct tester_conn_args *config;
+
+ config = tester_init( "slapd-watcher", TESTER_TESTER );
+ config->authmethod = LDAP_AUTH_SIMPLE;
+
+ while ( ( i = getopt( argc, argv, "D:O:R:U:X:Y:b:c:d:i:s:w:x" ) ) != EOF )
+ {
+ switch ( i ) {
+ case 'b': /* base DN for DB entrycount lookups */
+ ber_str2bv( optarg, 0, 0, &base );
+ if ( !cbase.bv_val )
+ cbase = base;
+ break;
+
+ case 'c': /* base DN for contextCSN lookups */
+ ber_str2bv( optarg, 0, 0, &cbase );
+ break;
+
+ case 'i':
+ interval = atoi(optarg);
+ break;
+
+ case 's':
+ sids = ldap_str2charray( optarg, "," );
+ break;
+
+ default:
+ if ( tester_config_opt( config, i, optarg ) == LDAP_SUCCESS )
+ break;
+
+ usage( argv[0], i );
+ break;
+ }
+ }
+
+ tester_config_finish( config );
+#ifdef SIGPIPE
+ (void) SIGNAL(SIGPIPE, SIG_IGN);
+#endif
+
+ /* don't clear the screen if debug is enabled */
+ if (debug)
+ clearscreen = "\n\n";
+
+ numservers = argc - optind;
+ if ( !numservers )
+ usage( argv[0], 0 );
+
+ if ( sids ) {
+ for (i=0; sids[i]; i++ );
+ if ( i != numservers ) {
+ fprintf(stderr, "Number of sids doesn't equal number of server URLs\n");
+ exit( EXIT_FAILURE );
+ }
+ }
+
+ argv += optind;
+ argc -= optind;
+ servers = calloc( numservers, sizeof(server));
+
+ if ( base.bv_val ) {
+ monfilter = "(|(entryDN:dnOneLevelMatch:=cn=Databases,cn=Monitor)" MONFILTER ")";
+ } else {
+ monfilter = MONFILTER;
+ }
+
+ if ( sids || numservers > 1 ) {
+ for ( i=0; i<numservers; i++ )
+ if ( sids )
+ servers[i].sid = atoi(sids[i]);
+ else
+ servers[i].sid = i+1;
+ }
+
+ for ( i = 0; i < numservers; i++ ) {
+ servers[i].url = argv[i];
+ servers[i].times = calloc( numservers, sizeof(activity));
+ servers[i].csn_curr.vals = calloc( numservers, sizeof(struct berval));
+ servers[i].csn_prev.vals = calloc( numservers, sizeof(struct berval));
+ servers[i].csn_curr.tvs = calloc( numservers, sizeof(struct timeval));
+ servers[i].csn_prev.tvs = calloc( numservers, sizeof(struct timeval));
+ }
+
+ msg1 = malloc( numservers * 2 * sizeof(int));
+ msg2 = msg1 + numservers;
+
+ for (;;) {
+ LDAPMessage *res = NULL, *e = NULL;
+ BerElement *ber = NULL;
+ struct berval dn, bv, *bvals, **bvp = &bvals;
+ struct timeval tv;
+ LDAP *ld;
+
+ for (i=0; i<numservers; i++) {
+ if ( !servers[i].ld || !(servers[i].flags & WAS_LATE )) {
+ msg1[i] = 0;
+ msg2[i] = 0;
+ }
+ if ( !servers[i].ld ) {
+ setup_server( config, &servers[i] );
+ } else {
+ ld = servers[i].ld;
+ rc = -1;
+ if ( servers[i].flags & WAS_DOWN )
+ servers[i].flags ^= WAS_DOWN;
+ if (( servers[i].flags & HAS_MONITOR ) && !msg1[i] ) {
+ char *attrs[3] = { at_monitorOpCompleted.bv_val };
+ if ( servers[i].flags & HAS_ENTRIES )
+ attrs[1] = at_olmMDBEntries.bv_val;
+ rc = ldap_search_ext( ld, "cn=monitor",
+ LDAP_SCOPE_SUBTREE, servers[i].monitorfilter,
+ attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msg1[i] );
+ if ( rc != LDAP_SUCCESS ) {
+ tester_ldap_error( ld, "ldap_search_ext(cn=Monitor)", servers[i].url );
+server_down1:
+ ldap_unbind_ext( ld, NULL, NULL );
+ servers[i].flags |= WAS_DOWN;
+ servers[i].ld = NULL;
+ gettimeofday( &tv, NULL );
+ servers[i].down = tv.tv_sec;
+ msg1[i] = 0;
+ msg2[i] = 0;
+ continue;
+ }
+ }
+ if (( servers[i].flags & HAS_BASE ) && !msg2[i] ) {
+ char *attrs[2] = { at_contextCSN.bv_val };
+ rc = ldap_search_ext( ld, cbase.bv_val,
+ LDAP_SCOPE_BASE, "(objectClass=*)",
+ attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msg2[i] );
+ if ( rc != LDAP_SUCCESS ) {
+ tester_ldap_error( ld, "ldap_search_ext(baseDN)", servers[i].url );
+ goto server_down1;
+ }
+ }
+ if ( rc != -1 )
+ gettimeofday( &servers[i].c_curr.time, 0 );
+ }
+ }
+
+ for (i=0; i<numservers; i++) {
+ ld = servers[i].ld;
+ if ( msg1[i] ) {
+ tv.tv_sec = 0;
+ tv.tv_usec = 250000;
+ rc = ldap_result( ld, msg1[i], LDAP_MSG_ALL, &tv, &res );
+ if ( rc < 0 ) {
+ tester_ldap_error( ld, "ldap_result(cn=Monitor)", servers[i].url );
+server_down2:
+ ldap_unbind_ext( ld, NULL, NULL );
+ servers[i].flags |= WAS_DOWN;
+ servers[i].ld = NULL;
+ servers[i].down = servers[i].c_curr.time.tv_sec;
+ msg1[i] = 0;
+ msg2[i] = 0;
+ continue;
+ }
+ if ( rc == 0 ) {
+ if ( !( servers[i].flags & WAS_LATE ))
+ servers[i].late = servers[i].c_curr.time.tv_sec;
+ servers[i].flags |= WAS_LATE;
+ continue;
+ }
+ if ( servers[i].flags & WAS_LATE )
+ servers[i].flags ^= WAS_LATE;
+ for ( e = ldap_first_entry( ld, res ); e; e = ldap_next_entry( ld, e )) {
+ ldap_get_dn_ber( ld, e, &ber, &dn );
+ if ( !strncasecmp( dn.bv_val, "cn=Database", sizeof("cn=Database")-1 ) ||
+ !strncasecmp( dn.bv_val, "cn=Frontend", sizeof("cn=Frontend")-1 )) {
+ for ( rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp );
+ rc == LDAP_SUCCESS;
+ rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp )) {
+ if ( bv.bv_val == NULL ) break;
+ if ( !ber_bvcmp( &bv, &at_olmMDBEntries )) {
+ if ( !BER_BVISNULL( &servers[i].monitorbase )) {
+ servers[i].c_curr.entries = strtoul( bvals[0].bv_val, NULL, 0 );
+ }
+ }
+ ber_memfree( bvals );
+ bvals = NULL;
+ }
+ } else if (!strncasecmp( dn.bv_val, opnames[0].rdn.bv_val,
+ opnames[0].rdn.bv_len )) {
+ get_counters( ld, e, ber, &servers[i].c_curr );
+ break;
+ }
+ if ( ber )
+ ber_free( ber, 0 );
+ }
+ ldap_msgfree( res );
+ }
+ if ( msg2[i] ) {
+ tv.tv_sec = 0;
+ tv.tv_usec = 250000;
+ rc = ldap_result( ld, msg2[i], LDAP_MSG_ALL, &tv, &res );
+ if ( rc < 0 ) {
+ tester_ldap_error( ld, "ldap_result(baseDN)", servers[i].url );
+ goto server_down2;
+ }
+ if ( rc == 0 ) {
+ if ( !( servers[i].flags & WAS_LATE ))
+ servers[i].late = servers[i].c_curr.time.tv_sec;
+ servers[i].flags |= WAS_LATE;
+ continue;
+ }
+ if ( servers[i].flags & WAS_LATE )
+ servers[i].flags ^= WAS_LATE;
+ e = ldap_first_entry( ld, res );
+ if ( e ) {
+ ldap_get_dn_ber( ld, e, &ber, &dn );
+ for ( rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp );
+ rc == LDAP_SUCCESS;
+ rc = ldap_get_attribute_ber( ld, e, ber, &bv, bvp )) {
+ int done = 0;
+ if ( bv.bv_val == NULL ) break;
+ if ( bvals ) {
+ if ( !ber_bvcmp( &bv, &at_contextCSN )) {
+ get_csns( &servers[i].csn_curr, bvals );
+ done = 1;
+ }
+ ber_memfree( bvals );
+ bvals = NULL;
+ if ( done )
+ break;
+ }
+ }
+ }
+ ldap_msgfree( res );
+ }
+ }
+ display();
+ sleep(interval);
+ }
+
+ exit( EXIT_SUCCESS );
+}
+