minix/external/bsd/bind/dist/contrib/sdb/ldap/ldapdb.c
David van Moolenbroek 00b67f09dd Import NetBSD named(8)
Also known as ISC bind.  This import adds utilities such as host(1),
dig(1), and nslookup(1), as well as many other tools and libraries.

Change-Id: I035ca46e64f1965d57019e773f4ff0ef035e4aa3
2017-03-21 22:00:06 +00:00

693 lines
17 KiB
C

/* $NetBSD: ldapdb.c,v 1.5 2015/07/08 17:28:56 christos Exp $ */
/*
* 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;
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)
{
UNUSED(methods);
UNUSED(clientinfo);
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)
{
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) {
struct ldapdb_data *data = *dbdata;
UNUSED(zone);
UNUSED(driverdata);
free_data(data);
}
static dns_sdbmethods_t ldapdb_methods = {
ldapdb_lookup,
NULL, /* authority */
ldapdb_allnodes,
ldapdb_create,
ldapdb_destroy,
NULL /* lookup2 */
};
/* 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);
}
}