1/* $OpenLDAP$ */
2/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
3 *
4 * Copyright 1999-2011 The OpenLDAP Foundation.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted only as authorized by the OpenLDAP
9 * Public License.
10 *
11 * A copy of this license is available in file LICENSE in the
12 * top-level directory of the distribution or, alternatively, at
13 * <http://www.OpenLDAP.org/license.html>.
14 */
15/* ACKNOWLEDGEMENTS:
16 * This work was initially developed by Kurt Spanier for inclusion
17 * in OpenLDAP Software.
18 */
19
20#include "portable.h"
21
22#include <stdio.h>
23
24#include "ac/stdlib.h"
25
26#include "ac/ctype.h"
27#include "ac/param.h"
28#include "ac/socket.h"
29#include "ac/string.h"
30#include "ac/unistd.h"
31#include "ac/wait.h"
32
33#include "ldap.h"
34#include "lutil.h"
35#include "ldap_pvt.h"
36
37#include "slapd-common.h"
38
39#define LOOPS	100
40#define RETRIES	0
41
42static void
43do_search( char *uri, char *manager, struct berval *passwd,
44	char *sbase, int scope, char *filter, LDAP **ldp,
45	char **attrs, int noattrs, int nobind,
46	int innerloop, int maxretries, int delay, int force, int chaserefs );
47
48static void
49do_random( char *uri, char *manager, struct berval *passwd,
50	char *sbase, int scope, char *filter, char *attr,
51	char **attrs, int noattrs, int nobind,
52	int innerloop, int maxretries, int delay, int force, int chaserefs );
53
54static void
55usage( char *name, char o )
56{
57	if ( o != '\0' ) {
58		fprintf( stderr, "unknown/incorrect option \"%c\"\n", o );
59	}
60
61        fprintf( stderr,
62		"usage: %s "
63		"-H <uri> | ([-h <host>] -p <port>) "
64		"-D <manager> "
65		"-w <passwd> "
66		"-b <searchbase> "
67		"-s <scope> "
68		"-f <searchfilter> "
69		"[-a <attr>] "
70		"[-A] "
71		"[-C] "
72		"[-F] "
73		"[-N] "
74		"[-S[S[S]]] "
75		"[-i <ignore>] "
76		"[-l <loops>] "
77		"[-L <outerloops>] "
78		"[-r <maxretries>] "
79		"[-t <delay>] "
80		"[<attrs>] "
81		"\n",
82			name );
83	exit( EXIT_FAILURE );
84}
85
86/* -S: just send requests without reading responses
87 * -SS: send all requests asynchronous and immediately start reading responses
88 * -SSS: send all requests asynchronous; then read responses
89 */
90static int swamp;
91
92int
93main( int argc, char **argv )
94{
95	int		i;
96	char		*uri = NULL;
97	char		*host = "localhost";
98	int		port = -1;
99	char		*manager = NULL;
100	struct berval	passwd = { 0, NULL };
101	char		*sbase = NULL;
102	int		scope = LDAP_SCOPE_SUBTREE;
103	char		*filter  = NULL;
104	char		*attr = NULL;
105	char		*srchattrs[] = { "cn", "sn", NULL };
106	char		**attrs = srchattrs;
107	int		loops = LOOPS;
108	int		outerloops = 1;
109	int		retries = RETRIES;
110	int		delay = 0;
111	int		force = 0;
112	int		chaserefs = 0;
113	int		noattrs = 0;
114	int		nobind = 0;
115
116	tester_init( "slapd-search", TESTER_SEARCH );
117
118	/* by default, tolerate referrals and no such object */
119	tester_ignore_str2errlist( "REFERRAL,NO_SUCH_OBJECT" );
120
121	while ( ( i = getopt( argc, argv, "Aa:b:CD:f:FH:h:i:l:L:Np:r:Ss:t:T:w:" ) ) != EOF )
122	{
123		switch ( i ) {
124		case 'A':
125			noattrs++;
126			break;
127
128		case 'C':
129			chaserefs++;
130			break;
131
132		case 'H':		/* the server uri */
133			uri = strdup( optarg );
134			break;
135
136		case 'h':		/* the servers host */
137			host = strdup( optarg );
138			break;
139
140		case 'i':
141			tester_ignore_str2errlist( optarg );
142			break;
143
144		case 'N':
145			nobind++;
146			break;
147
148		case 'p':		/* the servers port */
149			if ( lutil_atoi( &port, optarg ) != 0 ) {
150				usage( argv[0], i );
151			}
152			break;
153
154		case 'D':		/* the servers manager */
155			manager = strdup( optarg );
156			break;
157
158		case 'w':		/* the server managers password */
159			passwd.bv_val = strdup( optarg );
160			passwd.bv_len = strlen( optarg );
161			memset( optarg, '*', passwd.bv_len );
162			break;
163
164		case 'a':
165			attr = strdup( optarg );
166			break;
167
168		case 'b':		/* file with search base */
169			sbase = strdup( optarg );
170			break;
171
172		case 'f':		/* the search request */
173			filter = strdup( optarg );
174			break;
175
176		case 'F':
177			force++;
178			break;
179
180		case 'l':		/* number of loops */
181			if ( lutil_atoi( &loops, optarg ) != 0 ) {
182				usage( argv[0], i );
183			}
184			break;
185
186		case 'L':		/* number of loops */
187			if ( lutil_atoi( &outerloops, optarg ) != 0 ) {
188				usage( argv[0], i );
189			}
190			break;
191
192		case 'r':		/* number of retries */
193			if ( lutil_atoi( &retries, optarg ) != 0 ) {
194				usage( argv[0], i );
195			}
196			break;
197
198		case 't':		/* delay in seconds */
199			if ( lutil_atoi( &delay, optarg ) != 0 ) {
200				usage( argv[0], i );
201			}
202			break;
203
204		case 'T':
205			attrs = ldap_str2charray( optarg, "," );
206			if ( attrs == NULL ) {
207				usage( argv[0], i );
208			}
209			break;
210
211		case 'S':
212			swamp++;
213			break;
214
215		case 's':
216			scope = ldap_pvt_str2scope( optarg );
217			if ( scope == -1 ) {
218				usage( argv[0], i );
219			}
220			break;
221
222		default:
223			usage( argv[0], i );
224			break;
225		}
226	}
227
228	if (( sbase == NULL ) || ( filter == NULL ) || ( port == -1 && uri == NULL ))
229		usage( argv[0], '\0' );
230
231	if ( *filter == '\0' ) {
232
233		fprintf( stderr, "%s: invalid EMPTY search filter.\n",
234				argv[0] );
235		exit( EXIT_FAILURE );
236
237	}
238
239	if ( argv[optind] != NULL ) {
240		attrs = &argv[optind];
241	}
242
243	uri = tester_uri( uri, host, port );
244
245	for ( i = 0; i < outerloops; i++ ) {
246		if ( attr != NULL ) {
247			do_random( uri, manager, &passwd,
248				sbase, scope, filter, attr,
249				attrs, noattrs, nobind,
250				loops, retries, delay, force, chaserefs );
251
252		} else {
253			do_search( uri, manager, &passwd,
254				sbase, scope, filter, NULL,
255				attrs, noattrs, nobind,
256				loops, retries, delay, force, chaserefs );
257		}
258	}
259
260	exit( EXIT_SUCCESS );
261}
262
263
264static void
265do_random( char *uri, char *manager, struct berval *passwd,
266	char *sbase, int scope, char *filter, char *attr,
267	char **srchattrs, int noattrs, int nobind,
268	int innerloop, int maxretries, int delay, int force, int chaserefs )
269{
270	LDAP	*ld = NULL;
271	int  	i = 0, do_retry = maxretries;
272	char	*attrs[ 2 ];
273	int     rc = LDAP_SUCCESS;
274	int	version = LDAP_VERSION3;
275	int	nvalues = 0;
276	char	**values = NULL;
277	LDAPMessage *res = NULL, *e = NULL;
278
279	attrs[ 0 ] = attr;
280	attrs[ 1 ] = NULL;
281
282	ldap_initialize( &ld, uri );
283	if ( ld == NULL ) {
284		tester_perror( "ldap_initialize", NULL );
285		exit( EXIT_FAILURE );
286	}
287
288	(void) ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version );
289	(void) ldap_set_option( ld, LDAP_OPT_REFERRALS,
290		chaserefs ? LDAP_OPT_ON : LDAP_OPT_OFF );
291
292	if ( do_retry == maxretries ) {
293		fprintf( stderr, "PID=%ld - Search(%d): base=\"%s\", filter=\"%s\" attr=\"%s\".\n",
294				(long) pid, innerloop, sbase, filter, attr );
295	}
296
297	if ( nobind == 0 ) {
298		rc = ldap_sasl_bind_s( ld, manager, LDAP_SASL_SIMPLE, passwd, NULL, NULL, NULL );
299		if ( rc != LDAP_SUCCESS ) {
300			tester_ldap_error( ld, "ldap_sasl_bind_s", NULL );
301			switch ( rc ) {
302			case LDAP_BUSY:
303			case LDAP_UNAVAILABLE:
304			/* fallthru */
305			default:
306				break;
307			}
308			exit( EXIT_FAILURE );
309		}
310	}
311
312	rc = ldap_search_ext_s( ld, sbase, LDAP_SCOPE_SUBTREE,
313		filter, attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &res );
314	switch ( rc ) {
315	case LDAP_SIZELIMIT_EXCEEDED:
316	case LDAP_TIMELIMIT_EXCEEDED:
317	case LDAP_SUCCESS:
318		if ( ldap_count_entries( ld, res ) == 0 ) {
319			if ( rc ) {
320				tester_ldap_error( ld, "ldap_search_ext_s", NULL );
321			}
322			break;
323		}
324
325		for ( e = ldap_first_entry( ld, res ); e != NULL; e = ldap_next_entry( ld, e ) )
326		{
327			struct berval **v = ldap_get_values_len( ld, e, attr );
328
329			if ( v != NULL ) {
330				int n = ldap_count_values_len( v );
331				int j;
332
333				values = realloc( values, ( nvalues + n + 1 )*sizeof( char * ) );
334				for ( j = 0; j < n; j++ ) {
335					values[ nvalues + j ] = strdup( v[ j ]->bv_val );
336				}
337				values[ nvalues + j ] = NULL;
338				nvalues += n;
339				ldap_value_free_len( v );
340			}
341		}
342
343		ldap_msgfree( res );
344
345		if ( !values ) {
346			fprintf( stderr, "  PID=%ld - Search base=\"%s\" filter=\"%s\" got %d values.\n",
347				(long) pid, sbase, filter, nvalues );
348			exit(EXIT_FAILURE);
349		}
350
351		if ( do_retry == maxretries ) {
352			fprintf( stderr, "  PID=%ld - Search base=\"%s\" filter=\"%s\" got %d values.\n",
353				(long) pid, sbase, filter, nvalues );
354		}
355
356		for ( i = 0; i < innerloop; i++ ) {
357			char	buf[ BUFSIZ ];
358#if 0	/* use high-order bits for better randomness (Numerical Recipes in "C") */
359			int	r = rand() % nvalues;
360#endif
361			int	r = ((double)nvalues)*rand()/(RAND_MAX + 1.0);
362
363			snprintf( buf, sizeof( buf ), "(%s=%s)", attr, values[ r ] );
364
365			do_search( uri, manager, passwd,
366				sbase, scope, buf, &ld,
367				srchattrs, noattrs, nobind,
368				1, maxretries, delay, force, chaserefs );
369		}
370		break;
371
372	default:
373		tester_ldap_error( ld, "ldap_search_ext_s", NULL );
374		break;
375	}
376
377	fprintf( stderr, "  PID=%ld - Search done (%d).\n", (long) pid, rc );
378
379	if ( ld != NULL ) {
380		ldap_unbind_ext( ld, NULL, NULL );
381	}
382}
383
384static void
385do_search( char *uri, char *manager, struct berval *passwd,
386	char *sbase, int scope, char *filter, LDAP **ldp,
387	char **attrs, int noattrs, int nobind,
388	int innerloop, int maxretries, int delay, int force, int chaserefs )
389{
390	LDAP	*ld = ldp ? *ldp : NULL;
391	int  	i = 0, do_retry = maxretries;
392	int     rc = LDAP_SUCCESS;
393	int	version = LDAP_VERSION3;
394	char	buf[ BUFSIZ ];
395	int		*msgids = NULL, active = 0;
396
397	/* make room for msgid */
398	if ( swamp > 1 ) {
399		msgids = (int *)calloc( sizeof(int), innerloop );
400	}
401
402retry:;
403	if ( ld == NULL ) {
404		ldap_initialize( &ld, uri );
405		if ( ld == NULL ) {
406			tester_perror( "ldap_initialize", NULL );
407			exit( EXIT_FAILURE );
408		}
409
410		(void) ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version );
411		(void) ldap_set_option( ld, LDAP_OPT_REFERRALS,
412			chaserefs ? LDAP_OPT_ON : LDAP_OPT_OFF );
413
414		if ( do_retry == maxretries ) {
415			fprintf( stderr,
416				"PID=%ld - Search(%d): "
417				"base=\"%s\" scope=%s filter=\"%s\" "
418				"attrs=%s%s.\n",
419				(long) pid, innerloop,
420				sbase, ldap_pvt_scope2str( scope ), filter,
421				attrs[0], attrs[1] ? " (more...)" : "" );
422		}
423
424		if ( nobind == 0 ) {
425			rc = ldap_sasl_bind_s( ld, manager, LDAP_SASL_SIMPLE, passwd, NULL, NULL, NULL );
426			if ( rc != LDAP_SUCCESS ) {
427				snprintf( buf, sizeof( buf ),
428					"bindDN=\"%s\"", manager );
429				tester_ldap_error( ld, "ldap_sasl_bind_s", buf );
430				switch ( rc ) {
431				case LDAP_BUSY:
432				case LDAP_UNAVAILABLE:
433					if ( do_retry > 0 ) {
434						ldap_unbind_ext( ld, NULL, NULL );
435						ld = NULL;
436						do_retry--;
437						if ( delay != 0 ) {
438						    sleep( delay );
439						}
440						goto retry;
441					}
442				/* fallthru */
443				default:
444					break;
445				}
446				exit( EXIT_FAILURE );
447			}
448		}
449	}
450
451	if ( swamp > 1 ) {
452		do {
453			LDAPMessage *res = NULL;
454			int j, msgid;
455
456			if ( i < innerloop ) {
457				rc = ldap_search_ext( ld, sbase, scope,
458						filter, NULL, noattrs, NULL, NULL,
459						NULL, LDAP_NO_LIMIT, &msgids[i] );
460
461				active++;
462#if 0
463				fprintf( stderr,
464					">>> PID=%ld - Search maxloop=%d cnt=%d active=%d msgid=%d: "
465					"base=\"%s\" scope=%s filter=\"%s\"\n",
466					(long) pid, innerloop, i, active, msgids[i],
467					sbase, ldap_pvt_scope2str( scope ), filter );
468#endif
469				i++;
470
471				if ( rc ) {
472					int first = tester_ignore_err( rc );
473					/* if ignore.. */
474					if ( first ) {
475						/* only log if first occurrence */
476						if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) {
477							tester_ldap_error( ld, "ldap_search_ext", NULL );
478						}
479						continue;
480					}
481
482					/* busy needs special handling */
483					snprintf( buf, sizeof( buf ),
484						"base=\"%s\" filter=\"%s\"\n",
485						sbase, filter );
486					tester_ldap_error( ld, "ldap_search_ext", buf );
487					if ( rc == LDAP_BUSY && do_retry > 0 ) {
488						ldap_unbind_ext( ld, NULL, NULL );
489						ld = NULL;
490						do_retry--;
491						goto retry;
492					}
493					break;
494				}
495
496				if ( swamp > 2 ) {
497					continue;
498				}
499			}
500
501			rc = ldap_result( ld, LDAP_RES_ANY, 0, NULL, &res );
502			switch ( rc ) {
503			case -1:
504				/* gone really bad */
505				goto cleanup;
506
507			case 0:
508				/* timeout (impossible) */
509				break;
510
511			case LDAP_RES_SEARCH_ENTRY:
512			case LDAP_RES_SEARCH_REFERENCE:
513				/* ignore */
514				break;
515
516			case LDAP_RES_SEARCH_RESULT:
517				/* just remove, no error checking (TODO?) */
518				msgid = ldap_msgid( res );
519				ldap_parse_result( ld, res, &rc, NULL, NULL, NULL, NULL, 1 );
520				res = NULL;
521
522				/* linear search, bah */
523				for ( j = 0; j < i; j++ ) {
524					if ( msgids[ j ] == msgid ) {
525						msgids[ j ] = -1;
526						active--;
527#if 0
528						fprintf( stderr,
529							"<<< PID=%ld - SearchDone maxloop=%d cnt=%d active=%d msgid=%d: "
530							"base=\"%s\" scope=%s filter=\"%s\"\n",
531							(long) pid, innerloop, j, active, msgid,
532							sbase, ldap_pvt_scope2str( scope ), filter );
533#endif
534						break;
535					}
536				}
537				break;
538
539			default:
540				/* other messages unexpected */
541				fprintf( stderr,
542					"### PID=%ld - Search(%d): "
543					"base=\"%s\" scope=%s filter=\"%s\" "
544					"attrs=%s%s. unexpected response tag=%d\n",
545					(long) pid, innerloop,
546					sbase, ldap_pvt_scope2str( scope ), filter,
547					attrs[0], attrs[1] ? " (more...)" : "", rc );
548				break;
549			}
550
551			if ( res != NULL ) {
552				ldap_msgfree( res );
553			}
554		} while ( i < innerloop || active > 0 );
555
556	} else {
557		for ( ; i < innerloop; i++ ) {
558			LDAPMessage *res = NULL;
559
560			if (swamp) {
561				int msgid;
562				rc = ldap_search_ext( ld, sbase, scope,
563						filter, NULL, noattrs, NULL, NULL,
564						NULL, LDAP_NO_LIMIT, &msgid );
565				if ( rc == LDAP_SUCCESS ) continue;
566				else break;
567			}
568
569			rc = ldap_search_ext_s( ld, sbase, scope,
570					filter, attrs, noattrs, NULL, NULL,
571					NULL, LDAP_NO_LIMIT, &res );
572			if ( res != NULL ) {
573				ldap_msgfree( res );
574			}
575
576			if ( rc ) {
577				int first = tester_ignore_err( rc );
578				/* if ignore.. */
579				if ( first ) {
580					/* only log if first occurrence */
581					if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) {
582						tester_ldap_error( ld, "ldap_search_ext_s", NULL );
583					}
584					continue;
585				}
586
587				/* busy needs special handling */
588				snprintf( buf, sizeof( buf ),
589					"base=\"%s\" filter=\"%s\"\n",
590					sbase, filter );
591				tester_ldap_error( ld, "ldap_search_ext_s", buf );
592				if ( rc == LDAP_BUSY && do_retry > 0 ) {
593					ldap_unbind_ext( ld, NULL, NULL );
594					ld = NULL;
595					do_retry--;
596					goto retry;
597				}
598				break;
599			}
600		}
601	}
602
603cleanup:;
604	if ( msgids != NULL ) {
605		free( msgids );
606	}
607
608	if ( ldp != NULL ) {
609		*ldp = ld;
610
611	} else {
612		fprintf( stderr, "  PID=%ld - Search done (%d).\n", (long) pid, rc );
613
614		if ( ld != NULL ) {
615			ldap_unbind_ext( ld, NULL, NULL );
616		}
617	}
618}
619