1/*	$NetBSD: dlz_ldap_dynamic.c,v 1.7 2024/02/21 22:51:48 christos Exp $	*/
2
3/*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * SPDX-License-Identifier: MPL-2.0 and ISC
7 *
8 * This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11 */
12
13/*
14 * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
15 *
16 * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was
17 * conceived and contributed by Rob Butler.
18 *
19 * Permission to use, copy, modify, and distribute this software for any purpose
20 * with or without fee is hereby granted, provided that the above copyright
21 * notice and this permission notice appear in all copies.
22 *
23 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
24 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
25 * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
26 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
27 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
28 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
29 * PERFORMANCE OF THIS SOFTWARE.
30 */
31
32/*
33 * This provides the externally loadable ldap DLZ module, without
34 * update support
35 */
36
37#include <stdarg.h>
38#include <stdbool.h>
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42
43#include <dlz_dbi.h>
44#include <dlz_list.h>
45#include <dlz_minimal.h>
46#include <dlz_pthread.h>
47
48/*
49 * Need older API functions from ldap.h.
50 */
51#define LDAP_DEPRECATED 1
52
53#include <ldap.h>
54
55#define SIMPLE "simple"
56#define KRB41  "krb41"
57#define KRB42  "krb42"
58#define V2     "v2"
59#define V3     "v3"
60
61#define dbc_search_limit 30
62#define ALLNODES	 1
63#define ALLOWXFR	 2
64#define AUTHORITY	 3
65#define FINDZONE	 4
66#define LOOKUP		 5
67
68/*%
69 * Structure to hold everything needed by this "instance" of the LDAP
70 * driver remember, the driver code is only loaded once, but may have
71 * many separate instances.
72 */
73typedef struct {
74	db_list_t *db; /*%< handle to a list of DB */
75	int method;    /*%< security authentication
76			* method */
77	char *user;    /*%< who is authenticating */
78	char *cred;    /*%< password for simple
79			* authentication method */
80	int protocol;  /*%< LDAP communication
81			* protocol version */
82	char *hosts;   /*%< LDAP server hosts */
83
84	/* Helper functions from the dlz_dlopen driver */
85	log_t *log;
86	dns_sdlz_putrr_t *putrr;
87	dns_sdlz_putnamedrr_t *putnamedrr;
88	dns_dlz_writeablezone_t *writeable_zone;
89} ldap_instance_t;
90
91/* forward references */
92
93#if DLZ_DLOPEN_VERSION < 3
94isc_result_t
95dlz_findzonedb(void *dbdata, const char *name);
96#else  /* if DLZ_DLOPEN_VERSION < 3 */
97isc_result_t
98dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods,
99	       dns_clientinfo_t *clientinfo);
100#endif /* if DLZ_DLOPEN_VERSION < 3 */
101
102void
103dlz_destroy(void *dbdata);
104
105static void
106b9_add_helper(ldap_instance_t *db, const char *helper_name, void *ptr);
107
108/*
109 * Private methods
110 */
111
112/*% checks that the LDAP URL parameters make sense */
113static isc_result_t
114dlz_ldap_checkURL(ldap_instance_t *db, char *URL, int attrCnt,
115		  const char *msg) {
116	isc_result_t result = ISC_R_SUCCESS;
117	int ldap_result;
118	LDAPURLDesc *ldap_url = NULL;
119
120	if (!ldap_is_ldap_url(URL)) {
121		db->log(ISC_LOG_ERROR, "%s query is not a valid LDAP URL", msg);
122		result = ISC_R_FAILURE;
123		goto cleanup;
124	}
125
126	ldap_result = ldap_url_parse(URL, &ldap_url);
127	if (ldap_result != LDAP_SUCCESS || ldap_url == NULL) {
128		db->log(ISC_LOG_ERROR, "parsing %s query failed", msg);
129		result = ISC_R_FAILURE;
130		goto cleanup;
131	}
132
133	if (ldap_count_values(ldap_url->lud_attrs) < attrCnt) {
134		db->log(ISC_LOG_ERROR,
135			"%s query must specify at least "
136			"%d attributes to return",
137			msg, attrCnt);
138		result = ISC_R_FAILURE;
139		goto cleanup;
140	}
141
142	if (ldap_url->lud_host != NULL) {
143		db->log(ISC_LOG_ERROR, "%s query must not specify a host", msg);
144		result = ISC_R_FAILURE;
145		goto cleanup;
146	}
147
148	if (ldap_url->lud_port != 389) {
149		db->log(ISC_LOG_ERROR, "%s query must not specify a port", msg);
150		result = ISC_R_FAILURE;
151		goto cleanup;
152	}
153
154	if (ldap_url->lud_dn == NULL || strlen(ldap_url->lud_dn) < 1) {
155		db->log(ISC_LOG_ERROR, "%s query must specify a search base",
156			msg);
157		result = ISC_R_FAILURE;
158		goto cleanup;
159	}
160
161	if (ldap_url->lud_exts != NULL || ldap_url->lud_crit_exts != 0) {
162		db->log(ISC_LOG_ERROR,
163			"%s uses extensions. "
164			"The driver does not support LDAP extensions.",
165			msg);
166		result = ISC_R_FAILURE;
167		goto cleanup;
168	}
169
170cleanup:
171	if (ldap_url != NULL) {
172		ldap_free_urldesc(ldap_url);
173	}
174
175	return (result);
176}
177
178/*% Connects / reconnects to LDAP server */
179static isc_result_t
180dlz_ldap_connect(ldap_instance_t *dbi, dbinstance_t *dbc) {
181	isc_result_t result;
182	int ldap_result;
183
184	/* if we have a connection, get ride of it. */
185	if (dbc->dbconn != NULL) {
186		ldap_unbind_s((LDAP *)dbc->dbconn);
187		dbc->dbconn = NULL;
188	}
189
190	/* now connect / reconnect. */
191
192	/* initialize. */
193	dbc->dbconn = ldap_init(dbi->hosts, LDAP_PORT);
194	if (dbc->dbconn == NULL) {
195		return (ISC_R_NOMEMORY);
196	}
197
198	/* set protocol version. */
199	ldap_result = ldap_set_option((LDAP *)dbc->dbconn,
200				      LDAP_OPT_PROTOCOL_VERSION,
201				      &(dbi->protocol));
202	if (ldap_result != LDAP_SUCCESS) {
203		result = ISC_R_NOPERM;
204		goto cleanup;
205	}
206
207	/* "bind" to server.  i.e. send username / pass */
208	ldap_result = ldap_bind_s((LDAP *)dbc->dbconn, dbi->user, dbi->cred,
209				  dbi->method);
210	if (ldap_result != LDAP_SUCCESS) {
211		result = ISC_R_FAILURE;
212		goto cleanup;
213	}
214
215	return (ISC_R_SUCCESS);
216
217cleanup:
218
219	/* cleanup if failure. */
220	if (dbc->dbconn != NULL) {
221		ldap_unbind_s((LDAP *)dbc->dbconn);
222		dbc->dbconn = NULL;
223	}
224
225	return (result);
226}
227
228/*%
229 * Properly cleans up a list of database instances.
230 * This function is only used when the driver is compiled for
231 * multithreaded operation.
232 */
233static void
234dlz_ldap_destroy_dblist(db_list_t *dblist) {
235	dbinstance_t *ndbi = NULL;
236	dbinstance_t *dbi = NULL;
237
238	/* get the first DBI in the list */
239	ndbi = DLZ_LIST_HEAD(*dblist);
240
241	/* loop through the list */
242	while (ndbi != NULL) {
243		dbi = ndbi;
244		/* get the next DBI in the list */
245		ndbi = DLZ_LIST_NEXT(dbi, link);
246		/* release DB connection */
247		if (dbi->dbconn != NULL) {
248			ldap_unbind_s((LDAP *)dbi->dbconn);
249		}
250		/* release all memory that comprised a DBI */
251		destroy_dbinstance(dbi);
252	}
253	/* release memory for the list structure */
254	free(dblist);
255}
256
257/*%
258 * Loops through the list of DB instances, attempting to lock
259 * on the mutex.  If successful, the DBI is reserved for use
260 * and the thread can perform queries against the database.
261 * If the lock fails, the next one in the list is tried.
262 * looping continues until a lock is obtained, or until
263 * the list has been searched dbc_search_limit times.
264 * This function is only used when the driver is compiled for
265 * multithreaded operation.
266 */
267static dbinstance_t *
268dlz_ldap_find_avail_conn(ldap_instance_t *ldap) {
269	dbinstance_t *dbi = NULL;
270	dbinstance_t *head;
271	int count = 0;
272
273	/* get top of list */
274	head = dbi = DLZ_LIST_HEAD(*ldap->db);
275
276	/* loop through list */
277	while (count < dbc_search_limit) {
278		/* try to lock on the mutex */
279		if (dlz_mutex_trylock(&dbi->lock) == 0) {
280			return (dbi); /* success, return the DBI for use. */
281		}
282		/* not successful, keep trying */
283		dbi = DLZ_LIST_NEXT(dbi, link);
284
285		/* check to see if we have gone to the top of the list. */
286		if (dbi == NULL) {
287			count++;
288			dbi = head;
289		}
290	}
291
292	ldap->log(ISC_LOG_INFO,
293		  "LDAP driver unable to find available connection "
294		  "after searching %d times",
295		  count);
296	return (NULL);
297}
298
299static isc_result_t
300dlz_ldap_process_results(ldap_instance_t *db, LDAP *dbc, LDAPMessage *msg,
301			 char **attrs, void *ptr, bool allnodes) {
302	isc_result_t result = ISC_R_SUCCESS;
303	int i = 0;
304	int j;
305	int len;
306	char *attribute = NULL;
307	LDAPMessage *entry;
308	char *endp = NULL;
309	char *host = NULL;
310	char *type = NULL;
311	char *data = NULL;
312	char **vals = NULL;
313	int ttl;
314
315	/* get the first entry to process */
316	entry = ldap_first_entry(dbc, msg);
317	if (entry == NULL) {
318		db->log(ISC_LOG_INFO, "LDAP no entries to process.");
319		return (ISC_R_FAILURE);
320	}
321
322	/* loop through all entries returned */
323	while (entry != NULL) {
324		/* reset for this loop */
325		ttl = 0;
326		len = 0;
327		i = 0;
328		attribute = attrs[i];
329
330		/* determine how much space we need for data string */
331		for (j = 0; attrs[j] != NULL; j++) {
332			/* get the list of values for this attribute. */
333			vals = ldap_get_values(dbc, entry, attrs[j]);
334			/* skip empty attributes. */
335			if (vals == NULL || ldap_count_values(vals) < 1) {
336				continue;
337			}
338			/*
339			 * we only use the first value.  this driver
340			 * does not support multi-valued attributes.
341			 */
342			len = len + strlen(vals[0]) + 1;
343			/* free vals for next loop */
344			ldap_value_free(vals);
345		}
346
347		/* allocate memory for data string */
348		data = malloc(len + 1);
349		if (data == NULL) {
350			db->log(ISC_LOG_ERROR, "LDAP driver unable to allocate "
351					       "memory "
352					       "while processing results");
353			result = ISC_R_FAILURE;
354			goto cleanup;
355		}
356
357		/*
358		 * Make sure data is null termed at the beginning so
359		 * we can check if any data was stored to it later.
360		 */
361		data[0] = '\0';
362
363		/* reset j to re-use below */
364		j = 0;
365
366		/* loop through the attributes in the order specified. */
367		while (attribute != NULL) {
368			/* get the list of values for this attribute. */
369			vals = ldap_get_values(dbc, entry, attribute);
370
371			/* skip empty attributes. */
372			if (vals == NULL || vals[0] == NULL) {
373				/* increment attribute pointer */
374				attribute = attrs[++i];
375				/* start loop over */
376				continue;
377			}
378
379			/*
380			 * j initially = 0.  Increment j each time we
381			 * set a field that way next loop will set
382			 * next field.
383			 */
384			switch (j) {
385			case 0:
386				j++;
387				/*
388				 * convert text to int, make sure it
389				 * worked right
390				 */
391				ttl = strtol(vals[0], &endp, 10);
392				if (*endp != '\0' || ttl < 0) {
393					db->log(ISC_LOG_ERROR, "LDAP driver "
394							       "ttl must "
395							       "be a positive "
396							       "number");
397					goto cleanup;
398				}
399				break;
400			case 1:
401				j++;
402				type = strdup(vals[0]);
403				break;
404			case 2:
405				j++;
406				if (allnodes) {
407					host = strdup(vals[0]);
408				} else {
409					strcpy(data, vals[0]);
410				}
411				break;
412			case 3:
413				j++;
414				if (allnodes) {
415					strcpy(data, vals[0]);
416				} else {
417					strcat(data, " ");
418					strcat(data, vals[0]);
419				}
420				break;
421			default:
422				strcat(data, " ");
423				strcat(data, vals[0]);
424				break;
425			}
426
427			/* free values */
428			ldap_value_free(vals);
429			vals = NULL;
430
431			/* increment attribute pointer */
432			attribute = attrs[++i];
433		}
434
435		if (type == NULL) {
436			db->log(ISC_LOG_ERROR, "LDAP driver unable to retrieve "
437					       "DNS type");
438			result = ISC_R_FAILURE;
439			goto cleanup;
440		}
441
442		if (strlen(data) < 1) {
443			db->log(ISC_LOG_ERROR, "LDAP driver unable to retrieve "
444					       "DNS data");
445			result = ISC_R_FAILURE;
446			goto cleanup;
447		}
448
449		if (allnodes && host != NULL) {
450			dns_sdlzallnodes_t *an = (dns_sdlzallnodes_t *)ptr;
451			if (strcasecmp(host, "~") == 0) {
452				result = db->putnamedrr(an, "*", type, ttl,
453							data);
454			} else {
455				result = db->putnamedrr(an, host, type, ttl,
456							data);
457			}
458			if (result != ISC_R_SUCCESS) {
459				db->log(ISC_LOG_ERROR,
460					"ldap_dynamic: putnamedrr failed "
461					"for \"%s %s %u %s\" (%d)",
462					host, type, ttl, data, result);
463			}
464		} else {
465			dns_sdlzlookup_t *lookup = (dns_sdlzlookup_t *)ptr;
466			result = db->putrr(lookup, type, ttl, data);
467			if (result != ISC_R_SUCCESS) {
468				db->log(ISC_LOG_ERROR,
469					"ldap_dynamic: putrr failed "
470					"for \"%s %u %s\" (%s)",
471					type, ttl, data, result);
472			}
473		}
474
475		if (result != ISC_R_SUCCESS) {
476			db->log(ISC_LOG_ERROR, "LDAP driver failed "
477					       "while sending data to BIND.");
478			goto cleanup;
479		}
480
481		/* free memory for type, data and host for next loop */
482		free(type);
483		type = NULL;
484
485		free(data);
486		data = NULL;
487
488		if (host != NULL) {
489			free(host);
490			host = NULL;
491		}
492
493		/* get the next entry to process */
494		entry = ldap_next_entry(dbc, entry);
495	}
496
497cleanup:
498	/* de-allocate memory */
499	if (vals != NULL) {
500		ldap_value_free(vals);
501	}
502	if (host != NULL) {
503		free(host);
504	}
505	if (type != NULL) {
506		free(type);
507	}
508	if (data != NULL) {
509		free(data);
510	}
511
512	return (result);
513}
514
515/*%
516 * This function is the real core of the driver.   Zone, record
517 * and client strings are passed in (or NULL is passed if the
518 * string is not available).  The type of query we want to run
519 * is indicated by the query flag, and the dbdata object is passed
520 * passed in to.  dbdata really holds either:
521 *		1) a list of database instances (in multithreaded mode) OR
522 *		2) a single database instance (in single threaded mode)
523 * The function will construct the query and obtain an available
524 * database instance (DBI).  It will then run the query and hopefully
525 * obtain a result set.
526 */
527static isc_result_t
528dlz_ldap_get_results(const char *zone, const char *record, const char *client,
529		     unsigned int query, void *dbdata, void *ptr) {
530	isc_result_t result;
531	ldap_instance_t *db = (ldap_instance_t *)dbdata;
532	dbinstance_t *dbi = NULL;
533	char *querystring = NULL;
534	LDAPURLDesc *ldap_url = NULL;
535	int ldap_result = 0;
536	LDAPMessage *ldap_msg = NULL;
537	int i;
538	int entries;
539
540	/* get db instance / connection */
541	/* find an available DBI from the list */
542	dbi = dlz_ldap_find_avail_conn(db);
543
544	/* if DBI is null, can't do anything else */
545	if (dbi == NULL) {
546		return (ISC_R_FAILURE);
547	}
548
549	/* set fields */
550	if (zone != NULL) {
551		dbi->zone = strdup(zone);
552		if (dbi->zone == NULL) {
553			result = ISC_R_NOMEMORY;
554			goto cleanup;
555		}
556	} else {
557		dbi->zone = NULL;
558	}
559
560	if (record != NULL) {
561		dbi->record = strdup(record);
562		if (dbi->record == NULL) {
563			result = ISC_R_NOMEMORY;
564			goto cleanup;
565		}
566	} else {
567		dbi->record = NULL;
568	}
569
570	if (client != NULL) {
571		dbi->client = strdup(client);
572		if (dbi->client == NULL) {
573			result = ISC_R_NOMEMORY;
574			goto cleanup;
575		}
576	} else {
577		dbi->client = NULL;
578	}
579
580	/* what type of query are we going to run? */
581	switch (query) {
582	case ALLNODES:
583		/*
584		 * if the query was not passed in from the config file
585		 * then we can't run it.  return not_implemented, so
586		 * it's like the code for that operation was never
587		 * built into the driver.... AHHH flexibility!!!
588		 */
589		if (dbi->allnodes_q == NULL) {
590			result = ISC_R_NOTIMPLEMENTED;
591			goto cleanup;
592		} else {
593			querystring = build_querystring(dbi->allnodes_q);
594		}
595		break;
596	case ALLOWXFR:
597		/* same as comments as ALLNODES */
598		if (dbi->allowxfr_q == NULL) {
599			result = ISC_R_NOTIMPLEMENTED;
600			goto cleanup;
601		} else {
602			querystring = build_querystring(dbi->allowxfr_q);
603		}
604		break;
605	case AUTHORITY:
606		/* same as comments as ALLNODES */
607		if (dbi->authority_q == NULL) {
608			result = ISC_R_NOTIMPLEMENTED;
609			goto cleanup;
610		} else {
611			querystring = build_querystring(dbi->authority_q);
612		}
613		break;
614	case FINDZONE:
615		/* this is required.  It's the whole point of DLZ! */
616		if (dbi->findzone_q == NULL) {
617			db->log(ISC_LOG_DEBUG(2), "No query specified for "
618						  "findzone. "
619						  "Findzone requires a query");
620			result = ISC_R_FAILURE;
621			goto cleanup;
622		} else {
623			querystring = build_querystring(dbi->findzone_q);
624		}
625		break;
626	case LOOKUP:
627		/* this is required.  It's also a major point of DLZ! */
628		if (dbi->lookup_q == NULL) {
629			db->log(ISC_LOG_DEBUG(2), "No query specified for "
630						  "lookup. "
631						  "Lookup requires a query");
632			result = ISC_R_FAILURE;
633			goto cleanup;
634		} else {
635			querystring = build_querystring(dbi->lookup_q);
636		}
637		break;
638	default:
639		/*
640		 * this should never happen.  If it does, the code is
641		 * screwed up!
642		 */
643		db->log(ISC_LOG_ERROR, "Incorrect query flag passed to "
644				       "dlz_ldap_get_results");
645		result = ISC_R_UNEXPECTED;
646		goto cleanup;
647	}
648
649	/* if the querystring is null, Bummer, outta RAM.  UPGRADE TIME!!!   */
650	if (querystring == NULL) {
651		result = ISC_R_NOMEMORY;
652		goto cleanup;
653	}
654
655	/*
656	 * output the full query string during debug so we can see
657	 * what lame error the query has.
658	 */
659	db->log(ISC_LOG_DEBUG(1), "Query String: %s", querystring);
660
661	/* break URL down into it's component parts, if error cleanup */
662	ldap_result = ldap_url_parse(querystring, &ldap_url);
663	if (ldap_result != LDAP_SUCCESS || ldap_url == NULL) {
664		result = ISC_R_FAILURE;
665		goto cleanup;
666	}
667
668	for (i = 0; i < 3; i++) {
669		/*
670		 * dbi->dbconn may be null if trying to reconnect on a
671		 * previous query failed.
672		 */
673		if (dbi->dbconn == NULL) {
674			db->log(ISC_LOG_INFO, "LDAP driver attempting to "
675					      "re-connect");
676
677			result = dlz_ldap_connect((ldap_instance_t *)dbdata,
678						  dbi);
679			if (result != ISC_R_SUCCESS) {
680				result = ISC_R_FAILURE;
681				continue;
682			}
683		}
684
685		/* perform ldap search synchronously */
686		ldap_result =
687			ldap_search_s((LDAP *)dbi->dbconn, ldap_url->lud_dn,
688				      ldap_url->lud_scope, ldap_url->lud_filter,
689				      ldap_url->lud_attrs, 0, &ldap_msg);
690
691		/*
692		 * check return code.  No such object is ok, just
693		 * didn't find what we wanted
694		 */
695		switch (ldap_result) {
696		case LDAP_NO_SUCH_OBJECT:
697			db->log(ISC_LOG_DEBUG(1), "No object found matching "
698						  "query requirements");
699			result = ISC_R_NOTFOUND;
700			goto cleanup;
701			break;
702		case LDAP_SUCCESS: /* on success do nothing */
703			result = ISC_R_SUCCESS;
704			i = 3;
705			break;
706		case LDAP_SERVER_DOWN:
707			db->log(ISC_LOG_INFO, "LDAP driver attempting to "
708					      "re-connect");
709			result = dlz_ldap_connect((ldap_instance_t *)dbdata,
710						  dbi);
711			if (result != ISC_R_SUCCESS) {
712				result = ISC_R_FAILURE;
713			}
714			break;
715		default:
716			/*
717			 * other errors not ok.  Log error message and
718			 * get out
719			 */
720			db->log(ISC_LOG_ERROR, "LDAP error: %s",
721				ldap_err2string(ldap_result));
722			result = ISC_R_FAILURE;
723			goto cleanup;
724			break;
725		}
726	}
727
728	if (result != ISC_R_SUCCESS) {
729		goto cleanup;
730	}
731
732	switch (query) {
733	case ALLNODES:
734		result = dlz_ldap_process_results(db, (LDAP *)dbi->dbconn,
735						  ldap_msg, ldap_url->lud_attrs,
736						  ptr, true);
737		break;
738	case AUTHORITY:
739	case LOOKUP:
740		result = dlz_ldap_process_results(db, (LDAP *)dbi->dbconn,
741						  ldap_msg, ldap_url->lud_attrs,
742						  ptr, false);
743		break;
744	case ALLOWXFR:
745		entries = ldap_count_entries((LDAP *)dbi->dbconn, ldap_msg);
746		if (entries == 0) {
747			result = ISC_R_NOPERM;
748		} else if (entries > 0) {
749			result = ISC_R_SUCCESS;
750		} else {
751			result = ISC_R_FAILURE;
752		}
753		break;
754	case FINDZONE:
755		entries = ldap_count_entries((LDAP *)dbi->dbconn, ldap_msg);
756		if (entries == 0) {
757			result = ISC_R_NOTFOUND;
758		} else if (entries > 0) {
759			result = ISC_R_SUCCESS;
760		} else {
761			result = ISC_R_FAILURE;
762		}
763		break;
764	default:
765		/*
766		 * this should never happen.  If it does, the code is
767		 * screwed up!
768		 */
769		db->log(ISC_LOG_ERROR, "Incorrect query flag passed to "
770				       "dlz_ldap_get_results");
771		result = ISC_R_UNEXPECTED;
772	}
773
774cleanup:
775	/* it's always good to cleanup after yourself */
776
777	/* if we retrieved results, free them */
778	if (ldap_msg != NULL) {
779		ldap_msgfree(ldap_msg);
780	}
781
782	if (ldap_url != NULL) {
783		ldap_free_urldesc(ldap_url);
784	}
785
786	/* cleanup */
787	if (dbi->zone != NULL) {
788		free(dbi->zone);
789	}
790	if (dbi->record != NULL) {
791		free(dbi->record);
792	}
793	if (dbi->client != NULL) {
794		free(dbi->client);
795	}
796	dbi->zone = dbi->record = dbi->client = NULL;
797
798	/* release the lock so another thread can use this dbi */
799	(void)dlz_mutex_unlock(&dbi->lock);
800
801	/* release query string */
802	if (querystring != NULL) {
803		free(querystring);
804	}
805
806	/* return result */
807	return (result);
808}
809
810/*
811 * DLZ methods
812 */
813isc_result_t
814dlz_allowzonexfr(void *dbdata, const char *name, const char *client) {
815	isc_result_t result;
816
817	/* check to see if we are authoritative for the zone first */
818#if DLZ_DLOPEN_VERSION < 3
819	result = dlz_findzonedb(dbdata, name);
820#else  /* if DLZ_DLOPEN_VERSION < 3 */
821	result = dlz_findzonedb(dbdata, name, NULL, NULL);
822#endif /* if DLZ_DLOPEN_VERSION < 3 */
823	if (result != ISC_R_SUCCESS) {
824		return (result);
825	}
826
827	/* get all the zone data */
828	result = dlz_ldap_get_results(name, NULL, client, ALLOWXFR, dbdata,
829				      NULL);
830	return (result);
831}
832
833isc_result_t
834dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) {
835	return (dlz_ldap_get_results(zone, NULL, NULL, ALLNODES, dbdata,
836				     allnodes));
837}
838
839isc_result_t
840dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup) {
841	return (dlz_ldap_get_results(zone, NULL, NULL, AUTHORITY, dbdata,
842				     lookup));
843}
844
845#if DLZ_DLOPEN_VERSION < 3
846isc_result_t
847dlz_findzonedb(void *dbdata, const char *name)
848#else  /* if DLZ_DLOPEN_VERSION < 3 */
849isc_result_t
850dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods,
851	       dns_clientinfo_t *clientinfo)
852#endif /* if DLZ_DLOPEN_VERSION < 3 */
853{
854#if DLZ_DLOPEN_VERSION >= 3
855	UNUSED(methods);
856	UNUSED(clientinfo);
857#endif /* if DLZ_DLOPEN_VERSION >= 3 */
858	return (dlz_ldap_get_results(name, NULL, NULL, FINDZONE, dbdata, NULL));
859}
860
861#if DLZ_DLOPEN_VERSION == 1
862isc_result_t
863dlz_lookup(const char *zone, const char *name, void *dbdata,
864	   dns_sdlzlookup_t *lookup)
865#else  /* if DLZ_DLOPEN_VERSION == 1 */
866isc_result_t
867dlz_lookup(const char *zone, const char *name, void *dbdata,
868	   dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods,
869	   dns_clientinfo_t *clientinfo)
870#endif /* if DLZ_DLOPEN_VERSION == 1 */
871{
872	isc_result_t result;
873
874#if DLZ_DLOPEN_VERSION >= 2
875	UNUSED(methods);
876	UNUSED(clientinfo);
877#endif /* if DLZ_DLOPEN_VERSION >= 2 */
878
879	if (strcmp(name, "*") == 0) {
880		result = dlz_ldap_get_results(zone, "~", NULL, LOOKUP, dbdata,
881					      lookup);
882	} else {
883		result = dlz_ldap_get_results(zone, name, NULL, LOOKUP, dbdata,
884					      lookup);
885	}
886	return (result);
887}
888
889isc_result_t
890dlz_create(const char *dlzname, unsigned int argc, char *argv[], void **dbdata,
891	   ...) {
892	isc_result_t result = ISC_R_FAILURE;
893	ldap_instance_t *ldap = NULL;
894	dbinstance_t *dbi = NULL;
895	const char *helper_name = NULL;
896	int protocol, method, dbcount, i;
897	char *endp = NULL;
898	va_list ap;
899
900	UNUSED(dlzname);
901
902	/* allocate memory for LDAP instance */
903	ldap = calloc(1, sizeof(ldap_instance_t));
904	if (ldap == NULL) {
905		return (ISC_R_NOMEMORY);
906	}
907	memset(ldap, 0, sizeof(ldap_instance_t));
908
909	/* Fill in the helper functions */
910	va_start(ap, dbdata);
911	while ((helper_name = va_arg(ap, const char *)) != NULL) {
912		b9_add_helper(ldap, helper_name, va_arg(ap, void *));
913	}
914	va_end(ap);
915
916	/* if debugging, let user know we are multithreaded. */
917	ldap->log(ISC_LOG_DEBUG(1), "LDAP driver running multithreaded");
918
919	if (argc < 9) {
920		ldap->log(ISC_LOG_ERROR, "LDAP driver requires at least "
921					 "8 command line args.");
922		goto cleanup;
923	}
924
925	/* no more than 13 arg's should be passed to the driver */
926	if (argc > 12) {
927		ldap->log(ISC_LOG_ERROR, "LDAP driver cannot accept more than "
928					 "11 command line args.");
929		goto cleanup;
930	}
931
932	/* determine protocol version. */
933	if (strncasecmp(argv[2], V2, strlen(V2)) == 0) {
934		protocol = 2;
935	} else if (strncasecmp(argv[2], V3, strlen(V3)) == 0) {
936		protocol = 3;
937	} else {
938		ldap->log(ISC_LOG_ERROR,
939			  "LDAP driver protocol must be either %s or %s", V2,
940			  V3);
941		goto cleanup;
942	}
943
944	/* determine connection method. */
945	if (strncasecmp(argv[3], SIMPLE, strlen(SIMPLE)) == 0) {
946		method = LDAP_AUTH_SIMPLE;
947	} else if (strncasecmp(argv[3], KRB41, strlen(KRB41)) == 0) {
948		method = LDAP_AUTH_KRBV41;
949	} else if (strncasecmp(argv[3], KRB42, strlen(KRB42)) == 0) {
950		method = LDAP_AUTH_KRBV42;
951	} else {
952		ldap->log(ISC_LOG_ERROR,
953			  "LDAP driver authentication method must be "
954			  "one of %s, %s or %s",
955			  SIMPLE, KRB41, KRB42);
956		goto cleanup;
957	}
958
959	/* check how many db connections we should create */
960	dbcount = strtol(argv[1], &endp, 10);
961	if (*endp != '\0' || dbcount < 0) {
962		ldap->log(ISC_LOG_ERROR, "LDAP driver database connection "
963					 "count "
964					 "must be positive.");
965		goto cleanup;
966	}
967
968	/* check that LDAP URL parameters make sense */
969	switch (argc) {
970	case 12:
971		result = dlz_ldap_checkURL(ldap, argv[11], 0,
972					   "allow zone transfer");
973		if (result != ISC_R_SUCCESS) {
974			goto cleanup;
975		}
976		FALLTHROUGH;
977	case 11:
978		result = dlz_ldap_checkURL(ldap, argv[10], 3, "all nodes");
979		if (result != ISC_R_SUCCESS) {
980			goto cleanup;
981		}
982		FALLTHROUGH;
983	case 10:
984		if (strlen(argv[9]) > 0) {
985			result = dlz_ldap_checkURL(ldap, argv[9], 3,
986						   "authority");
987			if (result != ISC_R_SUCCESS) {
988				goto cleanup;
989			}
990		}
991		FALLTHROUGH;
992	case 9:
993		result = dlz_ldap_checkURL(ldap, argv[8], 3, "lookup");
994		if (result != ISC_R_SUCCESS) {
995			goto cleanup;
996		}
997		result = dlz_ldap_checkURL(ldap, argv[7], 0, "find zone");
998		if (result != ISC_R_SUCCESS) {
999			goto cleanup;
1000		}
1001		break;
1002	default:
1003		/* not really needed, should shut up compiler. */
1004		result = ISC_R_FAILURE;
1005	}
1006
1007	/* store info needed to automatically re-connect. */
1008	ldap->protocol = protocol;
1009	ldap->method = method;
1010	ldap->hosts = strdup(argv[6]);
1011	if (ldap->hosts == NULL) {
1012		result = ISC_R_NOMEMORY;
1013		goto cleanup;
1014	}
1015	ldap->user = strdup(argv[4]);
1016	if (ldap->user == NULL) {
1017		result = ISC_R_NOMEMORY;
1018		goto cleanup;
1019	}
1020	ldap->cred = strdup(argv[5]);
1021	if (ldap->cred == NULL) {
1022		result = ISC_R_NOMEMORY;
1023		goto cleanup;
1024	}
1025
1026	/* allocate memory for database connection list */
1027	ldap->db = calloc(1, sizeof(db_list_t));
1028	if (ldap->db == NULL) {
1029		result = ISC_R_NOMEMORY;
1030		goto cleanup;
1031	}
1032
1033	/* initialize DB connection list */
1034	DLZ_LIST_INIT(*(ldap->db));
1035
1036	/*
1037	 * create the appropriate number of database instances (DBI)
1038	 * append each new DBI to the end of the list
1039	 */
1040	for (i = 0; i < dbcount; i++) {
1041		/* how many queries were passed in from config file? */
1042		switch (argc) {
1043		case 9:
1044			result = build_dbinstance(NULL, NULL, NULL, argv[7],
1045						  argv[8], NULL, &dbi,
1046						  ldap->log);
1047			break;
1048		case 10:
1049			result = build_dbinstance(NULL, NULL, argv[9], argv[7],
1050						  argv[8], NULL, &dbi,
1051						  ldap->log);
1052			break;
1053		case 11:
1054			result = build_dbinstance(argv[10], NULL, argv[9],
1055						  argv[7], argv[8], NULL, &dbi,
1056						  ldap->log);
1057			break;
1058		case 12:
1059			result = build_dbinstance(argv[10], argv[11], argv[9],
1060						  argv[7], argv[8], NULL, &dbi,
1061						  ldap->log);
1062			break;
1063		default:
1064			/* not really needed, should shut up compiler. */
1065			result = ISC_R_FAILURE;
1066		}
1067
1068		if (result == ISC_R_SUCCESS) {
1069			ldap->log(ISC_LOG_DEBUG(2), "LDAP driver created "
1070						    "database instance "
1071						    "object.");
1072		} else { /* unsuccessful?, log err msg and cleanup. */
1073			ldap->log(ISC_LOG_ERROR, "LDAP driver could not create "
1074						 "database instance object.");
1075			goto cleanup;
1076		}
1077
1078		/* when multithreaded, build a list of DBI's */
1079		DLZ_LINK_INIT(dbi, link);
1080		DLZ_LIST_APPEND(*(ldap->db), dbi, link);
1081		/* attempt to connect */
1082		result = dlz_ldap_connect(ldap, dbi);
1083
1084		/*
1085		 * if db connection cannot be created, log err msg and
1086		 * cleanup.
1087		 */
1088		switch (result) {
1089		/* success, do nothing */
1090		case ISC_R_SUCCESS:
1091			break;
1092		/*
1093		 * no memory means ldap_init could not
1094		 * allocate memory
1095		 */
1096		case ISC_R_NOMEMORY:
1097			ldap->log(ISC_LOG_ERROR,
1098				  "LDAP driver could not allocate memory "
1099				  "for connection number %u",
1100				  i + 1);
1101			goto cleanup;
1102		/*
1103		 * no perm means ldap_set_option could not set
1104		 * protocol version
1105		 */
1106		case ISC_R_NOPERM:
1107			ldap->log(ISC_LOG_ERROR, "LDAP driver could not "
1108						 "set protocol version.");
1109			result = ISC_R_FAILURE;
1110			goto cleanup;
1111		/* failure means couldn't connect to ldap server */
1112		case ISC_R_FAILURE:
1113			ldap->log(ISC_LOG_ERROR,
1114				  "LDAP driver could not bind "
1115				  "connection number %u to server.",
1116				  i + 1);
1117			goto cleanup;
1118		/*
1119		 * default should never happen.  If it does,
1120		 * major errors.
1121		 */
1122		default:
1123			ldap->log(ISC_LOG_ERROR, "dlz_create() failed (%d)",
1124				  result);
1125			result = ISC_R_UNEXPECTED;
1126			goto cleanup;
1127		}
1128
1129		/* set DBI = null for next loop through. */
1130		dbi = NULL;
1131	}
1132
1133	/* set dbdata to the ldap_instance we created. */
1134	*dbdata = ldap;
1135
1136	return (ISC_R_SUCCESS);
1137
1138cleanup:
1139	dlz_destroy(ldap);
1140
1141	return (result);
1142}
1143
1144void
1145dlz_destroy(void *dbdata) {
1146	if (dbdata != NULL) {
1147		ldap_instance_t *db = (ldap_instance_t *)dbdata;
1148		/* cleanup the list of DBI's */
1149		if (db->db != NULL) {
1150			dlz_ldap_destroy_dblist((db_list_t *)(db->db));
1151		}
1152
1153		if (db->hosts != NULL) {
1154			free(db->hosts);
1155		}
1156		if (db->user != NULL) {
1157			free(db->user);
1158		}
1159		if (db->cred != NULL) {
1160			free(db->cred);
1161		}
1162		free(dbdata);
1163	}
1164}
1165
1166/*
1167 * Return the version of the API
1168 */
1169int
1170dlz_version(unsigned int *flags) {
1171	*flags |= DNS_SDLZFLAG_RELATIVERDATA | DNS_SDLZFLAG_THREADSAFE;
1172	return (DLZ_DLOPEN_VERSION);
1173}
1174
1175/*
1176 * Register a helper function from the bind9 dlz_dlopen driver
1177 */
1178static void
1179b9_add_helper(ldap_instance_t *db, const char *helper_name, void *ptr) {
1180	if (strcmp(helper_name, "log") == 0) {
1181		db->log = (log_t *)ptr;
1182	}
1183	if (strcmp(helper_name, "putrr") == 0) {
1184		db->putrr = (dns_sdlz_putrr_t *)ptr;
1185	}
1186	if (strcmp(helper_name, "putnamedrr") == 0) {
1187		db->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr;
1188	}
1189	if (strcmp(helper_name, "writeable_zone") == 0) {
1190		db->writeable_zone = (dns_dlz_writeablezone_t *)ptr;
1191	}
1192}
1193