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