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