1/*	$NetBSD: slapd-bind.c,v 1.3 2021/08/14 16:15:03 christos Exp $	*/
2
3/* $OpenLDAP$ */
4/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5 *
6 * Copyright 1999-2021 The OpenLDAP Foundation.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted only as authorized by the OpenLDAP
11 * Public License.
12 *
13 * A copy of this license is available in file LICENSE in the
14 * top-level directory of the distribution or, alternatively, at
15 * <http://www.OpenLDAP.org/license.html>.
16 */
17/* ACKNOWLEDGEMENTS:
18 * This work was initially developed by Howard Chu for inclusion
19 * in OpenLDAP Software.
20 */
21
22#include <sys/cdefs.h>
23__RCSID("$NetBSD: slapd-bind.c,v 1.3 2021/08/14 16:15:03 christos Exp $");
24
25#include "portable.h"
26
27#include <stdio.h>
28
29#include "ac/stdlib.h"
30#include "ac/time.h"
31
32#include "ac/ctype.h"
33#include "ac/param.h"
34#include "ac/socket.h"
35#include "ac/string.h"
36#include "ac/unistd.h"
37#include "ac/wait.h"
38#include "ac/time.h"
39
40#include "ldap.h"
41#include "lutil.h"
42#include "lutil_ldap.h"
43#include "lber_pvt.h"
44#include "ldap_pvt.h"
45
46#include "slapd-common.h"
47
48static int
49do_bind( struct tester_conn_args *config, char *dn, int maxloop, int force,
50	int noinit, LDAP **ldp, struct berval *pass, int action_type, void *action );
51
52static int
53do_base( struct tester_conn_args *config, char *dn, char *base, char *filter, char *pwattr,
54	int force, int noinit, int action_type, void *action );
55
56/* This program can be invoked two ways: if -D is used to specify a Bind DN,
57 * that DN will be used repeatedly for all of the Binds. If instead -b is used
58 * to specify a base DN, a search will be done for all "person" objects under
59 * that base DN. Then DNs from this list will be randomly selected for each
60 * Bind request. All of the users must have identical passwords. Also it is
61 * assumed that the users are all onelevel children of the base.
62 */
63static void
64usage( char *name, char opt )
65{
66	if ( opt ) {
67		fprintf( stderr, "%s: unable to handle option \'%c\'\n\n",
68			name, opt );
69	}
70
71	fprintf( stderr, "usage: %s " TESTER_COMMON_HELP
72		"[-b <baseDN> [-f <searchfilter>] [-a pwattr]] "
73		"[-B <extra>[,...]] "
74		"[-F] "
75		"[-I]\n",
76		name );
77	exit( EXIT_FAILURE );
78}
79
80int
81main( int argc, char **argv )
82{
83	int		i;
84	char		*base = NULL;
85	char		*filter = "(objectClass=person)";
86	char		*pwattr = NULL;
87	int		force = 0;
88	int		noinit = 1;
89	struct tester_conn_args	*config;
90
91	/* extra action to do after bind... */
92	struct berval	type[] = {
93		BER_BVC( "tester=" ),
94		BER_BVC( "add=" ),
95		BER_BVC( "bind=" ),
96		BER_BVC( "modify=" ),
97		BER_BVC( "modrdn=" ),
98		BER_BVC( "read=" ),
99		BER_BVC( "search=" ),
100		BER_BVNULL
101	};
102
103	LDAPURLDesc	*extra_ludp = NULL;
104
105	config = tester_init( "slapd-bind", TESTER_BIND );
106
107	/* by default, tolerate invalid credentials */
108	tester_ignore_str2errlist( "*INVALID_CREDENTIALS" );
109
110	while ( ( i = getopt( argc, argv, TESTER_COMMON_OPTS "a:B:b:Ff:I" ) ) != EOF )
111	{
112		switch ( i ) {
113		case 'a':
114			pwattr = optarg;
115			break;
116
117		case 'b':		/* base DN of a tree of user DNs */
118			base = optarg;
119			break;
120
121		case 'B':
122			{
123			int	c;
124
125			for ( c = 0; type[c].bv_val; c++ ) {
126				if ( strncasecmp( optarg, type[c].bv_val, type[c].bv_len ) == 0 )
127				{
128					break;
129				}
130			}
131
132			if ( type[c].bv_val == NULL ) {
133				usage( argv[0], 'B' );
134			}
135
136			switch ( c ) {
137			case TESTER_TESTER:
138			case TESTER_BIND:
139				/* invalid */
140				usage( argv[0], 'B' );
141
142			case TESTER_SEARCH:
143				{
144				if ( ldap_url_parse( &optarg[type[c].bv_len], &extra_ludp ) != LDAP_URL_SUCCESS )
145				{
146					usage( argv[0], 'B' );
147				}
148				} break;
149
150			case TESTER_ADDEL:
151			case TESTER_MODIFY:
152			case TESTER_MODRDN:
153			case TESTER_READ:
154				/* nothing to do */
155				break;
156
157			default:
158				assert( 0 );
159			}
160
161			} break;
162
163		case 'f':
164			filter = optarg;
165			break;
166
167		case 'F':
168			force++;
169			break;
170
171		case 'I':
172			/* reuse connection */
173			noinit = 0;
174			break;
175
176		default:
177			if ( tester_config_opt( config, i, optarg ) == LDAP_SUCCESS ) {
178				break;
179			}
180			usage( argv[0], i );
181			break;
182		}
183	}
184
185	tester_config_finish( config );
186
187	for ( i = 0; i < config->outerloops; i++ ) {
188		int rc;
189
190		if ( base != NULL ) {
191			rc = do_base( config, config->binddn, base,
192				filter, pwattr, force, noinit, -1, NULL );
193		} else {
194			rc = do_bind( config, config->binddn,
195				config->loops, force, noinit, NULL, &config->pass, -1, NULL );
196		}
197		if ( rc == LDAP_SERVER_DOWN )
198			break;
199	}
200
201	exit( EXIT_SUCCESS );
202}
203
204
205static int
206do_bind( struct tester_conn_args *config, char *dn, int maxloop, int force,
207	int noinit, LDAP **ldp, struct berval *pass, int action_type, void *action )
208{
209	LDAP	*ld = ldp ? *ldp : NULL;
210	char	*bindfunc = "ldap_sasl_bind_s";
211	int  	i, rc = -1;
212
213	/* for internal search */
214	int	timelimit = 0;
215	int	sizelimit = 0;
216
217	switch ( action_type ) {
218	case -1:
219		break;
220
221	case TESTER_SEARCH:
222		{
223		LDAPURLDesc	*ludp = (LDAPURLDesc *)action;
224
225		assert( action != NULL );
226
227		if ( ludp->lud_exts != NULL ) {
228			for ( i = 0; ludp->lud_exts[ i ] != NULL; i++ ) {
229				char	*ext = ludp->lud_exts[ i ];
230				int	crit = 0;
231
232				if (ext[0] == '!') {
233					crit++;
234					ext++;
235				}
236
237				if ( strncasecmp( ext, "x-timelimit=", STRLENOF( "x-timelimit=" ) ) == 0 ) {
238					if ( lutil_atoi( &timelimit, &ext[ STRLENOF( "x-timelimit=" ) ] ) && crit ) {
239						tester_error( "unable to parse critical extension x-timelimit" );
240					}
241
242				} else if ( strncasecmp( ext, "x-sizelimit=", STRLENOF( "x-sizelimit=" ) ) == 0 ) {
243					if ( lutil_atoi( &sizelimit, &ext[ STRLENOF( "x-sizelimit=" ) ] ) && crit ) {
244						tester_error( "unable to parse critical extension x-sizelimit" );
245					}
246
247				} else if ( crit ) {
248					tester_error( "unknown critical extension" );
249				}
250			}
251		}
252		} break;
253
254	default:
255		/* nothing to do yet */
256		break;
257	}
258
259	if ( maxloop > 1 ) {
260		fprintf( stderr, "PID=%ld - Bind(%d): dn=\"%s\".\n",
261			 (long) pid, maxloop, dn );
262	}
263
264	for ( i = 0; i < maxloop; i++ ) {
265		if ( !noinit || ld == NULL ) {
266			tester_init_ld( &ld, config, TESTER_INIT_ONLY );
267
268#ifdef HAVE_CYRUS_SASL
269			if ( config->secprops != NULL ) {
270				rc = ldap_set_option( ld,
271						LDAP_OPT_X_SASL_SECPROPS, config->secprops );
272
273				if( rc != LDAP_OPT_SUCCESS ) {
274					tester_ldap_error( ld, "ldap_set_option(SECPROPS)", NULL );
275					exit( EXIT_FAILURE );
276				}
277			}
278#endif
279		}
280
281		if ( config->authmethod == LDAP_AUTH_SASL ) {
282#ifdef HAVE_CYRUS_SASL
283			bindfunc = "ldap_sasl_interactive_bind_s";
284			rc = ldap_sasl_interactive_bind_s( ld,
285					dn,
286					config->mech,
287					NULL, NULL,
288					LDAP_SASL_QUIET,
289					lutil_sasl_interact,
290					config->defaults );
291#else /* HAVE_CYRUS_SASL */
292			/* caller shouldn't have allowed this */
293			assert(0);
294#endif
295		} else if ( config->authmethod == LDAP_AUTH_SIMPLE ) {
296			bindfunc = "ldap_sasl_bind_s";
297			rc = ldap_sasl_bind_s( ld,
298					dn, LDAP_SASL_SIMPLE,
299					pass, NULL, NULL, NULL );
300		}
301
302		if ( rc ) {
303			int first = tester_ignore_err( rc );
304
305			/* if ignore.. */
306			if ( first ) {
307				/* only log if first occurrence */
308				if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) {
309					tester_ldap_error( ld, bindfunc, NULL );
310				}
311				rc = LDAP_SUCCESS;
312
313			} else {
314				tester_ldap_error( ld, bindfunc, NULL );
315			}
316		}
317
318		switch ( action_type ) {
319		case -1:
320			break;
321
322		case TESTER_SEARCH:
323			{
324			LDAPURLDesc	*ludp = (LDAPURLDesc *)action;
325			LDAPMessage	*res = NULL;
326			struct timeval	tv = { 0 }, *tvp = NULL;
327
328			if ( timelimit ) {
329				tv.tv_sec = timelimit;
330				tvp = &tv;
331			}
332
333			assert( action != NULL );
334
335			rc = ldap_search_ext_s( ld,
336				ludp->lud_dn, ludp->lud_scope,
337				ludp->lud_filter, ludp->lud_attrs, 0,
338				NULL, NULL, tvp, sizelimit, &res );
339			ldap_msgfree( res );
340			} break;
341
342		default:
343			/* nothing to do yet */
344			break;
345		}
346
347		if ( !noinit ) {
348			ldap_unbind_ext( ld, NULL, NULL );
349			ld = NULL;
350		}
351
352		if ( rc != LDAP_SUCCESS ) {
353			break;
354		}
355	}
356
357	if ( maxloop > 1 ) {
358		fprintf( stderr, "  PID=%ld - Bind done (%d).\n", (long) pid, rc );
359	}
360
361	if ( ldp && noinit ) {
362		*ldp = ld;
363
364	} else if ( ld != NULL ) {
365		ldap_unbind_ext( ld, NULL, NULL );
366	}
367
368	return rc;
369}
370
371
372static int
373do_base( struct tester_conn_args *config, char *dn, char *base, char *filter, char *pwattr,
374	int force, int noinit, int action_type, void *action )
375{
376	LDAP	*ld = NULL;
377	int  	i = 0;
378	int     rc = LDAP_SUCCESS;
379	ber_int_t msgid;
380	LDAPMessage *res, *msg;
381	char **dns = NULL;
382	struct berval *creds = NULL;
383	char *attrs[] = { LDAP_NO_ATTRS, NULL };
384	int ndns = 0;
385#ifdef _WIN32
386	DWORD beg, end;
387#else
388	struct timeval beg, end;
389#endif
390	char *nullstr = "";
391
392	tester_init_ld( &ld, config, 0 );
393
394	fprintf( stderr, "PID=%ld - Bind(%d): base=\"%s\", filter=\"%s\" attr=\"%s\".\n",
395			(long) pid, config->loops, base, filter, pwattr );
396
397	if ( pwattr != NULL ) {
398		attrs[ 0 ] = pwattr;
399	}
400	rc = ldap_search_ext( ld, base, LDAP_SCOPE_SUBTREE,
401			filter, attrs, 0, NULL, NULL, 0, 0, &msgid );
402	if ( rc != LDAP_SUCCESS ) {
403		tester_ldap_error( ld, "ldap_search_ext", NULL );
404		exit( EXIT_FAILURE );
405	}
406
407	while ( ( rc = ldap_result( ld, LDAP_RES_ANY, LDAP_MSG_ONE, NULL, &res ) ) > 0 )
408	{
409		BerElement *ber;
410		struct berval bv;
411		int done = 0;
412
413		for ( msg = ldap_first_message( ld, res ); msg;
414			msg = ldap_next_message( ld, msg ) )
415		{
416			switch ( ldap_msgtype( msg ) ) {
417			case LDAP_RES_SEARCH_ENTRY:
418				rc = ldap_get_dn_ber( ld, msg, &ber, &bv );
419				dns = realloc( dns, (ndns + 1)*sizeof(char *) );
420				if ( !dns ) {
421					tester_error( "realloc failed" );
422					exit( EXIT_FAILURE );
423				}
424				dns[ndns] = ber_strdup( bv.bv_val );
425				if ( pwattr != NULL ) {
426					struct berval	**values = ldap_get_values_len( ld, msg, pwattr );
427
428					creds = realloc( creds, (ndns + 1)*sizeof(struct berval) );
429					if ( !creds ) {
430						tester_error( "realloc failed" );
431						exit( EXIT_FAILURE );
432					}
433					if ( values == NULL ) {
434novals:;
435						creds[ndns].bv_len = 0;
436						creds[ndns].bv_val = nullstr;
437
438					} else {
439						static struct berval	cleartext = BER_BVC( "{CLEARTEXT} " );
440						struct berval		value = *values[ 0 ];
441
442						if ( value.bv_val[ 0 ] == '{' ) {
443							char *end = ber_bvchr( &value, '}' );
444
445							if ( end ) {
446								if ( ber_bvcmp( &value, &cleartext ) == 0 ) {
447									value.bv_val += cleartext.bv_len;
448									value.bv_len -= cleartext.bv_len;
449
450								} else {
451									ldap_value_free_len( values );
452									goto novals;
453								}
454							}
455
456						}
457
458						ber_dupbv( &creds[ndns], &value );
459						ldap_value_free_len( values );
460					}
461				}
462				ndns++;
463				ber_free( ber, 0 );
464				break;
465
466			case LDAP_RES_SEARCH_RESULT:
467				done = 1;
468				break;
469			}
470			if ( done )
471				break;
472		}
473		ldap_msgfree( res );
474		if ( done ) break;
475	}
476
477#ifdef _WIN32
478	beg = GetTickCount();
479#else
480	gettimeofday( &beg, NULL );
481#endif
482
483	if ( ndns == 0 ) {
484		tester_error( "No DNs" );
485		if ( ld != NULL ) {
486			ldap_unbind_ext( ld, NULL, NULL );
487		}
488		return 1;
489	}
490
491	fprintf( stderr, "  PID=%ld - Bind base=\"%s\" filter=\"%s\" got %d values.\n",
492		(long) pid, base, filter, ndns );
493
494	/* Ok, got list of DNs, now start binding to each */
495	for ( i = 0; i < config->loops; i++ ) {
496		struct berval *pass = &config->pass;
497		int		j;
498
499#if 0	/* use high-order bits for better randomness (Numerical Recipes in "C") */
500		j = rand() % ndns;
501#endif
502		j = ((double)ndns)*rand()/(RAND_MAX + 1.0);
503
504		if ( creds && !BER_BVISEMPTY( &creds[j] ) ) {
505			pass = &creds[j];
506		}
507
508		if ( do_bind( config, dns[j], 1, force, noinit, &ld, pass,
509			action_type, action ) && !force )
510		{
511			break;
512		}
513	}
514
515	if ( ld != NULL ) {
516		ldap_unbind_ext( ld, NULL, NULL );
517		ld = NULL;
518	}
519
520#ifdef _WIN32
521	end = GetTickCount();
522	end -= beg;
523
524	fprintf( stderr, "  PID=%ld - Bind done %d in %d.%03d seconds.\n",
525		(long) pid, i, end / 1000, end % 1000 );
526#else
527	gettimeofday( &end, NULL );
528	end.tv_usec -= beg.tv_usec;
529	if (end.tv_usec < 0 ) {
530		end.tv_usec += 1000000;
531		end.tv_sec -= 1;
532	}
533	end.tv_sec -= beg.tv_sec;
534
535	fprintf( stderr, "  PID=%ld - Bind done %d in %ld.%06ld seconds.\n",
536		(long) pid, i, (long) end.tv_sec, (long) end.tv_usec );
537#endif
538
539	if ( dns ) {
540		for ( i = 0; i < ndns; i++ ) {
541			ber_memfree( dns[i] );
542		}
543		free( dns );
544	}
545
546	if ( creds ) {
547		for ( i = 0; i < ndns; i++ ) {
548			if ( creds[i].bv_val != nullstr ) {
549				ber_memfree( creds[i].bv_val );
550			}
551		}
552		free( creds );
553	}
554
555	return 0;
556}
557