1/*	$NetBSD: slapd-common.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-common.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/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 */
42pid_t pid;
43int debug;
44
45/* static vars */
46static char progname[ BUFSIZ ];
47tester_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
55static int ignore_base	[ -TESTER_CLIENT_FIRST + TESTER_SERVER_LAST + 1 ];
56#define    ignore_count	(ignore_base - TESTER_CLIENT_FIRST)
57
58static 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
141static int
142tester_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
179int
180tester_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
195int
196tester_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
211struct tester_conn_args *
212tester_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
231void
232tester_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
292void
293tester_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
304int
305tester_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
447void
448tester_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
479void
480tester_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
486retry:;
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			switch ( rc ) {
531				case LDAP_BUSY:
532				case LDAP_UNAVAILABLE:
533					if ( do_retry > 0 ) {
534						do_retry--;
535						if ( config->delay > 0 ) {
536							sleep( config->delay );
537						}
538						goto retry;
539					}
540			}
541			ldap_unbind_ext( ld, NULL, NULL );
542			ld = NULL;
543			if ( !( flags & TESTER_INIT_NOEXIT ))
544				exit( EXIT_FAILURE );
545		}
546	}
547
548	*ldp = ld;
549}
550
551void
552tester_error( const char *msg )
553{
554	fprintf( stderr, "%s: %s\n", progname, msg );
555}
556