xref: /netbsd-src/external/bsd/openldap/dist/tests/progs/slapd-bind.c (revision c42dbd0ed2e61fe6eda8590caa852ccf34719964)
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 
48 static int
49 do_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 
52 static int
53 do_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  */
63 static void
64 usage( 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 
80 int
81 main( 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 
205 static int
206 do_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 
372 static int
373 do_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 ) {
434 novals:;
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