1 /* $NetBSD: slapd-search.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 Kurt Spanier for inclusion 19 * in OpenLDAP Software. 20 */ 21 22 #include <sys/cdefs.h> 23 __RCSID("$NetBSD: slapd-search.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 31 #include "ac/ctype.h" 32 #include "ac/param.h" 33 #include "ac/socket.h" 34 #include "ac/string.h" 35 #include "ac/unistd.h" 36 #include "ac/wait.h" 37 38 #include "ldap.h" 39 #include "lutil.h" 40 #include "ldap_pvt.h" 41 42 #include "slapd-common.h" 43 44 #define LOOPS 100 45 #define RETRIES 0 46 47 static void 48 do_search( struct tester_conn_args *config, 49 char *sbase, int scope, char *filter, LDAP **ldp, 50 char **attrs, int noattrs, int nobind, 51 int innerloop, int force ); 52 53 static void 54 do_random( struct tester_conn_args *config, 55 char *sbase, int scope, char *filter, char *attr, 56 char **attrs, int noattrs, int nobind, int force ); 57 58 static void 59 usage( char *name, char opt ) 60 { 61 if ( opt != '\0' ) { 62 fprintf( stderr, "unknown/incorrect option \"%c\"\n", opt ); 63 } 64 65 fprintf( stderr, "usage: %s " TESTER_COMMON_HELP 66 "-b <searchbase> " 67 "-s <scope> " 68 "-f <searchfilter> " 69 "[-a <attr>] " 70 "[-A] " 71 "[-F] " 72 "[-N] " 73 "[-S[S[S]]] " 74 "[<attrs>] " 75 "\n", 76 name ); 77 exit( EXIT_FAILURE ); 78 } 79 80 /* -S: just send requests without reading responses 81 * -SS: send all requests asynchronous and immediately start reading responses 82 * -SSS: send all requests asynchronous; then read responses 83 */ 84 static int swamp; 85 86 int 87 main( int argc, char **argv ) 88 { 89 int i; 90 char *sbase = NULL; 91 int scope = LDAP_SCOPE_SUBTREE; 92 char *filter = NULL; 93 char *attr = NULL; 94 char *srchattrs[] = { "cn", "sn", NULL }; 95 char **attrs = srchattrs; 96 int force = 0; 97 int noattrs = 0; 98 int nobind = 0; 99 struct tester_conn_args *config; 100 101 config = tester_init( "slapd-search", TESTER_SEARCH ); 102 103 /* by default, tolerate referrals and no such object */ 104 tester_ignore_str2errlist( "REFERRAL,NO_SUCH_OBJECT" ); 105 106 while ( ( i = getopt( argc, argv, TESTER_COMMON_OPTS "Aa:b:f:FNSs:T:" ) ) != EOF ) 107 { 108 switch ( i ) { 109 case 'A': 110 noattrs++; 111 break; 112 113 case 'N': 114 nobind = TESTER_INIT_ONLY; 115 break; 116 117 case 'a': 118 attr = optarg; 119 break; 120 121 case 'b': /* file with search base */ 122 sbase = optarg; 123 break; 124 125 case 'f': /* the search request */ 126 filter = optarg; 127 break; 128 129 case 'F': 130 force++; 131 break; 132 133 case 'T': 134 attrs = ldap_str2charray( optarg, "," ); 135 if ( attrs == NULL ) { 136 usage( argv[0], i ); 137 } 138 break; 139 140 case 'S': 141 swamp++; 142 break; 143 144 case 's': 145 scope = ldap_pvt_str2scope( optarg ); 146 if ( scope == -1 ) { 147 usage( argv[0], i ); 148 } 149 break; 150 151 default: 152 if ( tester_config_opt( config, i, optarg ) == LDAP_SUCCESS ) { 153 break; 154 } 155 usage( argv[0], i ); 156 break; 157 } 158 } 159 160 if (( sbase == NULL ) || ( filter == NULL )) 161 usage( argv[0], 0 ); 162 163 if ( *filter == '\0' ) { 164 165 fprintf( stderr, "%s: invalid EMPTY search filter.\n", 166 argv[0] ); 167 exit( EXIT_FAILURE ); 168 169 } 170 171 if ( argv[optind] != NULL ) { 172 attrs = &argv[optind]; 173 } 174 175 tester_config_finish( config ); 176 177 for ( i = 0; i < config->outerloops; i++ ) { 178 if ( attr != NULL ) { 179 do_random( config, 180 sbase, scope, filter, attr, 181 attrs, noattrs, nobind, force ); 182 183 } else { 184 do_search( config, sbase, scope, filter, 185 NULL, attrs, noattrs, nobind, 186 config->loops, force ); 187 } 188 } 189 190 exit( EXIT_SUCCESS ); 191 } 192 193 194 static void 195 do_random( struct tester_conn_args *config, 196 char *sbase, int scope, char *filter, char *attr, 197 char **srchattrs, int noattrs, int nobind, int force ) 198 { 199 LDAP *ld = NULL; 200 int i = 0, do_retry = config->retries; 201 char *attrs[ 2 ]; 202 int rc = LDAP_SUCCESS; 203 int nvalues = 0; 204 char **values = NULL; 205 LDAPMessage *res = NULL, *e = NULL; 206 207 attrs[ 0 ] = attr; 208 attrs[ 1 ] = NULL; 209 210 tester_init_ld( &ld, config, nobind ); 211 212 rc = ldap_search_ext_s( ld, sbase, LDAP_SCOPE_SUBTREE, 213 filter, attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &res ); 214 switch ( rc ) { 215 case LDAP_SIZELIMIT_EXCEEDED: 216 case LDAP_TIMELIMIT_EXCEEDED: 217 case LDAP_SUCCESS: 218 if ( ldap_count_entries( ld, res ) == 0 ) { 219 if ( rc ) { 220 tester_ldap_error( ld, "ldap_search_ext_s", NULL ); 221 } 222 break; 223 } 224 225 for ( e = ldap_first_entry( ld, res ); e != NULL; e = ldap_next_entry( ld, e ) ) 226 { 227 struct berval **v = ldap_get_values_len( ld, e, attr ); 228 229 if ( v != NULL ) { 230 int n = ldap_count_values_len( v ); 231 int j; 232 233 values = realloc( values, ( nvalues + n + 1 )*sizeof( char * ) ); 234 if ( !values ) { 235 tester_error( "realloc failed" ); 236 exit( EXIT_FAILURE ); 237 } 238 for ( j = 0; j < n; j++ ) { 239 values[ nvalues + j ] = strdup( v[ j ]->bv_val ); 240 } 241 values[ nvalues + j ] = NULL; 242 nvalues += n; 243 ldap_value_free_len( v ); 244 } 245 } 246 247 ldap_msgfree( res ); 248 249 if ( !values ) { 250 fprintf( stderr, " PID=%ld - Search base=\"%s\" filter=\"%s\" got %d values.\n", 251 (long) pid, sbase, filter, nvalues ); 252 exit(EXIT_FAILURE); 253 } 254 255 if ( do_retry == config->retries ) { 256 fprintf( stderr, " PID=%ld - Search base=\"%s\" filter=\"%s\" got %d values.\n", 257 (long) pid, sbase, filter, nvalues ); 258 } 259 260 for ( i = 0; i < config->loops; i++ ) { 261 char buf[ BUFSIZ ]; 262 #if 0 /* use high-order bits for better randomness (Numerical Recipes in "C") */ 263 int r = rand() % nvalues; 264 #endif 265 int r = ((double)nvalues)*rand()/(RAND_MAX + 1.0); 266 267 snprintf( buf, sizeof( buf ), "(%s=%s)", attr, values[ r ] ); 268 269 do_search( config, 270 sbase, scope, buf, &ld, 271 srchattrs, noattrs, nobind, 272 1, force ); 273 } 274 break; 275 276 default: 277 tester_ldap_error( ld, "ldap_search_ext_s", NULL ); 278 break; 279 } 280 281 fprintf( stderr, " PID=%ld - Search done (%d).\n", (long) pid, rc ); 282 283 if ( values ) { 284 for ( i = 0; i < nvalues; i++ ) { 285 free( values[i] ); 286 } 287 free( values ); 288 } 289 290 if ( ld != NULL ) { 291 ldap_unbind_ext( ld, NULL, NULL ); 292 } 293 } 294 295 static void 296 do_search( struct tester_conn_args *config, 297 char *sbase, int scope, char *filter, LDAP **ldp, 298 char **attrs, int noattrs, int nobind, 299 int innerloop, int force ) 300 { 301 LDAP *ld = ldp ? *ldp : NULL; 302 int i = 0, do_retry = config->retries; 303 int rc = LDAP_SUCCESS; 304 char buf[ BUFSIZ ]; 305 int *msgids = NULL, active = 0; 306 307 /* make room for msgid */ 308 if ( swamp > 1 ) { 309 msgids = (int *)calloc( sizeof(int), innerloop ); 310 if ( !msgids ) { 311 tester_error( "calloc failed" ); 312 exit( EXIT_FAILURE ); 313 } 314 } 315 316 retry:; 317 if ( ld == NULL ) { 318 fprintf( stderr, 319 "PID=%ld - Search(%d): " 320 "base=\"%s\" scope=%s filter=\"%s\" " 321 "attrs=%s%s.\n", 322 (long) pid, innerloop, 323 sbase, ldap_pvt_scope2str( scope ), filter, 324 attrs[0], attrs[1] ? " (more...)" : "" ); 325 326 tester_init_ld( &ld, config, nobind ); 327 } 328 329 if ( swamp > 1 ) { 330 do { 331 LDAPMessage *res = NULL; 332 int j, msgid; 333 334 if ( i < innerloop ) { 335 rc = ldap_search_ext( ld, sbase, scope, 336 filter, NULL, noattrs, NULL, NULL, 337 NULL, LDAP_NO_LIMIT, &msgids[i] ); 338 339 active++; 340 #if 0 341 fprintf( stderr, 342 ">>> PID=%ld - Search maxloop=%d cnt=%d active=%d msgid=%d: " 343 "base=\"%s\" scope=%s filter=\"%s\"\n", 344 (long) pid, innerloop, i, active, msgids[i], 345 sbase, ldap_pvt_scope2str( scope ), filter ); 346 #endif 347 i++; 348 349 if ( rc ) { 350 int first = tester_ignore_err( rc ); 351 /* if ignore.. */ 352 if ( first ) { 353 /* only log if first occurrence */ 354 if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) { 355 tester_ldap_error( ld, "ldap_search_ext", NULL ); 356 } 357 continue; 358 } 359 360 /* busy needs special handling */ 361 snprintf( buf, sizeof( buf ), 362 "base=\"%s\" filter=\"%s\"\n", 363 sbase, filter ); 364 tester_ldap_error( ld, "ldap_search_ext", buf ); 365 if ( rc == LDAP_BUSY && do_retry > 0 ) { 366 ldap_unbind_ext( ld, NULL, NULL ); 367 ld = NULL; 368 do_retry--; 369 goto retry; 370 } 371 break; 372 } 373 374 if ( swamp > 2 ) { 375 continue; 376 } 377 } 378 379 rc = ldap_result( ld, LDAP_RES_ANY, 0, NULL, &res ); 380 switch ( rc ) { 381 case -1: 382 /* gone really bad */ 383 goto cleanup; 384 385 case 0: 386 /* timeout (impossible) */ 387 break; 388 389 case LDAP_RES_SEARCH_ENTRY: 390 case LDAP_RES_SEARCH_REFERENCE: 391 /* ignore */ 392 break; 393 394 case LDAP_RES_SEARCH_RESULT: 395 /* just remove, no error checking (TODO?) */ 396 msgid = ldap_msgid( res ); 397 ldap_parse_result( ld, res, &rc, NULL, NULL, NULL, NULL, 1 ); 398 res = NULL; 399 400 /* linear search, bah */ 401 for ( j = 0; j < i; j++ ) { 402 if ( msgids[ j ] == msgid ) { 403 msgids[ j ] = -1; 404 active--; 405 #if 0 406 fprintf( stderr, 407 "<<< PID=%ld - SearchDone maxloop=%d cnt=%d active=%d msgid=%d: " 408 "base=\"%s\" scope=%s filter=\"%s\"\n", 409 (long) pid, innerloop, j, active, msgid, 410 sbase, ldap_pvt_scope2str( scope ), filter ); 411 #endif 412 break; 413 } 414 } 415 break; 416 417 default: 418 /* other messages unexpected */ 419 fprintf( stderr, 420 "### PID=%ld - Search(%d): " 421 "base=\"%s\" scope=%s filter=\"%s\" " 422 "attrs=%s%s. unexpected response tag=%d\n", 423 (long) pid, innerloop, 424 sbase, ldap_pvt_scope2str( scope ), filter, 425 attrs[0], attrs[1] ? " (more...)" : "", rc ); 426 break; 427 } 428 429 if ( res != NULL ) { 430 ldap_msgfree( res ); 431 } 432 } while ( i < innerloop || active > 0 ); 433 434 } else { 435 for ( ; i < innerloop; i++ ) { 436 LDAPMessage *res = NULL; 437 438 if (swamp) { 439 int msgid; 440 rc = ldap_search_ext( ld, sbase, scope, 441 filter, NULL, noattrs, NULL, NULL, 442 NULL, LDAP_NO_LIMIT, &msgid ); 443 if ( rc == LDAP_SUCCESS ) continue; 444 else break; 445 } 446 447 rc = ldap_search_ext_s( ld, sbase, scope, 448 filter, attrs, noattrs, NULL, NULL, 449 NULL, LDAP_NO_LIMIT, &res ); 450 if ( res != NULL ) { 451 ldap_msgfree( res ); 452 } 453 454 if ( rc ) { 455 int first = tester_ignore_err( rc ); 456 /* if ignore.. */ 457 if ( first ) { 458 /* only log if first occurrence */ 459 if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) { 460 tester_ldap_error( ld, "ldap_search_ext_s", NULL ); 461 } 462 continue; 463 } 464 465 /* busy needs special handling */ 466 snprintf( buf, sizeof( buf ), 467 "base=\"%s\" filter=\"%s\"\n", 468 sbase, filter ); 469 tester_ldap_error( ld, "ldap_search_ext_s", buf ); 470 if ( rc == LDAP_BUSY && do_retry > 0 ) { 471 ldap_unbind_ext( ld, NULL, NULL ); 472 ld = NULL; 473 do_retry--; 474 goto retry; 475 } 476 break; 477 } 478 } 479 } 480 481 cleanup:; 482 if ( msgids != NULL ) { 483 free( msgids ); 484 } 485 486 if ( ldp != NULL ) { 487 *ldp = ld; 488 489 } else { 490 fprintf( stderr, " PID=%ld - Search done (%d).\n", (long) pid, rc ); 491 492 if ( ld != NULL ) { 493 ldap_unbind_ext( ld, NULL, NULL ); 494 } 495 } 496 } 497