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