text: /*
   * ldapdb.c version 1.0-beta
   *
   * Copyright (C) 2002, 2004 Stig Venaas
   *
   * Permission to use, copy, modify, and distribute this software for any
   * purpose with or without fee is hereby granted, provided that the above
   * copyright notice and this permission notice appear in all copies.
   *
   * Contributors: Jeremy C. McDermond
   */
  
  /*
   * If you want to use TLS, uncomment the define below
   */
  /* #define LDAPDB_TLS */
  
  /*
   * If you are using an old LDAP API uncomment the define below. Only do this
   * if you know what you're doing or get compilation errors on ldap_memfree().
   * This also forces LDAPv2.
   */
  /* #define LDAPDB_RFC1823API */
  
  /* Using LDAPv3 by default, change this if you want v2 */
  #ifndef LDAPDB_LDAP_VERSION
  #define LDAPDB_LDAP_VERSION 3
  #endif
  
  #include <config.h>
  
  #include <string.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <ctype.h>
  
  #include <isc/mem.h>
  #include <isc/print.h>
  #include <isc/result.h>
  #include <isc/util.h>
  #include <isc/thread.h>
  
  #include <dns/sdb.h>
  
  #include <named/globals.h>
  #include <named/log.h>
  
  #include <ldap.h>
  #include "ldapdb.h"
  
  /*
   * A simple database driver for LDAP
   */ 
  
  /* enough for name with 8 labels of max length */
  #define MAXNAMELEN 519
  
  static dns_sdbimplementation_t *ldapdb = NULL;
  
  struct ldapdb_data {
  	char *hostport;
  	char *hostname;
  	int portno;
  	char *base;
  	int defaultttl;
  	char *filterall;
  	int filteralllen;
  	char *filterone;
  	int filteronelen;
  	char *filtername;
  	char *bindname;
  	char *bindpw;
  #ifdef LDAPDB_TLS
  	int tls;
  #endif
  };
  
  /* used by ldapdb_getconn */
  
  struct ldapdb_entry {
  	void *index;
  	size_t size;
  	void *data;
  	struct ldapdb_entry *next;
  };
  
  static struct ldapdb_entry *ldapdb_find(struct ldapdb_entry *stack,
  					const void *index, size_t size) {
  	while (stack != NULL) {
  		if (stack->size == size && !memcmp(stack->index, index, size))
  			return stack;
  		stack = stack->next;
  	}
  	return NULL;
  }
  
  static void ldapdb_insert(struct ldapdb_entry **stack,
  			  struct ldapdb_entry *item) {
  	item->next = *stack;
  	*stack = item;
  }
  
  static void ldapdb_lock(int what) {
  	static isc_mutex_t lock;
  
  	switch (what) {
  	case 0:
  		isc_mutex_init(&lock);
  		break;
  	case 1:
  		LOCK(&lock);
  		break;
  	case -1:
  		UNLOCK(&lock);
  		break;
  	}
  }
  
  /* data == NULL means cleanup */
  static LDAP **
  ldapdb_getconn(struct ldapdb_data *data)
  {
  	static struct ldapdb_entry *allthreadsdata = NULL;
  	struct ldapdb_entry *threaddata, *conndata;
  	unsigned long threadid;
  
  	if (data == NULL) {
  		/* cleanup */
  		/* lock out other threads */
  		ldapdb_lock(1);
  		while (allthreadsdata != NULL) {
  			threaddata = allthreadsdata;
  			free(threaddata->index);
  			while (threaddata->data != NULL) {
  				conndata = threaddata->data;
  				free(conndata->index);
  				if (conndata->data != NULL)
  					ldap_unbind((LDAP *)conndata->data);
  				threaddata->data = conndata->next;
  				free(conndata);
  			}
  			allthreadsdata = threaddata->next;
  			free(threaddata);
  		}
  		ldapdb_lock(-1);
  		return (NULL);
  	}
  
  	/* look for connection data for current thread */
  	threadid = isc_thread_self();
  	threaddata = ldapdb_find(allthreadsdata, &threadid, sizeof(threadid));
  	if (threaddata == NULL) {
  		/* no data for this thread, create empty connection list */
  		threaddata = malloc(sizeof(*threaddata));
  		if (threaddata == NULL)
  			return (NULL);
  		threaddata->index = malloc(sizeof(threadid));
  		if (threaddata->index == NULL) {
  			free(threaddata);
  			return (NULL);
  		}
  		*(unsigned long *)threaddata->index = threadid;
  		threaddata->size = sizeof(threadid);
  		threaddata->data = NULL;
  
  		/* need to lock out other threads here */
  		ldapdb_lock(1);
  		ldapdb_insert(&allthreadsdata, threaddata);
  		ldapdb_lock(-1);
  	}
  
  	/* threaddata points at the connection list for current thread */
  	/* look for existing connection to our server */
  	conndata = ldapdb_find((struct ldapdb_entry *)threaddata->data,
  			       data->hostport, strlen(data->hostport));
  	if (conndata == NULL) {
  		/* no connection data structure for this server, create one */
  		conndata = malloc(sizeof(*conndata));
  		if (conndata == NULL)
  			return (NULL);
  		conndata->index = data->hostport;
  		conndata->size = strlen(data->hostport);
  		conndata->data = NULL;
  		ldapdb_insert((struct ldapdb_entry **)&threaddata->data,
  			      conndata);
  	}
  
  	return (LDAP **)&conndata->data;
  }
  
  static void
  ldapdb_bind(struct ldapdb_data *data, LDAP **ldp)
  {
  #ifndef LDAPDB_RFC1823API
  	const int ver = LDAPDB_LDAP_VERSION;
  #endif
  
  	if (*ldp != NULL)
  		ldap_unbind(*ldp);
  	*ldp = ldap_open(data->hostname, data->portno);
  	if (*ldp == NULL)
  		return;
  
  #ifndef LDAPDB_RFC1823API
  	ldap_set_option(*ldp, LDAP_OPT_PROTOCOL_VERSION, &ver);
  #endif
  
  #ifdef LDAPDB_TLS
  	if (data->tls) {
  		ldap_start_tls_s(*ldp, NULL, NULL);
  	}
  #endif
  
  	if (ldap_simple_bind_s(*ldp, data->bindname, data->bindpw) != LDAP_SUCCESS) {
  		ldap_unbind(*ldp);
  		*ldp = NULL;
  	}
  }
  
  #ifdef DNS_CLIENTINFO_VERSION
  static isc_result_t
  ldapdb_search(const char *zone, const char *name, void *dbdata, void *retdata,
  	      dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo)
  #else
  static isc_result_t
  ldapdb_search(const char *zone, const char *name, void *dbdata, void *retdata,
  	      void *methods, void *clientinfo)
  #endif /* DNS_CLIENTINFO_VERSION */
  {
  	struct ldapdb_data *data = dbdata;
  	isc_result_t result = ISC_R_NOTFOUND;
  	LDAP **ldp;
  	LDAPMessage *res, *e;
  	char *fltr, *a, **vals = NULL, **names = NULL;
  	char type[64];
  #ifdef LDAPDB_RFC1823API
  	void *ptr;
  #else
  	BerElement *ptr;
  #endif
  	int i, j, errno, msgid;
  
  	UNUSED(methods);
  	UNUSED(clientinfo);
  
  	ldp = ldapdb_getconn(data);
  	if (ldp == NULL)
  		return (ISC_R_FAILURE);
  	if (*ldp == NULL) {
  		ldapdb_bind(data, ldp);
  		if (*ldp == NULL) {
  			isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,	
  				      "LDAP sdb zone '%s': bind failed", zone);
  			return (ISC_R_FAILURE);
  		}
  	}
  
  	if (name == NULL) {
  		fltr = data->filterall;
  	} else {
  		if (strlen(name) > MAXNAMELEN) {
  			isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
                                        "LDAP sdb zone '%s': name %s too long", zone, name);
  			return (ISC_R_FAILURE);
  		}
  		sprintf(data->filtername, "%s))", name);
  		fltr = data->filterone;
  	}
  
  	msgid = ldap_search(*ldp, data->base, LDAP_SCOPE_SUBTREE, fltr, NULL, 0);
  	if (msgid == -1) {
  		ldapdb_bind(data, ldp);
  		if (*ldp != NULL)
  			msgid = ldap_search(*ldp, data->base, LDAP_SCOPE_SUBTREE, fltr, NULL, 0);
  	}
  
  	if (*ldp == NULL || msgid == -1) {
  		isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,	
  			      "LDAP sdb zone '%s': search failed, filter %s", zone, fltr);
  		return (ISC_R_FAILURE);
  	}
  
  	/* Get the records one by one as they arrive and return them to bind */
  	while ((errno = ldap_result(*ldp, msgid, 0, NULL, &res)) != LDAP_RES_SEARCH_RESULT ) {
  		LDAP *ld = *ldp;
  		int ttl = data->defaultttl;
  
  		/* not supporting continuation references at present */
  		if (errno != LDAP_RES_SEARCH_ENTRY) {
  			isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,	
  				      "LDAP sdb zone '%s': ldap_result returned %d", zone, errno);
  			ldap_msgfree(res);
  			return (ISC_R_FAILURE);
                  }
  
  		/* only one entry per result message */
  		e = ldap_first_entry(ld, res);
  		if (e == NULL) {
  			ldap_msgfree(res);
  			isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,	
  				      "LDAP sdb zone '%s': ldap_first_entry failed", zone);
  			return (ISC_R_FAILURE);
                  }
  
  		if (name == NULL) {
  			names = ldap_get_values(ld, e, "relativeDomainName");
  			if (names == NULL)
  				continue;
  		}
  
  		vals = ldap_get_values(ld, e, "dNSTTL");
  		if (vals != NULL) {
  			ttl = atoi(vals[0]);
  			ldap_value_free(vals);
  		}
  
  		for (a = ldap_first_attribute(ld, e, &ptr); a != NULL; a = ldap_next_attribute(ld, e, ptr)) {
  			char *s;
  
  			for (s = a; *s; s++)
  				*s = toupper(*s);
  			s = strstr(a, "RECORD");
  			if ((s == NULL) || (s == a) || (s - a >= (signed int)sizeof(type))) {
  #ifndef LDAPDB_RFC1823API
  				ldap_memfree(a);
  #endif
  				continue;
  			}
  
  			strncpy(type, a, s - a);
  			type[s - a] = '\0';
  			vals = ldap_get_values(ld, e, a);
  			if (vals != NULL) {
  				for (i = 0; vals[i] != NULL; i++) {
  					if (name != NULL) {
  						result = dns_sdb_putrr(retdata, type, ttl, vals[i]);
  					} else {
  						for (j = 0; names[j] != NULL; j++) {
  							result = dns_sdb_putnamedrr(retdata, names[j], type, ttl, vals[i]);
  							if (result != ISC_R_SUCCESS)
  								break;
  						}
  					}
  ;					if (result != ISC_R_SUCCESS) {
  						isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,	
  							      "LDAP sdb zone '%s': dns_sdb_put... failed for %s", zone, vals[i]);
  						ldap_value_free(vals);
  #ifndef LDAPDB_RFC1823API
  						ldap_memfree(a);
  						if (ptr != NULL)
  							ber_free(ptr, 0);
  #endif
  						if (name == NULL)
  							ldap_value_free(names);
  						ldap_msgfree(res);
  						return (ISC_R_FAILURE);
  					}
  				}
  				ldap_value_free(vals);
  			}
  #ifndef LDAPDB_RFC1823API
  			ldap_memfree(a);
  #endif
  		}
  #ifndef LDAPDB_RFC1823API
  		if (ptr != NULL)
  			ber_free(ptr, 0);
  #endif
  		if (name == NULL)
  			ldap_value_free(names);
  
  		/* free this result */
  		ldap_msgfree(res);
  	}
  
  	/* free final result */
  	ldap_msgfree(res);
          return (result);
  }
  
  
  /* callback routines */
  #ifdef DNS_CLIENTINFO_VERSION
  static isc_result_t
  ldapdb_lookup(const char *zone, const char *name, void *dbdata,
  	      dns_sdblookup_t *lookup, dns_clientinfomethods_t *methods,
  	      dns_clientinfo_t *clientinfo,void *source_ip,dns_rdatatype_t type, void *zone_data)
  {
  	UNUSED(source_ip);
  	UNUSED(type);
  	UNUSED(zone_data);
  	
  	return (ldapdb_search(zone, name, dbdata, lookup, NULL, NULL));
  }
  #else
  static isc_result_t
  ldapdb_lookup(const char *zone, const char *name, void *dbdata,
  	      dns_sdblookup_t *lookup,void *source_ip,dns_rdatatype_t type, void *zone_data)
  {
  	UNUSED(source_ip);
  	UNUSED(type);
  	UNUSED(zone_data);
  	
  	return (ldapdb_search(zone, name, dbdata, lookup, methods,
  			      clientinfo));
  }
  #endif /* DNS_CLIENTINFO_VERSION */
  
  static isc_result_t
  ldapdb_allnodes(const char *zone, void *dbdata,
  		dns_sdballnodes_t *allnodes)
  {
  	return (ldapdb_search(zone, NULL, dbdata, allnodes, NULL, NULL));
  }
  
  static char *
  unhex(char *in)
  {
  	static const char hexdigits[] = "0123456789abcdef";
  	char *p, *s = in;
  	int d1, d2;
  
  	while ((s = strchr(s, '%'))) {
  		if (!(s[1] && s[2]))
  			return NULL;
  		if ((p = strchr(hexdigits, tolower(s[1]))) == NULL)
  			return NULL;
  		d1 = p - hexdigits;
  		if ((p = strchr(hexdigits, tolower(s[2]))) == NULL)
  			return NULL;
  		d2 = p - hexdigits;
  		*s++ = d1 << 4 | d2;
  		memmove(s, s + 2, strlen(s) - 1);
  	}
  	return in;
  }
  
  /* returns 0 for ok, -1 for bad syntax, -2 for unknown critical extension */
  static int
  parseextensions(char *extensions, struct ldapdb_data *data)
  {
  	char *s, *next, *name, *value;
  	int critical;
  
  	while (extensions != NULL) {
  		s = strchr(extensions, ',');
  		if (s != NULL) {
  			*s++ = '\0';
  			next = s;
  		} else {
  			next = NULL;
  		}
  
  		if (*extensions != '\0') {
  			s = strchr(extensions, '=');
  			if (s != NULL) {
  				*s++ = '\0';
  				value = *s != '\0' ? s : NULL;
  			} else {
  				value = NULL;
  			}
  			name = extensions;
  
  			critical = *name == '!';
  			if (critical) {
  				name++;
  			}
  			if (*name == '\0') {
  				return -1;
  			}
  			
  			if (!strcasecmp(name, "bindname")) {
  				data->bindname = value;
  			} else if (!strcasecmp(name, "x-bindpw")) {
  				data->bindpw = value;
  #ifdef LDAPDB_TLS
  			} else if (!strcasecmp(name, "x-tls")) {
  				data->tls = value == NULL || !strcasecmp(value, "true");
  #endif
  			} else if (critical) {
  				return -2;
  			}
  		}
  		extensions = next;
  	}
  	return 0;
  }
  
  static void
  free_data(struct ldapdb_data *data)
  {
  	if (data->hostport != NULL)
  		isc_mem_free(ns_g_mctx, data->hostport);
  	if (data->hostname != NULL)
  		isc_mem_free(ns_g_mctx, data->hostname);
  	if (data->filterall != NULL)
  		isc_mem_put(ns_g_mctx, data->filterall, data->filteralllen);
  	if (data->filterone != NULL)
  		isc_mem_put(ns_g_mctx, data->filterone, data->filteronelen);
          isc_mem_put(ns_g_mctx, data, sizeof(struct ldapdb_data));
  }
  
  
  static isc_result_t
  ldapdb_create(const char *zone, int argc, char **argv,
  	      void *driverdata, void **dbdata)
  {
  	struct ldapdb_data *data;
  	char *s, *filter = NULL, *extensions = NULL;
  	int defaultttl;
  
  	UNUSED(driverdata);
  
  	/* we assume that only one thread will call create at a time */
  	/* want to do this only once for all instances */
  
  	if ((argc < 2)
  	    || (argv[0] != strstr( argv[0], "ldap://"))
  	    || ((defaultttl = atoi(argv[1])) < 1))
                  return (ISC_R_FAILURE);
          data = isc_mem_get(ns_g_mctx, sizeof(struct ldapdb_data));
          if (data == NULL)
                  return (ISC_R_NOMEMORY);
  
  	memset(data, 0, sizeof(struct ldapdb_data));
  	data->hostport = isc_mem_strdup(ns_g_mctx, argv[0] + strlen("ldap://"));
  	if (data->hostport == NULL) {
  		free_data(data);
  		return (ISC_R_NOMEMORY);
  	}
  
  	data->defaultttl = defaultttl;
  
  	s = strchr(data->hostport, '/');
  	if (s != NULL) {
  		*s++ = '\0';
  		data->base = s;
  		/* attrs, scope, filter etc? */
  		s = strchr(s, '?');
  		if (s != NULL) {
  			*s++ = '\0';
  			/* ignore attributes */
  			s = strchr(s, '?');
  			if (s != NULL) {
  				*s++ = '\0';
  				/* ignore scope */
  				s = strchr(s, '?');
  				if (s != NULL) {
  					*s++ = '\0';
  					/* filter */
  					filter = s;
  					s = strchr(s, '?');
  					if (s != NULL) {
  						*s++ = '\0';
  						/* extensions */
  						extensions = s;
  						s = strchr(s, '?');
  						if (s != NULL) {
  							*s++ = '\0';
  						}
  						if (*extensions == '\0') {
  							extensions = NULL;
  						}
  					}
  					if (*filter == '\0') {
  						filter = NULL;
  					}
  				}
  			}
  		}
  		if (*data->base == '\0') {
  			data->base = NULL;
  		}
  	}
  
  	/* parse extensions */
  	if (extensions != NULL) {
  		int err;
  
  		err = parseextensions(extensions, data);
  		if (err < 0) {
  			/* err should be -1 or -2 */
  			free_data(data);
  			if (err == -1) {
  				isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
  					      "LDAP sdb zone '%s': URL: extension syntax error", zone);
  			} else if (err == -2) {
  				isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
  					      "LDAP sdb zone '%s': URL: unknown critical extension", zone);
  			}
  			return (ISC_R_FAILURE);
  		}
  	}
  
  	if ((data->base != NULL && unhex(data->base) == NULL) ||
  	    (filter != NULL && unhex(filter) == NULL) ||
  	    (data->bindname != NULL && unhex(data->bindname) == NULL) ||
  	    (data->bindpw != NULL && unhex(data->bindpw) == NULL)) {
  		free_data(data);
  		isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR,	
  			      "LDAP sdb zone '%s': URL: bad hex values", zone);
  		return (ISC_R_FAILURE);
  	}
  
  	/* compute filterall and filterone once and for all */
  	if (filter == NULL) {
  		data->filteralllen = strlen(zone) + strlen("(zoneName=)") + 1;
  		data->filteronelen = strlen(zone) + strlen("(&(zoneName=)(relativeDomainName=))") + MAXNAMELEN + 1;
  	} else {
  		data->filteralllen = strlen(filter) + strlen(zone) + strlen("(&(zoneName=))") + 1;
  		data->filteronelen = strlen(filter) + strlen(zone) + strlen("(&(zoneName=)(relativeDomainName=))") + MAXNAMELEN + 1;
  	}
  
  	data->filterall = isc_mem_get(ns_g_mctx, data->filteralllen);
  	if (data->filterall == NULL) {
  		free_data(data);
  		return (ISC_R_NOMEMORY);
  	}
  	data->filterone = isc_mem_get(ns_g_mctx, data->filteronelen);
  	if (data->filterone == NULL) {
  		free_data(data);
  		return (ISC_R_NOMEMORY);
  	}
  
  	if (filter == NULL) {
  		sprintf(data->filterall, "(zoneName=%s)", zone);
  		sprintf(data->filterone, "(&(zoneName=%s)(relativeDomainName=", zone); 
  	} else {
  		sprintf(data->filterall, "(&%s(zoneName=%s))", filter, zone);
  		sprintf(data->filterone, "(&%s(zoneName=%s)(relativeDomainName=", filter, zone);
  	}
  	data->filtername = data->filterone + strlen(data->filterone);
  
  	/* support URLs with literal IPv6 addresses */
  	data->hostname = isc_mem_strdup(ns_g_mctx, data->hostport + (*data->hostport == '[' ? 1 : 0));
  	if (data->hostname == NULL) {
  		free_data(data);
  		return (ISC_R_NOMEMORY);
  	}
  
  	if (*data->hostport == '[' &&
  	    (s = strchr(data->hostname, ']')) != NULL )
  		*s++ = '\0';
  	else
  		s = data->hostname;
  	s = strchr(s, ':');
  	if (s != NULL) {
  		*s++ = '\0';
  		data->portno = atoi(s);
  	} else
  		data->portno = LDAP_PORT;
  
  	*dbdata = data;
  	return (ISC_R_SUCCESS);
  }
  
  static void
  ldapdb_destroy(const char *zone, void *driverdata, void **dbdata, void *zone_data) {
  	struct ldapdb_data *data = *dbdata;
  	
          UNUSED(zone);
          UNUSED(driverdata);
  	UNUSED(zone_data);
  	
  	free_data(data);
  }
  
  static dns_sdbmethods_t ldapdb_methods = {
  	ldapdb_lookup,
  	NULL, /* authority */
  	ldapdb_allnodes,
  	ldapdb_create,
  	ldapdb_destroy,
  	NULL,	/* lookup2 */
  	NULL	/*zonedata for Intelligent DNS*/
  };
  
  /* Wrapper around dns_sdb_register() */
  isc_result_t
  ldapdb_init(void) {
  	unsigned int flags =
  		DNS_SDBFLAG_RELATIVEOWNER |
  		DNS_SDBFLAG_RELATIVERDATA |
  		DNS_SDBFLAG_THREADSAFE;
  
  	ldapdb_lock(0);
  	return (dns_sdb_register("ldap", &ldapdb_methods, NULL, flags,
  				 ns_g_mctx, &ldapdb));
  }
  
  /* Wrapper around dns_sdb_unregister() */
  void
  ldapdb_clear(void) {
  	if (ldapdb != NULL) {
  		/* clean up thread data */
  		ldapdb_getconn(NULL);
  		dns_sdb_unregister(&ldapdb);
  	}
  }
  