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