1 /* $NetBSD: ldapdb.c,v 1.5 2015/07/08 17:28:56 christos Exp $ */ 2 3 /* 4 * ldapdb.c version 1.0-beta 5 * 6 * Copyright (C) 2002, 2004 Stig Venaas 7 * 8 * Permission to use, copy, modify, and distribute this software for any 9 * purpose with or without fee is hereby granted, provided that the above 10 * copyright notice and this permission notice appear in all copies. 11 * 12 * Contributors: Jeremy C. McDermond 13 */ 14 15 /* 16 * If you want to use TLS, uncomment the define below 17 */ 18 /* #define LDAPDB_TLS */ 19 20 /* 21 * If you are using an old LDAP API uncomment the define below. Only do this 22 * if you know what you're doing or get compilation errors on ldap_memfree(). 23 * This also forces LDAPv2. 24 */ 25 /* #define LDAPDB_RFC1823API */ 26 27 /* Using LDAPv3 by default, change this if you want v2 */ 28 #ifndef LDAPDB_LDAP_VERSION 29 #define LDAPDB_LDAP_VERSION 3 30 #endif 31 32 #include <config.h> 33 34 #include <string.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <ctype.h> 38 39 #include <isc/mem.h> 40 #include <isc/print.h> 41 #include <isc/result.h> 42 #include <isc/util.h> 43 #include <isc/thread.h> 44 45 #include <dns/sdb.h> 46 47 #include <named/globals.h> 48 #include <named/log.h> 49 50 #include <ldap.h> 51 #include "ldapdb.h" 52 53 /* 54 * A simple database driver for LDAP 55 */ 56 57 /* enough for name with 8 labels of max length */ 58 #define MAXNAMELEN 519 59 60 static dns_sdbimplementation_t *ldapdb = NULL; 61 62 struct ldapdb_data { 63 char *hostport; 64 char *hostname; 65 int portno; 66 char *base; 67 int defaultttl; 68 char *filterall; 69 int filteralllen; 70 char *filterone; 71 int filteronelen; 72 char *filtername; 73 char *bindname; 74 char *bindpw; 75 #ifdef LDAPDB_TLS 76 int tls; 77 #endif 78 }; 79 80 /* used by ldapdb_getconn */ 81 82 struct ldapdb_entry { 83 void *index; 84 size_t size; 85 void *data; 86 struct ldapdb_entry *next; 87 }; 88 89 static struct ldapdb_entry *ldapdb_find(struct ldapdb_entry *stack, 90 const void *index, size_t size) { 91 while (stack != NULL) { 92 if (stack->size == size && !memcmp(stack->index, index, size)) 93 return stack; 94 stack = stack->next; 95 } 96 return NULL; 97 } 98 99 static void ldapdb_insert(struct ldapdb_entry **stack, 100 struct ldapdb_entry *item) { 101 item->next = *stack; 102 *stack = item; 103 } 104 105 static void ldapdb_lock(int what) { 106 static isc_mutex_t lock; 107 108 switch (what) { 109 case 0: 110 isc_mutex_init(&lock); 111 break; 112 case 1: 113 LOCK(&lock); 114 break; 115 case -1: 116 UNLOCK(&lock); 117 break; 118 } 119 } 120 121 /* data == NULL means cleanup */ 122 static LDAP ** 123 ldapdb_getconn(struct ldapdb_data *data) 124 { 125 static struct ldapdb_entry *allthreadsdata = NULL; 126 struct ldapdb_entry *threaddata, *conndata; 127 unsigned long threadid; 128 129 if (data == NULL) { 130 /* cleanup */ 131 /* lock out other threads */ 132 ldapdb_lock(1); 133 while (allthreadsdata != NULL) { 134 threaddata = allthreadsdata; 135 free(threaddata->index); 136 while (threaddata->data != NULL) { 137 conndata = threaddata->data; 138 if (conndata->data != NULL) 139 ldap_unbind((LDAP *)conndata->data); 140 threaddata->data = conndata->next; 141 free(conndata); 142 } 143 allthreadsdata = threaddata->next; 144 free(threaddata); 145 } 146 ldapdb_lock(-1); 147 return (NULL); 148 } 149 150 /* look for connection data for current thread */ 151 threadid = isc_thread_self(); 152 threaddata = ldapdb_find(allthreadsdata, &threadid, sizeof(threadid)); 153 if (threaddata == NULL) { 154 /* no data for this thread, create empty connection list */ 155 threaddata = malloc(sizeof(*threaddata)); 156 if (threaddata == NULL) 157 return (NULL); 158 threaddata->index = malloc(sizeof(threadid)); 159 if (threaddata->index == NULL) { 160 free(threaddata); 161 return (NULL); 162 } 163 *(unsigned long *)threaddata->index = threadid; 164 threaddata->size = sizeof(threadid); 165 threaddata->data = NULL; 166 167 /* need to lock out other threads here */ 168 ldapdb_lock(1); 169 ldapdb_insert(&allthreadsdata, threaddata); 170 ldapdb_lock(-1); 171 } 172 173 /* threaddata points at the connection list for current thread */ 174 /* look for existing connection to our server */ 175 conndata = ldapdb_find((struct ldapdb_entry *)threaddata->data, 176 data->hostport, strlen(data->hostport)); 177 if (conndata == NULL) { 178 /* no connection data structure for this server, create one */ 179 conndata = malloc(sizeof(*conndata)); 180 if (conndata == NULL) 181 return (NULL); 182 conndata->index = data->hostport; 183 conndata->size = strlen(data->hostport); 184 conndata->data = NULL; 185 ldapdb_insert((struct ldapdb_entry **)&threaddata->data, 186 conndata); 187 } 188 189 return (LDAP **)&conndata->data; 190 } 191 192 static void 193 ldapdb_bind(struct ldapdb_data *data, LDAP **ldp) 194 { 195 #ifndef LDAPDB_RFC1823API 196 const int ver = LDAPDB_LDAP_VERSION; 197 #endif 198 199 if (*ldp != NULL) 200 ldap_unbind(*ldp); 201 *ldp = ldap_open(data->hostname, data->portno); 202 if (*ldp == NULL) 203 return; 204 205 #ifndef LDAPDB_RFC1823API 206 ldap_set_option(*ldp, LDAP_OPT_PROTOCOL_VERSION, &ver); 207 #endif 208 209 #ifdef LDAPDB_TLS 210 if (data->tls) { 211 ldap_start_tls_s(*ldp, NULL, NULL); 212 } 213 #endif 214 215 if (ldap_simple_bind_s(*ldp, data->bindname, data->bindpw) != LDAP_SUCCESS) { 216 ldap_unbind(*ldp); 217 *ldp = NULL; 218 } 219 } 220 221 #ifdef DNS_CLIENTINFO_VERSION 222 static isc_result_t 223 ldapdb_search(const char *zone, const char *name, void *dbdata, void *retdata, 224 dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo) 225 #else 226 static isc_result_t 227 ldapdb_search(const char *zone, const char *name, void *dbdata, void *retdata, 228 void *methods, void *clientinfo) 229 #endif /* DNS_CLIENTINFO_VERSION */ 230 { 231 struct ldapdb_data *data = dbdata; 232 isc_result_t result = ISC_R_NOTFOUND; 233 LDAP **ldp; 234 LDAPMessage *res, *e; 235 char *fltr, *a, **vals = NULL, **names = NULL; 236 char type[64]; 237 #ifdef LDAPDB_RFC1823API 238 void *ptr; 239 #else 240 BerElement *ptr; 241 #endif 242 int i, j, errno, msgid; 243 244 UNUSED(methods); 245 UNUSED(clientinfo); 246 247 ldp = ldapdb_getconn(data); 248 if (ldp == NULL) 249 return (ISC_R_FAILURE); 250 if (*ldp == NULL) { 251 ldapdb_bind(data, ldp); 252 if (*ldp == NULL) { 253 isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, 254 "LDAP sdb zone '%s': bind failed", zone); 255 return (ISC_R_FAILURE); 256 } 257 } 258 259 if (name == NULL) { 260 fltr = data->filterall; 261 } else { 262 if (strlen(name) > MAXNAMELEN) { 263 isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, 264 "LDAP sdb zone '%s': name %s too long", zone, name); 265 return (ISC_R_FAILURE); 266 } 267 sprintf(data->filtername, "%s))", name); 268 fltr = data->filterone; 269 } 270 271 msgid = ldap_search(*ldp, data->base, LDAP_SCOPE_SUBTREE, fltr, NULL, 0); 272 if (msgid == -1) { 273 ldapdb_bind(data, ldp); 274 if (*ldp != NULL) 275 msgid = ldap_search(*ldp, data->base, LDAP_SCOPE_SUBTREE, fltr, NULL, 0); 276 } 277 278 if (*ldp == NULL || msgid == -1) { 279 isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, 280 "LDAP sdb zone '%s': search failed, filter %s", zone, fltr); 281 return (ISC_R_FAILURE); 282 } 283 284 /* Get the records one by one as they arrive and return them to bind */ 285 while ((errno = ldap_result(*ldp, msgid, 0, NULL, &res)) != LDAP_RES_SEARCH_RESULT ) { 286 LDAP *ld = *ldp; 287 int ttl = data->defaultttl; 288 289 /* not supporting continuation references at present */ 290 if (errno != LDAP_RES_SEARCH_ENTRY) { 291 isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, 292 "LDAP sdb zone '%s': ldap_result returned %d", zone, errno); 293 ldap_msgfree(res); 294 return (ISC_R_FAILURE); 295 } 296 297 /* only one entry per result message */ 298 e = ldap_first_entry(ld, res); 299 if (e == NULL) { 300 ldap_msgfree(res); 301 isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, 302 "LDAP sdb zone '%s': ldap_first_entry failed", zone); 303 return (ISC_R_FAILURE); 304 } 305 306 if (name == NULL) { 307 names = ldap_get_values(ld, e, "relativeDomainName"); 308 if (names == NULL) 309 continue; 310 } 311 312 vals = ldap_get_values(ld, e, "dNSTTL"); 313 if (vals != NULL) { 314 ttl = atoi(vals[0]); 315 ldap_value_free(vals); 316 } 317 318 for (a = ldap_first_attribute(ld, e, &ptr); a != NULL; a = ldap_next_attribute(ld, e, ptr)) { 319 char *s; 320 321 for (s = a; *s; s++) 322 *s = toupper(*s); 323 s = strstr(a, "RECORD"); 324 if ((s == NULL) || (s == a) || (s - a >= (signed int)sizeof(type))) { 325 #ifndef LDAPDB_RFC1823API 326 ldap_memfree(a); 327 #endif 328 continue; 329 } 330 331 strncpy(type, a, s - a); 332 type[s - a] = '\0'; 333 vals = ldap_get_values(ld, e, a); 334 if (vals != NULL) { 335 for (i = 0; vals[i] != NULL; i++) { 336 if (name != NULL) { 337 result = dns_sdb_putrr(retdata, type, ttl, vals[i]); 338 } else { 339 for (j = 0; names[j] != NULL; j++) { 340 result = dns_sdb_putnamedrr(retdata, names[j], type, ttl, vals[i]); 341 if (result != ISC_R_SUCCESS) 342 break; 343 } 344 } 345 ; if (result != ISC_R_SUCCESS) { 346 isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, 347 "LDAP sdb zone '%s': dns_sdb_put... failed for %s", zone, vals[i]); 348 ldap_value_free(vals); 349 #ifndef LDAPDB_RFC1823API 350 ldap_memfree(a); 351 if (ptr != NULL) 352 ber_free(ptr, 0); 353 #endif 354 if (name == NULL) 355 ldap_value_free(names); 356 ldap_msgfree(res); 357 return (ISC_R_FAILURE); 358 } 359 } 360 ldap_value_free(vals); 361 } 362 #ifndef LDAPDB_RFC1823API 363 ldap_memfree(a); 364 #endif 365 } 366 #ifndef LDAPDB_RFC1823API 367 if (ptr != NULL) 368 ber_free(ptr, 0); 369 #endif 370 if (name == NULL) 371 ldap_value_free(names); 372 373 /* free this result */ 374 ldap_msgfree(res); 375 } 376 377 /* free final result */ 378 ldap_msgfree(res); 379 return (result); 380 } 381 382 383 /* callback routines */ 384 #ifdef DNS_CLIENTINFO_VERSION 385 static isc_result_t 386 ldapdb_lookup(const char *zone, const char *name, void *dbdata, 387 dns_sdblookup_t *lookup, dns_clientinfomethods_t *methods, 388 dns_clientinfo_t *clientinfo) 389 { 390 UNUSED(methods); 391 UNUSED(clientinfo); 392 return (ldapdb_search(zone, name, dbdata, lookup, NULL, NULL)); 393 } 394 #else 395 static isc_result_t 396 ldapdb_lookup(const char *zone, const char *name, void *dbdata, 397 dns_sdblookup_t *lookup) 398 { 399 return (ldapdb_search(zone, name, dbdata, lookup, methods, 400 clientinfo)); 401 } 402 #endif /* DNS_CLIENTINFO_VERSION */ 403 404 static isc_result_t 405 ldapdb_allnodes(const char *zone, void *dbdata, 406 dns_sdballnodes_t *allnodes) 407 { 408 return (ldapdb_search(zone, NULL, dbdata, allnodes, NULL, NULL)); 409 } 410 411 static char * 412 unhex(char *in) 413 { 414 static const char hexdigits[] = "0123456789abcdef"; 415 char *p, *s = in; 416 int d1, d2; 417 418 while ((s = strchr(s, '%'))) { 419 if (!(s[1] && s[2])) 420 return NULL; 421 if ((p = strchr(hexdigits, tolower(s[1]))) == NULL) 422 return NULL; 423 d1 = p - hexdigits; 424 if ((p = strchr(hexdigits, tolower(s[2]))) == NULL) 425 return NULL; 426 d2 = p - hexdigits; 427 *s++ = d1 << 4 | d2; 428 memmove(s, s + 2, strlen(s) - 1); 429 } 430 return in; 431 } 432 433 /* returns 0 for ok, -1 for bad syntax, -2 for unknown critical extension */ 434 static int 435 parseextensions(char *extensions, struct ldapdb_data *data) 436 { 437 char *s, *next, *name, *value; 438 int critical; 439 440 while (extensions != NULL) { 441 s = strchr(extensions, ','); 442 if (s != NULL) { 443 *s++ = '\0'; 444 next = s; 445 } else { 446 next = NULL; 447 } 448 449 if (*extensions != '\0') { 450 s = strchr(extensions, '='); 451 if (s != NULL) { 452 *s++ = '\0'; 453 value = *s != '\0' ? s : NULL; 454 } else { 455 value = NULL; 456 } 457 name = extensions; 458 459 critical = *name == '!'; 460 if (critical) { 461 name++; 462 } 463 if (*name == '\0') { 464 return -1; 465 } 466 467 if (!strcasecmp(name, "bindname")) { 468 data->bindname = value; 469 } else if (!strcasecmp(name, "x-bindpw")) { 470 data->bindpw = value; 471 #ifdef LDAPDB_TLS 472 } else if (!strcasecmp(name, "x-tls")) { 473 data->tls = value == NULL || !strcasecmp(value, "true"); 474 #endif 475 } else if (critical) { 476 return -2; 477 } 478 } 479 extensions = next; 480 } 481 return 0; 482 } 483 484 static void 485 free_data(struct ldapdb_data *data) 486 { 487 if (data->hostport != NULL) 488 isc_mem_free(ns_g_mctx, data->hostport); 489 if (data->hostname != NULL) 490 isc_mem_free(ns_g_mctx, data->hostname); 491 if (data->filterall != NULL) 492 isc_mem_put(ns_g_mctx, data->filterall, data->filteralllen); 493 if (data->filterone != NULL) 494 isc_mem_put(ns_g_mctx, data->filterone, data->filteronelen); 495 isc_mem_put(ns_g_mctx, data, sizeof(struct ldapdb_data)); 496 } 497 498 499 static isc_result_t 500 ldapdb_create(const char *zone, int argc, char **argv, 501 void *driverdata, void **dbdata) 502 { 503 struct ldapdb_data *data; 504 char *s, *filter = NULL, *extensions = NULL; 505 int defaultttl; 506 507 UNUSED(driverdata); 508 509 /* we assume that only one thread will call create at a time */ 510 /* want to do this only once for all instances */ 511 512 if ((argc < 2) 513 || (argv[0] != strstr( argv[0], "ldap://")) 514 || ((defaultttl = atoi(argv[1])) < 1)) 515 return (ISC_R_FAILURE); 516 data = isc_mem_get(ns_g_mctx, sizeof(struct ldapdb_data)); 517 if (data == NULL) 518 return (ISC_R_NOMEMORY); 519 520 memset(data, 0, sizeof(struct ldapdb_data)); 521 data->hostport = isc_mem_strdup(ns_g_mctx, argv[0] + strlen("ldap://")); 522 if (data->hostport == NULL) { 523 free_data(data); 524 return (ISC_R_NOMEMORY); 525 } 526 527 data->defaultttl = defaultttl; 528 529 s = strchr(data->hostport, '/'); 530 if (s != NULL) { 531 *s++ = '\0'; 532 data->base = s; 533 /* attrs, scope, filter etc? */ 534 s = strchr(s, '?'); 535 if (s != NULL) { 536 *s++ = '\0'; 537 /* ignore attributes */ 538 s = strchr(s, '?'); 539 if (s != NULL) { 540 *s++ = '\0'; 541 /* ignore scope */ 542 s = strchr(s, '?'); 543 if (s != NULL) { 544 *s++ = '\0'; 545 /* filter */ 546 filter = s; 547 s = strchr(s, '?'); 548 if (s != NULL) { 549 *s++ = '\0'; 550 /* extensions */ 551 extensions = s; 552 s = strchr(s, '?'); 553 if (s != NULL) { 554 *s++ = '\0'; 555 } 556 if (*extensions == '\0') { 557 extensions = NULL; 558 } 559 } 560 if (*filter == '\0') { 561 filter = NULL; 562 } 563 } 564 } 565 } 566 if (*data->base == '\0') { 567 data->base = NULL; 568 } 569 } 570 571 /* parse extensions */ 572 if (extensions != NULL) { 573 int err; 574 575 err = parseextensions(extensions, data); 576 if (err < 0) { 577 /* err should be -1 or -2 */ 578 free_data(data); 579 if (err == -1) { 580 isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, 581 "LDAP sdb zone '%s': URL: extension syntax error", zone); 582 } else if (err == -2) { 583 isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, 584 "LDAP sdb zone '%s': URL: unknown critical extension", zone); 585 } 586 return (ISC_R_FAILURE); 587 } 588 } 589 590 if ((data->base != NULL && unhex(data->base) == NULL) || 591 (filter != NULL && unhex(filter) == NULL) || 592 (data->bindname != NULL && unhex(data->bindname) == NULL) || 593 (data->bindpw != NULL && unhex(data->bindpw) == NULL)) { 594 free_data(data); 595 isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, 596 "LDAP sdb zone '%s': URL: bad hex values", zone); 597 return (ISC_R_FAILURE); 598 } 599 600 /* compute filterall and filterone once and for all */ 601 if (filter == NULL) { 602 data->filteralllen = strlen(zone) + strlen("(zoneName=)") + 1; 603 data->filteronelen = strlen(zone) + strlen("(&(zoneName=)(relativeDomainName=))") + MAXNAMELEN + 1; 604 } else { 605 data->filteralllen = strlen(filter) + strlen(zone) + strlen("(&(zoneName=))") + 1; 606 data->filteronelen = strlen(filter) + strlen(zone) + strlen("(&(zoneName=)(relativeDomainName=))") + MAXNAMELEN + 1; 607 } 608 609 data->filterall = isc_mem_get(ns_g_mctx, data->filteralllen); 610 if (data->filterall == NULL) { 611 free_data(data); 612 return (ISC_R_NOMEMORY); 613 } 614 data->filterone = isc_mem_get(ns_g_mctx, data->filteronelen); 615 if (data->filterone == NULL) { 616 free_data(data); 617 return (ISC_R_NOMEMORY); 618 } 619 620 if (filter == NULL) { 621 sprintf(data->filterall, "(zoneName=%s)", zone); 622 sprintf(data->filterone, "(&(zoneName=%s)(relativeDomainName=", zone); 623 } else { 624 sprintf(data->filterall, "(&%s(zoneName=%s))", filter, zone); 625 sprintf(data->filterone, "(&%s(zoneName=%s)(relativeDomainName=", filter, zone); 626 } 627 data->filtername = data->filterone + strlen(data->filterone); 628 629 /* support URLs with literal IPv6 addresses */ 630 data->hostname = isc_mem_strdup(ns_g_mctx, data->hostport + (*data->hostport == '[' ? 1 : 0)); 631 if (data->hostname == NULL) { 632 free_data(data); 633 return (ISC_R_NOMEMORY); 634 } 635 636 if (*data->hostport == '[' && 637 (s = strchr(data->hostname, ']')) != NULL ) 638 *s++ = '\0'; 639 else 640 s = data->hostname; 641 s = strchr(s, ':'); 642 if (s != NULL) { 643 *s++ = '\0'; 644 data->portno = atoi(s); 645 } else 646 data->portno = LDAP_PORT; 647 648 *dbdata = data; 649 return (ISC_R_SUCCESS); 650 } 651 652 static void 653 ldapdb_destroy(const char *zone, void *driverdata, void **dbdata) { 654 struct ldapdb_data *data = *dbdata; 655 656 UNUSED(zone); 657 UNUSED(driverdata); 658 659 free_data(data); 660 } 661 662 static dns_sdbmethods_t ldapdb_methods = { 663 ldapdb_lookup, 664 NULL, /* authority */ 665 ldapdb_allnodes, 666 ldapdb_create, 667 ldapdb_destroy, 668 NULL /* lookup2 */ 669 }; 670 671 /* Wrapper around dns_sdb_register() */ 672 isc_result_t 673 ldapdb_init(void) { 674 unsigned int flags = 675 DNS_SDBFLAG_RELATIVEOWNER | 676 DNS_SDBFLAG_RELATIVERDATA | 677 DNS_SDBFLAG_THREADSAFE; 678 679 ldapdb_lock(0); 680 return (dns_sdb_register("ldap", &ldapdb_methods, NULL, flags, 681 ns_g_mctx, &ldapdb)); 682 } 683 684 /* Wrapper around dns_sdb_unregister() */ 685 void 686 ldapdb_clear(void) { 687 if (ldapdb != NULL) { 688 /* clean up thread data */ 689 ldapdb_getconn(NULL); 690 dns_sdb_unregister(&ldapdb); 691 } 692 } 693