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