Home | History | Annotate | Line # | Download | only in progs
      1 /*	$NetBSD: slapd-common.c,v 1.4 2025/09/05 21:16:33 christos Exp $	*/
      2 
      3 /* $OpenLDAP$ */
      4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
      5  *
      6  * Copyright 1999-2024 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-common.c,v 1.4 2025/09/05 21:16:33 christos Exp $");
     24 
     25 #include "portable.h"
     26 
     27 #include <stdio.h>
     28 
     29 #include "ac/stdlib.h"
     30 #include "ac/unistd.h"
     31 #include "ac/string.h"
     32 #include "ac/errno.h"
     33 
     34 #include "ldap.h"
     35 
     36 #include "lutil.h"
     37 #include "lutil_ldap.h"
     38 #include "ldap_pvt.h"
     39 #include "slapd-common.h"
     40 
     41 /* global vars */
     42 pid_t pid;
     43 int debug;
     44 
     45 /* static vars */
     46 static char progname[ BUFSIZ ];
     47 tester_t progtype;
     48 
     49 /*
     50  * ignore_count[] is indexed by result code:
     51  * negative for OpenLDAP client-side errors, positive for protocol codes.
     52  */
     53 #define	TESTER_CLIENT_FIRST	LDAP_REFERRAL_LIMIT_EXCEEDED /* negative */
     54 #define	TESTER_SERVER_LAST	LDAP_OTHER
     55 static int ignore_base	[ -TESTER_CLIENT_FIRST + TESTER_SERVER_LAST + 1 ];
     56 #define    ignore_count	(ignore_base - TESTER_CLIENT_FIRST)
     57 
     58 static const struct {
     59 	const char *name;
     60 	int	err;
     61 } ignore_str2err[] = {
     62 	{ "OPERATIONS_ERROR",		LDAP_OPERATIONS_ERROR },
     63 	{ "PROTOCOL_ERROR",		LDAP_PROTOCOL_ERROR },
     64 	{ "TIMELIMIT_EXCEEDED",		LDAP_TIMELIMIT_EXCEEDED },
     65 	{ "SIZELIMIT_EXCEEDED",		LDAP_SIZELIMIT_EXCEEDED },
     66 	{ "COMPARE_FALSE",		LDAP_COMPARE_FALSE },
     67 	{ "COMPARE_TRUE",		LDAP_COMPARE_TRUE },
     68 	{ "AUTH_METHOD_NOT_SUPPORTED",	LDAP_AUTH_METHOD_NOT_SUPPORTED },
     69 	{ "STRONG_AUTH_NOT_SUPPORTED",	LDAP_STRONG_AUTH_NOT_SUPPORTED },
     70 	{ "STRONG_AUTH_REQUIRED",	LDAP_STRONG_AUTH_REQUIRED },
     71 	{ "STRONGER_AUTH_REQUIRED",	LDAP_STRONGER_AUTH_REQUIRED },
     72 	{ "PARTIAL_RESULTS",		LDAP_PARTIAL_RESULTS },
     73 
     74 	{ "REFERRAL",			LDAP_REFERRAL },
     75 	{ "ADMINLIMIT_EXCEEDED",	LDAP_ADMINLIMIT_EXCEEDED },
     76 	{ "UNAVAILABLE_CRITICAL_EXTENSION", LDAP_UNAVAILABLE_CRITICAL_EXTENSION },
     77 	{ "CONFIDENTIALITY_REQUIRED",	LDAP_CONFIDENTIALITY_REQUIRED },
     78 	{ "SASL_BIND_IN_PROGRESS",	LDAP_SASL_BIND_IN_PROGRESS },
     79 
     80 	{ "NO_SUCH_ATTRIBUTE",		LDAP_NO_SUCH_ATTRIBUTE },
     81 	{ "UNDEFINED_TYPE",		LDAP_UNDEFINED_TYPE },
     82 	{ "INAPPROPRIATE_MATCHING",	LDAP_INAPPROPRIATE_MATCHING },
     83 	{ "CONSTRAINT_VIOLATION",	LDAP_CONSTRAINT_VIOLATION },
     84 	{ "TYPE_OR_VALUE_EXISTS",	LDAP_TYPE_OR_VALUE_EXISTS },
     85 	{ "INVALID_SYNTAX",		LDAP_INVALID_SYNTAX },
     86 
     87 	{ "NO_SUCH_OBJECT",		LDAP_NO_SUCH_OBJECT },
     88 	{ "ALIAS_PROBLEM",		LDAP_ALIAS_PROBLEM },
     89 	{ "INVALID_DN_SYNTAX",		LDAP_INVALID_DN_SYNTAX },
     90 	{ "IS_LEAF",			LDAP_IS_LEAF },
     91 	{ "ALIAS_DEREF_PROBLEM",	LDAP_ALIAS_DEREF_PROBLEM },
     92 
     93 	/* obsolete */
     94 	{ "PROXY_AUTHZ_FAILURE",	LDAP_X_PROXY_AUTHZ_FAILURE },
     95 	{ "INAPPROPRIATE_AUTH",		LDAP_INAPPROPRIATE_AUTH },
     96 	{ "INVALID_CREDENTIALS",	LDAP_INVALID_CREDENTIALS },
     97 	{ "INSUFFICIENT_ACCESS",	LDAP_INSUFFICIENT_ACCESS },
     98 
     99 	{ "BUSY",			LDAP_BUSY },
    100 	{ "UNAVAILABLE",		LDAP_UNAVAILABLE },
    101 	{ "UNWILLING_TO_PERFORM",	LDAP_UNWILLING_TO_PERFORM },
    102 	{ "LOOP_DETECT",		LDAP_LOOP_DETECT },
    103 
    104 	{ "NAMING_VIOLATION",		LDAP_NAMING_VIOLATION },
    105 	{ "OBJECT_CLASS_VIOLATION",	LDAP_OBJECT_CLASS_VIOLATION },
    106 	{ "NOT_ALLOWED_ON_NONLEAF",	LDAP_NOT_ALLOWED_ON_NONLEAF },
    107 	{ "NOT_ALLOWED_ON_RDN",		LDAP_NOT_ALLOWED_ON_RDN },
    108 	{ "ALREADY_EXISTS",		LDAP_ALREADY_EXISTS },
    109 	{ "NO_OBJECT_CLASS_MODS",	LDAP_NO_OBJECT_CLASS_MODS },
    110 	{ "RESULTS_TOO_LARGE",		LDAP_RESULTS_TOO_LARGE },
    111 	{ "AFFECTS_MULTIPLE_DSAS",	LDAP_AFFECTS_MULTIPLE_DSAS },
    112 
    113 	{ "OTHER",			LDAP_OTHER },
    114 
    115 	{ "SERVER_DOWN",		LDAP_SERVER_DOWN },
    116 	{ "LOCAL_ERROR",		LDAP_LOCAL_ERROR },
    117 	{ "ENCODING_ERROR",		LDAP_ENCODING_ERROR },
    118 	{ "DECODING_ERROR",		LDAP_DECODING_ERROR },
    119 	{ "TIMEOUT",			LDAP_TIMEOUT },
    120 	{ "AUTH_UNKNOWN",		LDAP_AUTH_UNKNOWN },
    121 	{ "FILTER_ERROR",		LDAP_FILTER_ERROR },
    122 	{ "USER_CANCELLED",		LDAP_USER_CANCELLED },
    123 	{ "PARAM_ERROR",		LDAP_PARAM_ERROR },
    124 	{ "NO_MEMORY",			LDAP_NO_MEMORY },
    125 	{ "CONNECT_ERROR",		LDAP_CONNECT_ERROR },
    126 	{ "NOT_SUPPORTED",		LDAP_NOT_SUPPORTED },
    127 	{ "CONTROL_NOT_FOUND",		LDAP_CONTROL_NOT_FOUND },
    128 	{ "NO_RESULTS_RETURNED",	LDAP_NO_RESULTS_RETURNED },
    129 	{ "MORE_RESULTS_TO_RETURN",	LDAP_MORE_RESULTS_TO_RETURN },
    130 	{ "CLIENT_LOOP",		LDAP_CLIENT_LOOP },
    131 	{ "REFERRAL_LIMIT_EXCEEDED", 	LDAP_REFERRAL_LIMIT_EXCEEDED },
    132 
    133 	{ NULL }
    134 };
    135 
    136 #define UNKNOWN_ERR	(1234567890)
    137 
    138 #define RETRIES 0
    139 #define LOOPS	100
    140 
    141 static int
    142 tester_ignore_str2err( const char *err )
    143 {
    144 	int		i, ignore = 1;
    145 
    146 	if ( strcmp( err, "ALL" ) == 0 ) {
    147 		for ( i = 0; ignore_str2err[ i ].name != NULL; i++ ) {
    148 			ignore_count[ ignore_str2err[ i ].err ] = 1;
    149 		}
    150 		ignore_count[ LDAP_SUCCESS ] = 0;
    151 
    152 		return 0;
    153 	}
    154 
    155 	if ( err[ 0 ] == '!' ) {
    156 		ignore = 0;
    157 		err++;
    158 
    159 	} else if ( err[ 0 ] == '*' ) {
    160 		ignore = -1;
    161 		err++;
    162 	}
    163 
    164 	for ( i = 0; ignore_str2err[ i ].name != NULL; i++ ) {
    165 		if ( strcmp( err, ignore_str2err[ i ].name ) == 0 ) {
    166 			int	err = ignore_str2err[ i ].err;
    167 
    168 			if ( err != LDAP_SUCCESS ) {
    169 				ignore_count[ err ] = ignore;
    170 			}
    171 
    172 			return err;
    173 		}
    174 	}
    175 
    176 	return UNKNOWN_ERR;
    177 }
    178 
    179 int
    180 tester_ignore_str2errlist( const char *err )
    181 {
    182 	int	i;
    183 	char	**errs = ldap_str2charray( err, "," );
    184 
    185 	for ( i = 0; errs[ i ] != NULL; i++ ) {
    186 		/* TODO: allow <err>:<prog> to ignore <err> only when <prog> */
    187 		(void)tester_ignore_str2err( errs[ i ] );
    188 	}
    189 
    190 	ldap_charray_free( errs );
    191 
    192 	return 0;
    193 }
    194 
    195 int
    196 tester_ignore_err( int err )
    197 {
    198 	int rc = 1;
    199 
    200 	if ( err && TESTER_CLIENT_FIRST <= err && err <= TESTER_SERVER_LAST ) {
    201 		rc = ignore_count[ err ];
    202 		if ( rc != 0 ) {
    203 			ignore_count[ err ] = rc + (rc > 0 ? 1 : -1);
    204 		}
    205 	}
    206 
    207 	/* SUCCESS is always "ignored" */
    208 	return rc;
    209 }
    210 
    211 struct tester_conn_args *
    212 tester_init( const char *pname, tester_t ptype )
    213 {
    214 	static struct tester_conn_args config = {
    215 		.authmethod = -1,
    216 		.retries = RETRIES,
    217 		.loops = LOOPS,
    218 		.outerloops = 1,
    219 
    220 		.uri = NULL,
    221 	};
    222 
    223 	pid = getpid();
    224 	srand( pid );
    225 	snprintf( progname, sizeof( progname ), "%s PID=%d", pname, pid );
    226 	progtype = ptype;
    227 
    228 	return &config;
    229 }
    230 
    231 void
    232 tester_ldap_error( LDAP *ld, const char *fname, const char *msg )
    233 {
    234 	int		err;
    235 	char		*text = NULL;
    236 	LDAPControl	**ctrls = NULL;
    237 
    238 	ldap_get_option( ld, LDAP_OPT_RESULT_CODE, (void *)&err );
    239 	if ( err != LDAP_SUCCESS ) {
    240 		ldap_get_option( ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void *)&text );
    241 	}
    242 
    243 	fprintf( stderr, "%s: %s: %s (%d) %s %s\n",
    244 		progname, fname, ldap_err2string( err ), err,
    245 		text == NULL ? "" : text,
    246 		msg ? msg : "" );
    247 
    248 	if ( text ) {
    249 		ldap_memfree( text );
    250 		text = NULL;
    251 	}
    252 
    253 	ldap_get_option( ld, LDAP_OPT_MATCHED_DN, (void *)&text );
    254 	if ( text != NULL ) {
    255 		if ( text[ 0 ] != '\0' ) {
    256 			fprintf( stderr, "\tmatched: %s\n", text );
    257 		}
    258 		ldap_memfree( text );
    259 		text = NULL;
    260 	}
    261 
    262 	ldap_get_option( ld, LDAP_OPT_SERVER_CONTROLS, (void *)&ctrls );
    263 	if ( ctrls != NULL ) {
    264 		int	i;
    265 
    266 		fprintf( stderr, "\tcontrols:\n" );
    267 		for ( i = 0; ctrls[ i ] != NULL; i++ ) {
    268 			fprintf( stderr, "\t\t%s\n", ctrls[ i ]->ldctl_oid );
    269 		}
    270 		ldap_controls_free( ctrls );
    271 		ctrls = NULL;
    272 	}
    273 
    274 	if ( err == LDAP_REFERRAL ) {
    275 		char **refs = NULL;
    276 
    277 		ldap_get_option( ld, LDAP_OPT_REFERRAL_URLS, (void *)&refs );
    278 
    279 		if ( refs ) {
    280 			int	i;
    281 
    282 			fprintf( stderr, "\treferral:\n" );
    283 			for ( i = 0; refs[ i ] != NULL; i++ ) {
    284 				fprintf( stderr, "\t\t%s\n", refs[ i ] );
    285 			}
    286 
    287 			ber_memvfree( (void **)refs );
    288 		}
    289 	}
    290 }
    291 
    292 void
    293 tester_perror( const char *fname, const char *msg )
    294 {
    295 	int	save_errno = errno;
    296 	char	buf[ BUFSIZ ];
    297 
    298 	fprintf( stderr, "%s: %s: (%d) %s %s\n",
    299 			progname, fname, save_errno,
    300 			AC_STRERROR_R( save_errno, buf, sizeof( buf ) ),
    301 			msg ? msg : "" );
    302 }
    303 
    304 int
    305 tester_config_opt( struct tester_conn_args *config, char opt, char *optarg )
    306 {
    307 	switch ( opt ) {
    308 		case 'C':
    309 			config->chaserefs++;
    310 			break;
    311 
    312 		case 'D':
    313 			config->binddn = optarg;
    314 			break;
    315 
    316 		case 'd':
    317 			{
    318 				if ( lutil_atoi( &debug, optarg ) != 0 ) {
    319 					return -1;
    320 				}
    321 
    322 				if ( ber_set_option( NULL, LBER_OPT_DEBUG_LEVEL, &debug )
    323 					!= LBER_OPT_SUCCESS )
    324 				{
    325 					fprintf( stderr,
    326 						"Could not set LBER_OPT_DEBUG_LEVEL %d\n", debug );
    327 				}
    328 
    329 				if ( ldap_set_option( NULL, LDAP_OPT_DEBUG_LEVEL, &debug )
    330 					!= LDAP_OPT_SUCCESS )
    331 				{
    332 					fprintf( stderr,
    333 						"Could not set LDAP_OPT_DEBUG_LEVEL %d\n", debug );
    334 				}
    335 				break;
    336 			}
    337 
    338 		case 'H':
    339 			config->uri = optarg;
    340 			break;
    341 
    342 		case 'i':
    343 			tester_ignore_str2errlist( optarg );
    344 			break;
    345 
    346 		case 'L':
    347 			if ( lutil_atoi( &config->outerloops, optarg ) != 0 ) {
    348 				return -1;
    349 			}
    350 			break;
    351 
    352 		case 'l':
    353 			if ( lutil_atoi( &config->loops, optarg ) != 0 ) {
    354 				return -1;
    355 			}
    356 			break;
    357 
    358 #ifdef HAVE_CYRUS_SASL
    359 		case 'O':
    360 			if ( config->secprops != NULL ) {
    361 				return -1;
    362 			}
    363 			if ( config->authmethod != -1 && config->authmethod != LDAP_AUTH_SASL ) {
    364 				return -1;
    365 			}
    366 			config->authmethod = LDAP_AUTH_SASL;
    367 			config->secprops = optarg;
    368 			break;
    369 
    370 		case 'R':
    371 			if ( config->realm != NULL ) {
    372 				return -1;
    373 			}
    374 			if ( config->authmethod != -1 && config->authmethod != LDAP_AUTH_SASL ) {
    375 				return -1;
    376 			}
    377 			config->authmethod = LDAP_AUTH_SASL;
    378 			config->realm = optarg;
    379 			break;
    380 
    381 		case 'U':
    382 			if ( config->authc_id != NULL ) {
    383 				return -1;
    384 			}
    385 			if ( config->authmethod != -1 && config->authmethod != LDAP_AUTH_SASL ) {
    386 				return -1;
    387 			}
    388 			config->authmethod = LDAP_AUTH_SASL;
    389 			config->authc_id = optarg;
    390 			break;
    391 
    392 		case 'X':
    393 			if ( config->authz_id != NULL ) {
    394 				return -1;
    395 			}
    396 			if ( config->authmethod != -1 && config->authmethod != LDAP_AUTH_SASL ) {
    397 				return -1;
    398 			}
    399 			config->authmethod = LDAP_AUTH_SASL;
    400 			config->authz_id = optarg;
    401 			break;
    402 
    403 		case 'Y':
    404 			if ( config->mech != NULL ) {
    405 				return -1;
    406 			}
    407 			if ( config->authmethod != -1 && config->authmethod != LDAP_AUTH_SASL ) {
    408 				return -1;
    409 			}
    410 			config->authmethod = LDAP_AUTH_SASL;
    411 			config->mech = optarg;
    412 			break;
    413 #endif
    414 
    415 		case 'r':
    416 			if ( lutil_atoi( &config->retries, optarg ) != 0 ) {
    417 				return -1;
    418 			}
    419 			break;
    420 
    421 		case 't':
    422 			if ( lutil_atoi( &config->delay, optarg ) != 0 ) {
    423 				return -1;
    424 			}
    425 			break;
    426 
    427 		case 'w':
    428 			config->pass.bv_val = strdup( optarg );
    429 			config->pass.bv_len = strlen( optarg );
    430 			memset( optarg, '*', config->pass.bv_len );
    431 			break;
    432 
    433 		case 'x':
    434 			if ( config->authmethod != -1 && config->authmethod != LDAP_AUTH_SIMPLE ) {
    435 				return -1;
    436 			}
    437 			config->authmethod = LDAP_AUTH_SIMPLE;
    438 			break;
    439 
    440 		default:
    441 			return -1;
    442 	}
    443 
    444 	return LDAP_SUCCESS;
    445 }
    446 
    447 void
    448 tester_config_finish( struct tester_conn_args *config )
    449 {
    450 	if ( config->authmethod == -1 ) {
    451 #ifdef HAVE_CYRUS_SASL
    452 		if ( config->binddn != NULL ) {
    453 			config->authmethod = LDAP_AUTH_SIMPLE;
    454 		} else {
    455 			config->authmethod = LDAP_AUTH_SASL;
    456 		}
    457 #else
    458 		config->authmethod = LDAP_AUTH_SIMPLE;
    459 #endif
    460 	}
    461 
    462 #ifdef HAVE_CYRUS_SASL
    463 	if ( config->authmethod == LDAP_AUTH_SASL ) {
    464 		config->defaults = lutil_sasl_defaults( NULL,
    465 			config->mech,
    466 			config->realm,
    467 			config->authc_id,
    468 			config->pass.bv_val,
    469 			config->authz_id );
    470 
    471 		if ( config->defaults == NULL ) {
    472 			tester_error( "unable to prepare SASL defaults" );
    473 			exit( EXIT_FAILURE );
    474 		}
    475 	}
    476 #endif
    477 }
    478 
    479 void
    480 tester_init_ld( LDAP **ldp, struct tester_conn_args *config, int flags )
    481 {
    482 	LDAP *ld;
    483 	int rc, do_retry = config->retries;
    484 	int version = LDAP_VERSION3;
    485 
    486 retry:;
    487 	ldap_initialize( &ld, config->uri );
    488 	if ( ld == NULL ) {
    489 		tester_perror( "ldap_initialize", NULL );
    490 		exit( EXIT_FAILURE );
    491 	}
    492 
    493 	(void) ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version );
    494 	(void) ldap_set_option( ld, LDAP_OPT_REFERRALS,
    495 		config->chaserefs ? LDAP_OPT_ON: LDAP_OPT_OFF );
    496 
    497 	if ( !( flags & TESTER_INIT_ONLY ) ) {
    498 		if ( config->authmethod == LDAP_AUTH_SASL ) {
    499 #ifdef HAVE_CYRUS_SASL
    500 			if ( config->secprops != NULL ) {
    501 				rc = ldap_set_option( ld,
    502 						LDAP_OPT_X_SASL_SECPROPS, config->secprops );
    503 
    504 				if ( rc != LDAP_OPT_SUCCESS ) {
    505 					tester_ldap_error( ld, "ldap_set_option(SECPROPS)", NULL );
    506 					ldap_unbind_ext( ld, NULL, NULL );
    507 					exit( EXIT_FAILURE );
    508 				}
    509 			}
    510 
    511 			rc = ldap_sasl_interactive_bind_s( ld,
    512 					config->binddn,
    513 					config->mech,
    514 					NULL, NULL,
    515 					LDAP_SASL_QUIET,
    516 					lutil_sasl_interact,
    517 					config->defaults );
    518 #else /* HAVE_CYRUS_SASL */
    519 			/* caller shouldn't have allowed this */
    520 			assert(0);
    521 #endif
    522 		} else if ( config->authmethod == LDAP_AUTH_SIMPLE ) {
    523 			rc = ldap_sasl_bind_s( ld,
    524 					config->binddn, LDAP_SASL_SIMPLE,
    525 					&config->pass, NULL, NULL, NULL );
    526 		}
    527 
    528 		if ( rc != LDAP_SUCCESS ) {
    529 			tester_ldap_error( ld, "ldap_sasl_bind_s", NULL );
    530 			ldap_unbind_ext( ld, NULL, NULL );
    531 			ld = NULL;
    532 			switch ( rc ) {
    533 				case LDAP_BUSY:
    534 				case LDAP_UNAVAILABLE:
    535 					if ( do_retry > 0 ) {
    536 						do_retry--;
    537 						if ( config->delay > 0 ) {
    538 							sleep( config->delay );
    539 						}
    540 						goto retry;
    541 					}
    542 			}
    543 			if ( !( flags & TESTER_INIT_NOEXIT ))
    544 				exit( EXIT_FAILURE );
    545 		}
    546 	}
    547 
    548 	*ldp = ld;
    549 }
    550 
    551 void
    552 tester_error( const char *msg )
    553 {
    554 	fprintf( stderr, "%s: %s\n", progname, msg );
    555 }
    556