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