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