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