From 72c86c105a7cf315036d7131a4ef408bc6227639 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Mon, 11 Mar 2013 16:49:52 -0700 Subject: [PATCH] [master] DLZ modules: filesystem, ldap, wildcard 3523. [contrib] Ported filesystem and ldap DLZ drivers to dynamically-loadable modules, and added the "wildcard" module based on a contribution from Vadim Goncharov . [RT #23569] --- CHANGES | 5 + bin/named/unix/dlz_dlopen_driver.c | 1 + bin/named/win32/dlz_dlopen_driver.c | 1 + contrib/dlz/modules/bdbhpt/Makefile | 4 +- .../dlz/modules/bdbhpt/dlz_bdbhpt_dynamic.c | 1289 ++++++++--------- contrib/dlz/modules/common/dlz_dbi.c | 461 ++++++ contrib/dlz/modules/filesystem/Makefile | 20 + contrib/dlz/modules/filesystem/dir.c | 116 ++ contrib/dlz/modules/filesystem/dir.h | 47 + .../filesystem/dlz_filesystem_dynamic.c | 979 +++++++++++++ contrib/dlz/modules/include/dlz_dbi.h | 118 ++ contrib/dlz/modules/include/dlz_list.h | 49 + .../dlz/modules/{ => include}/dlz_minimal.h | 20 +- contrib/dlz/modules/include/dlz_pthread.h | 38 + contrib/dlz/modules/ldap/Makefile | 21 + contrib/dlz/modules/ldap/dlz_ldap_dynamic.c | 1205 +++++++++++++++ contrib/dlz/modules/ldap/testing/README | 10 + contrib/dlz/modules/ldap/testing/dlz.schema | 187 +++ contrib/dlz/modules/ldap/testing/example.ldif | 168 +++ contrib/dlz/modules/ldap/testing/named.conf | 46 + contrib/dlz/modules/ldap/testing/slapd.conf | 44 + contrib/dlz/modules/wildcard/Makefile | 20 + contrib/dlz/modules/wildcard/README | 31 + .../modules/wildcard/dlz_wildcard_dynamic.c | 738 ++++++++++ .../dlz/modules/wildcard/testing/named.conf | 58 + lib/dns/sdlz.c | 3 +- 26 files changed, 5026 insertions(+), 653 deletions(-) create mode 100644 contrib/dlz/modules/common/dlz_dbi.c create mode 100644 contrib/dlz/modules/filesystem/Makefile create mode 100644 contrib/dlz/modules/filesystem/dir.c create mode 100644 contrib/dlz/modules/filesystem/dir.h create mode 100644 contrib/dlz/modules/filesystem/dlz_filesystem_dynamic.c create mode 100644 contrib/dlz/modules/include/dlz_dbi.h create mode 100644 contrib/dlz/modules/include/dlz_list.h rename contrib/dlz/modules/{ => include}/dlz_minimal.h (93%) create mode 100644 contrib/dlz/modules/include/dlz_pthread.h create mode 100644 contrib/dlz/modules/ldap/Makefile create mode 100644 contrib/dlz/modules/ldap/dlz_ldap_dynamic.c create mode 100644 contrib/dlz/modules/ldap/testing/README create mode 100644 contrib/dlz/modules/ldap/testing/dlz.schema create mode 100644 contrib/dlz/modules/ldap/testing/example.ldif create mode 100644 contrib/dlz/modules/ldap/testing/named.conf create mode 100644 contrib/dlz/modules/ldap/testing/slapd.conf create mode 100644 contrib/dlz/modules/wildcard/Makefile create mode 100644 contrib/dlz/modules/wildcard/README create mode 100644 contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c create mode 100644 contrib/dlz/modules/wildcard/testing/named.conf diff --git a/CHANGES b/CHANGES index d66584e5f5..5e0d74c41b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +3523. [contrib] Ported filesystem and ldap DLZ drivers to + dynamically-loadable modules, and added the + "wildcard" module based on a contribution from + Vadim Goncharov . [RT #23569] + 3522. [bug] DLZ lookups could fail to return SERVFAIL when they ought to. [RT #32685] diff --git a/bin/named/unix/dlz_dlopen_driver.c b/bin/named/unix/dlz_dlopen_driver.c index a294b18554..77c10239f8 100644 --- a/bin/named/unix/dlz_dlopen_driver.c +++ b/bin/named/unix/dlz_dlopen_driver.c @@ -602,6 +602,7 @@ dlz_dlopen_init(isc_mem_t *mctx) { result = dns_sdlzregister("dlopen", &dlz_dlopen_methods, NULL, DNS_SDLZFLAG_RELATIVEOWNER | + DNS_SDLZFLAG_RELATIVERDATA | DNS_SDLZFLAG_THREADSAFE, mctx, &dlz_dlopen); diff --git a/bin/named/win32/dlz_dlopen_driver.c b/bin/named/win32/dlz_dlopen_driver.c index f4982f354d..20448bda45 100644 --- a/bin/named/win32/dlz_dlopen_driver.c +++ b/bin/named/win32/dlz_dlopen_driver.c @@ -580,6 +580,7 @@ dlz_dlopen_init(isc_mem_t *mctx) { result = dns_sdlzregister("dlopen", &dlz_dlopen_methods, NULL, DNS_SDLZFLAG_RELATIVEOWNER | + DNS_SDLZFLAG_RELATIVERDATA | DNS_SDLZFLAG_THREADSAFE, mctx, &dlz_dlopen); diff --git a/contrib/dlz/modules/bdbhpt/Makefile b/contrib/dlz/modules/bdbhpt/Makefile index 76f590a8be..795a2e8b92 100644 --- a/contrib/dlz/modules/bdbhpt/Makefile +++ b/contrib/dlz/modules/bdbhpt/Makefile @@ -1,12 +1,12 @@ prefix = /usr libdir = $(prefix)/lib/bind9 -CFLAGS=-fPIC -g +CFLAGS=-fPIC -g -I../include BDB_LIBS=-ldb all: dlz_bdbhpt_dynamic.so -dlz_bdbhpt_dynamic.so: +dlz_bdbhpt_dynamic.so: dlz_bdbhpt_dynamic.c $(CC) $(CFLAGS) -shared -o dlz_bdbhpt_dynamic.so \ dlz_bdbhpt_dynamic.c $(BDB_LIBS) diff --git a/contrib/dlz/modules/bdbhpt/dlz_bdbhpt_dynamic.c b/contrib/dlz/modules/bdbhpt/dlz_bdbhpt_dynamic.c index ecfa1018b2..9de2e451c5 100644 --- a/contrib/dlz/modules/bdbhpt/dlz_bdbhpt_dynamic.c +++ b/contrib/dlz/modules/bdbhpt/dlz_bdbhpt_dynamic.c @@ -68,15 +68,7 @@ #include -#include "../dlz_minimal.h" - -#ifdef WIN32 -#define STRTOK_R(a, b, c) strtok_s(a, b, c) -#elif defined(_REENTRANT) -#define STRTOK_R(a, b, c) strtok_r(a, b, c) -#else -#define STRTOK_R(a, b, c) strtok(a, b) -#endif +#include "dlz_minimal.h" /* should the bdb driver use threads. */ #ifdef ISC_PLATFORM_USETHREADS @@ -99,389 +91,377 @@ * */ typedef struct bdbhpt_instance { - DB_ENV *dbenv; /* bdbhpt environment */ - DB *data; /* dns_data database handle */ - DB *zone; /* zone database handle */ - DB *xfr; /* zone xfr database handle */ - DB *client; /* client database handle */ + DB_ENV *dbenv; /* bdbhpt environment */ + DB *data; /* dns_data database handle */ + DB *zone; /* zone database handle */ + DB *xfr; /* zone xfr database handle */ + DB *client; /* client database handle */ - /* Helper functions from the dlz_dlopen driver */ - log_t *log; - dns_sdlz_putrr_t *putrr; - dns_sdlz_putnamedrr_t *putnamedrr; - dns_dlz_writeablezone_t *writeable_zone; + /* Helper functions from the dlz_dlopen driver */ + log_t *log; + dns_sdlz_putrr_t *putrr; + dns_sdlz_putnamedrr_t *putnamedrr; + dns_dlz_writeablezone_t *writeable_zone; } bdbhpt_instance_t; - typedef struct bdbhpt_parsed_data { - char *host; - char *type; - int ttl; - char *data; + char *host; + char *type; + int ttl; + char *data; } bdbhpt_parsed_data_t; -/* +static void +b9_add_helper(struct bdbhpt_instance *db, const char *helper_name, void *ptr); + +/*% * Reverses a string in place. */ static char *bdbhpt_strrev(char *str) { - char *p1, *p2; - - if (! str || ! *str) - return str; - for (p1 = str, p2 = str + strlen(str) - 1; p2 > p1; ++p1, --p2) { - *p1 ^= *p2; - *p2 ^= *p1; - *p1 ^= *p2; - } - return str; + char *p1, *p2; + + if (! str || ! *str) + return str; + for (p1 = str, p2 = str + strlen(str) - 1; p2 > p1; ++p1, --p2) { + *p1 ^= *p2; + *p2 ^= *p1; + *p1 ^= *p2; + } + return str; } -/* +/*% * Parses the DBT from the Berkeley DB into a parsed_data record * The parsed_data record should be allocated before and passed into the * bdbhpt_parse_data function. The char (type & data) fields should not * be "free"d as that memory is part of the DBT data field. It will be * "free"d when the DBT is freed. */ + static isc_result_t bdbhpt_parse_data(log_t *log, char *in, bdbhpt_parsed_data_t *pd) { - char *endp, *ttlStr; - char *tmp = in; - char *lastchar = (char *) &tmp[strlen(tmp)]; + + char *endp, *ttlStr; + char *tmp = in; + char *lastchar = (char *) &tmp[strlen(tmp)]; - /* - * String should be formatted as: - * replication_id - * (a space) - * host_name - * (a space) - * ttl - * (a space) - * type - * (a space) - * remaining data - * - * examples: - * - * 9191 host 10 A 127.0.0.1 - * server1_212 host 10 A 127.0.0.2 - * {xxxx-xxxx-xxxx-xxxx-xxxx} host 10 MX 20 mail.example.com - */ + /*% + * String should be formatted as: + * replication_id + * (a space) + * host_name + * (a space) + * ttl + * (a space) + * type + * (a space) + * remaining data + * + * examples: + * + * 9191 host 10 A 127.0.0.1 + * server1_212 host 10 A 127.0.0.2 + * {xxxx-xxxx-xxxx-xxxx-xxxx} host 10 MX 20 mail.example.com + */ - /* - * we don't need the replication id, so don't - * bother saving a pointer to it. - */ + /* + * we don't need the replication id, so don't + * bother saving a pointer to it. + */ - /* find space after replication id */ - tmp = strchr(tmp, ' '); - /* verify we found a space */ - if (tmp == NULL) - return ISC_R_FAILURE; - /* make sure it is safe to increment pointer */ - if (++tmp > lastchar) - return ISC_R_FAILURE; + /* find space after replication id */ + tmp = strchr(tmp, ' '); + /* verify we found a space */ + if (tmp == NULL) + return ISC_R_FAILURE; + /* make sure it is safe to increment pointer */ + if (++tmp > lastchar) + return ISC_R_FAILURE; + + /* save pointer to host */ + pd->host = tmp; + + /* find space after host and change it to a '\0' */ + tmp = strchr(tmp, ' '); + /* verify we found a space */ + if (tmp == NULL) + return ISC_R_FAILURE; + /* change the space to a null (string terminator) */ + tmp[0] = '\0'; + /* make sure it is safe to increment pointer */ + if (++tmp > lastchar) + return ISC_R_FAILURE; - /* save pointer to host */ - pd->host = tmp; + /* save pointer to ttl string */ + ttlStr = tmp; - /* find space after host and change it to a '\0' */ - tmp = strchr(tmp, ' '); - /* verify we found a space */ - if (tmp == NULL) - return ISC_R_FAILURE; - /* change the space to a null (string terminator) */ - tmp[0] = '\0'; - /* make sure it is safe to increment pointer */ - if (++tmp > lastchar) - return ISC_R_FAILURE; - - /* save pointer to ttl string */ - ttlStr = tmp; - - /* find space after ttl and change it to a '\0' */ - tmp = strchr(tmp, ' '); - /* verify we found a space */ - if (tmp == NULL) - return ISC_R_FAILURE; - /* change the space to a null (string terminator) */ - tmp[0] = '\0'; - /* make sure it is safe to increment pointer */ - if (++tmp > lastchar) - return ISC_R_FAILURE; - - /* save pointer to dns type */ - pd->type = tmp; - - /* find space after type and change it to a '\0' */ - tmp = strchr(tmp, ' '); - /* verify we found a space */ - if (tmp == NULL) - return ISC_R_FAILURE; - /* change the space to a null (string terminator) */ - tmp[0] = '\0'; - /* make sure it is safe to increment pointer */ - if (++tmp > lastchar) - return ISC_R_FAILURE; - - /* save pointer to remainder of DNS data */ - pd->data = tmp; - - /* convert ttl string to integer */ - pd->ttl = strtol(ttlStr, &endp, 10); - if (*endp != '\0' || pd->ttl < 0) { - log(ISC_LOG_ERROR, - "bdbhpt_dynamic: ttl must be a positive number"); - return ISC_R_FAILURE; - } - - /* if we get this far everything should have worked. */ - return ISC_R_SUCCESS; + /* find space after ttl and change it to a '\0' */ + tmp = strchr(tmp, ' '); + /* verify we found a space */ + if (tmp == NULL) + return ISC_R_FAILURE; + /* change the space to a null (string terminator) */ + tmp[0] = '\0'; + /* make sure it is safe to increment pointer */ + if (++tmp > lastchar) + return ISC_R_FAILURE; + + /* save pointer to dns type */ + pd->type = tmp; + + /* find space after type and change it to a '\0' */ + tmp = strchr(tmp, ' '); + /* verify we found a space */ + if (tmp == NULL) + return ISC_R_FAILURE; + /* change the space to a null (string terminator) */ + tmp[0] = '\0'; + /* make sure it is safe to increment pointer */ + if (++tmp > lastchar) + return ISC_R_FAILURE; + + /* save pointer to remainder of DNS data */ + pd->data = tmp; + + /* convert ttl string to integer */ + pd->ttl = strtol(ttlStr, &endp, 10); + if (*endp != '\0' || pd->ttl < 0) { + log(ISC_LOG_ERROR, + "bdbhpt_dynamic: " + "ttl must be a positive number"); + return ISC_R_FAILURE; + } + + /* if we get this far everything should have worked. */ + return ISC_R_SUCCESS; } /* - * Return the version of the API + * See if a zone transfer is allowed */ -int -dlz_version(unsigned int *flags) { - UNUSED(flags); - return (DLZ_DLOPEN_VERSION); +isc_result_t +dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { + isc_result_t result; + bdbhpt_instance_t *db = (bdbhpt_instance_t *) dbdata; + DBT key, data; + + /* check to see if we are authoritative for the zone first. */ +#if DLZ_DLOPEN_VERSION >= 3 + result = dlz_findzonedb(dbdata, name, NULL, NULL); +#else + result = dlz_findzonedb(dbdata, name); +#endif + if (result != ISC_R_SUCCESS) + return (ISC_R_NOTFOUND); + + memset(&key, 0, sizeof(DBT)); + key.flags = DB_DBT_MALLOC; + key.data = strdup(name); + if (key.data == NULL) { + result = ISC_R_NOMEMORY; + goto xfr_cleanup; + } + key.size = strlen(key.data); + + memset(&data, 0, sizeof(DBT)); + data.flags = DB_DBT_MALLOC; + data.data = strdup(client); + if (data.data == NULL) { + result = ISC_R_NOMEMORY; + goto xfr_cleanup; + } + data.size = strlen(data.data); + + switch(db->client->get(db->client, NULL, &key, &data, DB_GET_BOTH)) { + case DB_NOTFOUND: + result = ISC_R_NOTFOUND; + break; + case 0: + result = ISC_R_SUCCESS; + break; + default: + result = ISC_R_FAILURE; + } + + xfr_cleanup: + /* free any memory duplicate string in the key field */ + if (key.data != NULL) + free(key.data); + + /* free any memory allocated to the data field. */ + if (data.data != NULL) + free(data.data); + + return result; } -/* - * Remember a helper function from the bind9 dlz_dlopen driver +/*% + * Perform a zone transfer + * + * BDB does not allow a secondary index on a database that allows + * duplicates. We have a few options: + * + * 1) kill speed by having lookup method use a secondary db which + * is associated to the primary DB with the DNS data. Then have + * another secondary db for zone transfer which also points to + * the dns_data primary. NO - The point of this driver is + * lookup performance. + * + * 2) Blow up database size by storing DNS data twice. Once for + * the lookup (dns_data) database, and a second time for the zone + * transfer (dns_xfr) database. NO - That would probably require + * a larger cache to provide good performance. Also, that would + * make the DB larger on disk potentially slowing it as well. + * + * 3) Loop through the dns_xfr database with a cursor to get + * all the different hosts in a zone. Then use the zone & host + * together to lookup the data in the dns_data database. YES - + * This may slow down zone xfr's a little, but that's ok they + * don't happen as often and don't need to be as fast. We can + * also use this table when deleting a zone (The BDB driver + * is read only - the delete would be used during replication + * updates by a separate process). */ -static void -b9_add_helper(struct bdbhpt_instance *db, - const char *helper_name, void *ptr) { - if (strcmp(helper_name, "log") == 0) - db->log = (log_t *)ptr; - if (strcmp(helper_name, "putrr") == 0) - db->putrr = (dns_sdlz_putrr_t *)ptr; - if (strcmp(helper_name, "putnamedrr") == 0) - db->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr; - if (strcmp(helper_name, "writeable_zone") == 0) - db->writeable_zone = (dns_dlz_writeablezone_t *)ptr; +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) { + isc_result_t result = ISC_R_NOTFOUND; + bdbhpt_instance_t *db = (bdbhpt_instance_t *) dbdata; + DBC *xfr_cursor = NULL; + DBC *dns_cursor = NULL; + DBT xfr_key, xfr_data, dns_key, dns_data; + int xfr_flags; + int dns_flags; + int bdbhptres; + bdbhpt_parsed_data_t pd; + char *tmp = NULL, *tmp_zone, *tmp_zone_host = NULL; + + memset(&xfr_key, 0, sizeof(DBT)); + memset(&xfr_data, 0, sizeof(DBT)); + memset(&dns_key, 0, sizeof(DBT)); + memset(&dns_data, 0, sizeof(DBT)); + + xfr_key.data = tmp_zone = strdup(zone); + if (xfr_key.data == NULL) + return (ISC_R_NOMEMORY); + + xfr_key.size = strlen(xfr_key.data); + + /* get a cursor to loop through dns_xfr table */ + if (db->xfr->cursor(db->xfr, NULL, &xfr_cursor, 0) != 0) { + result = ISC_R_FAILURE; + goto allnodes_cleanup; + } + + /* get a cursor to loop through dns_data table */ + if (db->data->cursor(db->data, NULL, &dns_cursor, 0) != 0) { + result = ISC_R_FAILURE; + goto allnodes_cleanup; + } + + xfr_flags = DB_SET; + + /* loop through xfr table for specified zone. */ + while ((bdbhptres = xfr_cursor->c_get(xfr_cursor, &xfr_key, + &xfr_data, xfr_flags)) == 0) + { + xfr_flags = DB_NEXT_DUP; + + /* +1 to allow for space between zone and host names */ + dns_key.size = xfr_data.size + xfr_key.size + 1; + + /* +1 to allow for null term at end of string. */ + dns_key.data = tmp_zone_host = malloc(dns_key.size + 1); + if (dns_key.data == NULL) + goto allnodes_cleanup; + + /* + * construct search key for dns_data. + * zone_name(a space)host_name + */ + strcpy(dns_key.data, zone); + strcat(dns_key.data, " "); + strncat(dns_key.data, xfr_data.data, xfr_data.size); + + dns_flags = DB_SET; + + while ((bdbhptres = dns_cursor->c_get(dns_cursor, + &dns_key, + &dns_data, + dns_flags)) == 0) + { + dns_flags = DB_NEXT_DUP; + + /* +1 to allow for null term at end of string. */ + tmp = realloc(tmp, dns_data.size + 1); + if (tmp == NULL) + goto allnodes_cleanup; + + /* copy data to tmp string, and append null term. */ + strncpy(tmp, dns_data.data, dns_data.size); + tmp[dns_data.size] = '\0'; + + /* split string into dns data parts. */ + if (bdbhpt_parse_data(db->log, + tmp, &pd) != ISC_R_SUCCESS) + goto allnodes_cleanup; + result = db->putnamedrr(allnodes, pd.host, + pd.type, pd.ttl, pd.data); + if (result != ISC_R_SUCCESS) + goto allnodes_cleanup; + + } /* end inner while loop */ + + /* clean up memory */ + if (tmp_zone_host != NULL) { + free(tmp_zone_host); + tmp_zone_host = NULL; + } + } /* end outer while loop */ + + allnodes_cleanup: + /* free any memory */ + if (tmp != NULL) + free(tmp); + + if (tmp_zone_host != NULL) + free(tmp_zone_host); + + if (tmp_zone != NULL) + free(tmp_zone); + + /* get rid of cursors */ + if (xfr_cursor != NULL) + xfr_cursor->c_close(xfr_cursor); + + if (dns_cursor != NULL) + dns_cursor->c_close(dns_cursor); + + return result; } -/* +/*% * Performs bdbhpt cleanup. * Used by bdbhpt_create if there is an error starting up. * Used by bdbhpt_destroy when the driver is shutting down. */ static void bdbhpt_cleanup(bdbhpt_instance_t *db) { - /* close databases */ - if (db->data != NULL) - db->data->close(db->data, 0); - if (db->xfr != NULL) - db->xfr->close(db->xfr, 0); - if (db->zone != NULL) - db->zone->close(db->zone, 0); - if (db->client != NULL) - db->client->close(db->client, 0); - - /* close environment */ - if (db->dbenv != NULL) - db->dbenv->close(db->dbenv, 0); + /* close databases */ + if (db->data != NULL) + db->data->close(db->data, 0); + if (db->xfr != NULL) + db->xfr->close(db->xfr, 0); + if (db->zone != NULL) + db->zone->close(db->zone, 0); + if (db->client != NULL) + db->client->close(db->client, 0); + + /* close environment */ + if (db->dbenv != NULL) + db->dbenv->close(db->dbenv, 0); } -/* - * Initialises, sets flags and then opens Berkeley databases. - * - */ -static isc_result_t -bdbhpt_opendb(log_t *log, DB_ENV *db_env, DBTYPE db_type, DB **db, const char *db_name, - char *db_file, int flags) { - int result; - - /* Initialise the database. */ - if ((result = db_create(db, db_env, 0)) != 0) { - log(ISC_LOG_ERROR, - "bdbhpt_dynamic: could not initialize %s database. " - "BerkeleyDB error: %s", - db_name, db_strerror(result)); - return ISC_R_FAILURE; - } - - /* set database flags. */ - if ((result = (*db)->set_flags(*db, flags)) != 0) { - log(ISC_LOG_ERROR, - "bdbhpt_dynamic: could not set flags for %s database. " - "BerkeleyDB error: %s", - db_name, db_strerror(result)); - return ISC_R_FAILURE; - } - - /* open the database. */ - if ((result = (*db)->open(*db, NULL, db_file, db_name, db_type, - DB_RDONLY | bdbhpt_threads, 0)) != 0) { - log(ISC_LOG_ERROR, - "bdbhpt_dynamic: could not open %s database in %s. " - "BerkeleyDB error: %s", - db_name, db_file, db_strerror(result)); - return ISC_R_FAILURE; - } - - return ISC_R_SUCCESS; -} - - -/* - * Called to initialize the driver - */ -isc_result_t -dlz_create(const char *dlzname, unsigned int argc, char *argv[], - void **dbdata, ...) -{ - isc_result_t result; - int bdbhptres; - int bdbFlags = 0; - bdbhpt_instance_t *db = NULL; - - const char *helper_name; - va_list ap; - - UNUSED(dlzname); - - /* Allocate memory for our db structures and helper functions */ - db = calloc(1, sizeof(struct bdbhpt_instance)); - if (db == NULL) - return (ISC_R_NOMEMORY); - - /* Fill in the helper functions */ - va_start(ap, dbdata); - while ((helper_name = va_arg(ap, const char *)) != NULL) { - b9_add_helper(db, helper_name, va_arg(ap, void*)); - } - va_end(ap); - - /* verify we have 4 arg's passed to the driver */ - if (argc != 4) { - db->log(ISC_LOG_ERROR, - "bdbhpt_dynamic: please supply 3 command line args. " - "You supplied: %s", - argc); - return (ISC_R_FAILURE); - } - - switch((char) *argv[1]) { - /* - * Transactional mode. Highest safety - lowest speed. - */ - case 'T': - case 't': - bdbFlags = DB_INIT_MPOOL | DB_INIT_LOCK | - DB_INIT_LOG | DB_INIT_TXN; - db->log(ISC_LOG_INFO, - "bdbhpt_dynamic: using transactional mode."); - break; - - /* - * Concurrent mode. Lower safety (no rollback) - - * higher speed. - */ - case 'C': - case 'c': - bdbFlags = DB_INIT_CDB | DB_INIT_MPOOL; - db->log(ISC_LOG_INFO, - "bdbhpt_dynamic: using concurrent mode."); - break; - - /* - * Private mode. No inter-process communication & no locking. - * Lowest saftey - highest speed. - */ - case 'P': - case 'p': - bdbFlags = DB_PRIVATE | DB_INIT_MPOOL; - db->log(ISC_LOG_INFO, - "bdbhpt_dynamic: using private mode."); - break; - default: - db->log(ISC_LOG_ERROR, - "bdbhpt_dynamic: operating mode must be set to P or C or T. " - "You specified '%s'", - argv[1]); - return (ISC_R_FAILURE); - } - - /* - * create bdbhpt environment - * Basically bdbhpt allocates and assigns memory to db->dbenv - */ - bdbhptres = db_env_create(&db->dbenv, 0); - if (bdbhptres != 0) { - db->log(ISC_LOG_ERROR, - "bdbhpt_dynamic: db environment could not be created. " - "BerkeleyDB error: %s", - db_strerror(bdbhptres)); - result = ISC_R_FAILURE; - goto init_cleanup; - } - - /* open bdbhpt environment */ - bdbhptres = db->dbenv->open(db->dbenv, argv[2], - bdbFlags | bdbhpt_threads | DB_CREATE, 0); - if (bdbhptres != 0) { - db->log(ISC_LOG_ERROR, - "bdbhpt_dynamic: db environment at '%s' could not be opened. " - "BerkeleyDB error: %s", - argv[2], db_strerror(bdbhptres)); - result = ISC_R_FAILURE; - goto init_cleanup; - } - - /* open dlz_data database. */ - result = bdbhpt_opendb(db->log, db->dbenv, DB_UNKNOWN, &db->data, - dlz_data, argv[3], DB_DUP | DB_DUPSORT); - if (result != ISC_R_SUCCESS) - goto init_cleanup; - - /* open dlz_xfr database. */ - result = bdbhpt_opendb(db->log, db->dbenv, DB_UNKNOWN, &db->xfr, - dlz_xfr, argv[3], DB_DUP | DB_DUPSORT); - if (result != ISC_R_SUCCESS) - goto init_cleanup; - - /* open dlz_zone database. */ - result = bdbhpt_opendb(db->log, db->dbenv, DB_UNKNOWN, &db->zone, - dlz_zone, argv[3], 0); - if (result != ISC_R_SUCCESS) - goto init_cleanup; - - /* open dlz_client database. */ - result = bdbhpt_opendb(db->log, db->dbenv, DB_UNKNOWN, &db->client, - dlz_client, argv[3], DB_DUP | DB_DUPSORT); - if (result != ISC_R_SUCCESS) - goto init_cleanup; - - *dbdata = db; - - db->log(ISC_LOG_INFO, - "bdbhpt_dynamic: version %s, started", dlz_bdbhpt_dynamic_version); - return(ISC_R_SUCCESS); - - init_cleanup: - bdbhpt_cleanup(db); - return result; -} - -/* - * Shut down the backend - */ -void -dlz_destroy(void *dbdata) { - struct bdbhpt_instance *db = (struct bdbhpt_instance *)dbdata; - - db->log(ISC_LOG_INFO, - "dlz_bdbhpt_dynamic (%s): shutting down", dlz_bdbhpt_dynamic_version); - bdbhpt_cleanup((bdbhpt_instance_t *) dbdata); - free (db); -} - - /* * See if we handle a given zone */ @@ -491,56 +471,56 @@ dlz_findzonedb(void *dbdata, const char *name) #else isc_result_t dlz_findzonedb(void *dbdata, const char *name, - dns_clientinfomethods_t *methods, - dns_clientinfo_t *clientinfo) + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) #endif { - isc_result_t result; - bdbhpt_instance_t *db = (bdbhpt_instance_t *) dbdata; - DBT key, data; - - memset(&key, 0, sizeof(DBT)); - memset(&data, 0, sizeof(DBT)); - data.flags = DB_DBT_MALLOC; + isc_result_t result; + bdbhpt_instance_t *db = (bdbhpt_instance_t *) dbdata; + DBT key, data; + + memset(&key, 0, sizeof(DBT)); + memset(&data, 0, sizeof(DBT)); + data.flags = DB_DBT_MALLOC; #if DLZ_DLOPEN_VERSION >= 3 - UNUSED(methods); - UNUSED(clientinfo); + UNUSED(methods); + UNUSED(clientinfo); #endif - key.data = strdup(name); + key.data = strdup(name); - if (key.data == NULL) - return (ISC_R_NOMEMORY); + if (key.data == NULL) + return (ISC_R_NOMEMORY); - /* - * reverse string to take advantage of BDB locality of reference - * if we need futher lookups because the zone doesn't match the - * first time. - */ - key.data = bdbhpt_strrev(key.data); - key.size = strlen(key.data); + /* + * reverse string to take advantage of BDB locality of reference + * if we need futher lookups because the zone doesn't match the + * first time. + */ + key.data = bdbhpt_strrev(key.data); + key.size = strlen(key.data); - switch(db->zone->get(db->zone, NULL, &key, &data, 0)) { - case DB_NOTFOUND: - result = ISC_R_NOTFOUND; - break; - case 0: - result = ISC_R_SUCCESS; - break; - default: - result = ISC_R_FAILURE; - } - - /* free any memory duplicate string in the key field */ - if (key.data != NULL) - free(key.data); - - /* free any memory allocated to the data field. */ - if (data.data != NULL) - free(data.data); - - return result; + switch(db->zone->get(db->zone, NULL, &key, &data, 0)) { + case DB_NOTFOUND: + result = ISC_R_NOTFOUND; + break; + case 0: + result = ISC_R_SUCCESS; + break; + default: + result = ISC_R_FAILURE; + } + + /* free any memory duplicate string in the key field */ + if (key.data != NULL) + free(key.data); + + /* free any memory allocated to the data field. */ + if (data.data != NULL) + free(data.data); + + return result; } /* @@ -549,7 +529,7 @@ dlz_findzonedb(void *dbdata, const char *name, */ #if DLZ_DLOPEN_VERSION == 1 isc_result_t dlz_lookup(const char *zone, const char *name, - void *dbdata, dns_sdlzlookup_t *lookup) + void *dbdata, dns_sdlzlookup_t *lookup) #else isc_result_t dlz_lookup(const char *zone, const char *name, void *dbdata, dns_sdlzlookup_t *lookup, @@ -557,280 +537,293 @@ isc_result_t dlz_lookup(const char *zone, const char *name, void *dbdata, dns_clientinfo_t *clientinfo) #endif { + isc_result_t result = ISC_R_NOTFOUND; + bdbhpt_instance_t *db = (bdbhpt_instance_t *) dbdata; + DBC *data_cursor = NULL; + DBT key, data; + int bdbhptres; + int flags; - isc_result_t result = ISC_R_NOTFOUND; - bdbhpt_instance_t *db = (bdbhpt_instance_t *) dbdata; - DBC *data_cursor = NULL; - DBT key, data; - int bdbhptres; - int flags; - - bdbhpt_parsed_data_t pd; - char *tmp = NULL; - char *keyStr = NULL; - + bdbhpt_parsed_data_t pd; + char *tmp = NULL; + char *keyStr = NULL; + #if DLZ_DLOPEN_VERSION >= 2 - UNUSED(methods); - UNUSED(clientinfo); + UNUSED(methods); + UNUSED(clientinfo); #endif - memset(&key, 0, sizeof(DBT)); - memset(&data, 0, sizeof(DBT)); - - key.size = strlen(zone) + strlen(name) + 1; + memset(&key, 0, sizeof(DBT)); + memset(&data, 0, sizeof(DBT)); + + key.size = strlen(zone) + strlen(name) + 1; - /* allocate mem for key */ - key.data = keyStr = malloc((key.size + 1) * sizeof(char)); - - if (keyStr == NULL) - return ISC_R_NOMEMORY; - - strcpy(keyStr, zone); - strcat(keyStr, " "); - strcat(keyStr, name); - - /* get a cursor to loop through data */ - if (db->data->cursor(db->data, NULL, &data_cursor, 0) != 0) { - result = ISC_R_FAILURE; - goto lookup_cleanup; - } + /* allocate mem for key */ + key.data = keyStr = malloc((key.size + 1) * sizeof(char)); + + if (keyStr == NULL) + return ISC_R_NOMEMORY; + + strcpy(keyStr, zone); + strcat(keyStr, " "); + strcat(keyStr, name); + + /* get a cursor to loop through data */ + if (db->data->cursor(db->data, NULL, &data_cursor, 0) != 0) { + result = ISC_R_FAILURE; + goto lookup_cleanup; + } - result = ISC_R_NOTFOUND; + result = ISC_R_NOTFOUND; - flags = DB_SET; - while ((bdbhptres = data_cursor->c_get(data_cursor, &key, &data, - flags)) == 0) { - - flags = DB_NEXT_DUP; - tmp = realloc(tmp, data.size + 1); - if (tmp == NULL) - goto lookup_cleanup; - - strncpy(tmp, data.data, data.size); - tmp[data.size] = '\0'; - - if (bdbhpt_parse_data(db->log, tmp, &pd) != ISC_R_SUCCESS) - goto lookup_cleanup; - - result = db->putrr(lookup, pd.type, pd.ttl, pd.data); - - if (result != ISC_R_SUCCESS) - goto lookup_cleanup; - } /* end while loop */ - + flags = DB_SET; + while ((bdbhptres = data_cursor->c_get(data_cursor, &key, &data, + flags)) == 0) + { + flags = DB_NEXT_DUP; + tmp = realloc(tmp, data.size + 1); + if (tmp == NULL) + goto lookup_cleanup; + + strncpy(tmp, data.data, data.size); + tmp[data.size] = '\0'; + + if (bdbhpt_parse_data(db->log, tmp, &pd) != ISC_R_SUCCESS) + goto lookup_cleanup; + + result = db->putrr(lookup, pd.type, pd.ttl, pd.data); + if (result != ISC_R_SUCCESS) + goto lookup_cleanup; + } /* end while loop */ + lookup_cleanup: - /* get rid of cursor */ - if (data_cursor != NULL) - data_cursor->c_close(data_cursor); - - if (keyStr != NULL) - free(keyStr); - if (tmp != NULL) - free(tmp); - - return result; + /* get rid of cursor */ + if (data_cursor != NULL) + data_cursor->c_close(data_cursor); + + if (keyStr != NULL) + free(keyStr); + if (tmp != NULL) + free(tmp); + + return result; +} + +/*% + * Initialises, sets flags and then opens Berkeley databases. + */ +static isc_result_t +bdbhpt_opendb(log_t *log, DB_ENV *db_env, DBTYPE db_type, DB **db, + const char *db_name, char *db_file, int flags) +{ + int result; + + /* Initialise the database. */ + if ((result = db_create(db, db_env, 0)) != 0) { + log(ISC_LOG_ERROR, + "bdbhpt_dynamic: could not initialize %s database. " + "BerkeleyDB error: %s", + db_name, db_strerror(result)); + return ISC_R_FAILURE; + } + + /* set database flags. */ + if ((result = (*db)->set_flags(*db, flags)) != 0) { + log(ISC_LOG_ERROR, + "bdbhpt_dynamic: could not set flags for %s database. " + "BerkeleyDB error: %s", + db_name, db_strerror(result)); + return ISC_R_FAILURE; + } + + /* open the database. */ + if ((result = (*db)->open(*db, NULL, db_file, db_name, db_type, + DB_RDONLY | bdbhpt_threads, 0)) != 0) { + log(ISC_LOG_ERROR, + "bdbhpt_dynamic: could not open %s database in %s. " + "BerkeleyDB error: %s", + db_name, db_file, db_strerror(result)); + return ISC_R_FAILURE; + } + + return ISC_R_SUCCESS; } /* - * See if a zone transfer is allowed + * Called to initialize the driver */ isc_result_t -dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { - isc_result_t result; - bdbhpt_instance_t *db = (bdbhpt_instance_t *) dbdata; - DBT key, data; - - /* check to see if we are authoritative for the zone first. */ -#if DLZ_DLOPEN_VERSION >= 3 - result = dlz_findzonedb(dbdata, name, NULL, NULL); -#else - result = dlz_findzonedb(dbdata, name); -#endif - if (result != ISC_R_SUCCESS) - return (ISC_R_NOTFOUND); - - memset(&key, 0, sizeof(DBT)); - key.flags = DB_DBT_MALLOC; - key.data = strdup(name); - if (key.data == NULL) { - result = ISC_R_NOMEMORY; - goto xfr_cleanup; - } - key.size = strlen(key.data); - - memset(&data, 0, sizeof(DBT)); - data.flags = DB_DBT_MALLOC; - data.data = strdup(client); - if (data.data == NULL) { - result = ISC_R_NOMEMORY; - goto xfr_cleanup; - } - data.size = strlen(data.data); - - switch(db->client->get(db->client, NULL, &key, &data, DB_GET_BOTH)) { - case DB_NOTFOUND: - result = ISC_R_NOTFOUND; - break; - case 0: - result = ISC_R_SUCCESS; - break; - default: - result = ISC_R_FAILURE; - } +dlz_create(const char *dlzname, unsigned int argc, char *argv[], + void **dbdata, ...) +{ + isc_result_t result; + int bdbhptres; + int bdbFlags = 0; + bdbhpt_instance_t *db = NULL; + + const char *helper_name; + va_list ap; - xfr_cleanup: + UNUSED(dlzname); - /* free any memory duplicate string in the key field */ - if (key.data != NULL) - free(key.data); - - /* free any memory allocated to the data field. */ - if (data.data != NULL) - free(data.data); - - return result; + /* Allocate memory for our db structures and helper functions */ + db = calloc(1, sizeof(struct bdbhpt_instance)); + if (db == NULL) + return (ISC_R_NOMEMORY); + + /* Fill in the helper functions */ + va_start(ap, dbdata); + while ((helper_name = va_arg(ap, const char *)) != NULL) + b9_add_helper(db, helper_name, va_arg(ap, void*)); + va_end(ap); + + /* verify we have 4 arg's passed to the driver */ + if (argc != 4) { + db->log(ISC_LOG_ERROR, + "bdbhpt_dynamic: please supply 3 command line args. " + "You supplied: %s", argc); + return (ISC_R_FAILURE); + } + + switch((char) *argv[1]) { + /* + * Transactional mode. Highest safety - lowest speed. + */ + case 'T': + case 't': + bdbFlags = DB_INIT_MPOOL | DB_INIT_LOCK | + DB_INIT_LOG | DB_INIT_TXN; + db->log(ISC_LOG_INFO, + "bdbhpt_dynamic: using transactional mode."); + break; + + /* + * Concurrent mode. Lower safety (no rollback) - + * higher speed. + */ + case 'C': + case 'c': + bdbFlags = DB_INIT_CDB | DB_INIT_MPOOL; + db->log(ISC_LOG_INFO, + "bdbhpt_dynamic: using concurrent mode."); + break; + + /* + * Private mode. No inter-process communication & no locking. + * Lowest saftey - highest speed. + */ + case 'P': + case 'p': + bdbFlags = DB_PRIVATE | DB_INIT_MPOOL; + db->log(ISC_LOG_INFO, + "bdbhpt_dynamic: using private mode."); + break; + default: + db->log(ISC_LOG_ERROR, + "bdbhpt_dynamic: " + "operating mode must be set to P or C or T. " + "You specified '%s'", argv[1]); + return (ISC_R_FAILURE); + } + + /* + * create bdbhpt environment + * Basically bdbhpt allocates and assigns memory to db->dbenv + */ + bdbhptres = db_env_create(&db->dbenv, 0); + if (bdbhptres != 0) { + db->log(ISC_LOG_ERROR, + "bdbhpt_dynamic: db environment could not be created. " + "BerkeleyDB error: %s",jdb_strerror(bdbhptres)); + result = ISC_R_FAILURE; + goto init_cleanup; + } + + /* open bdbhpt environment */ + bdbhptres = db->dbenv->open(db->dbenv, argv[2], + bdbFlags | bdbhpt_threads | DB_CREATE, 0); + if (bdbhptres != 0) { + db->log(ISC_LOG_ERROR, + "bdbhpt_dynamic: " + "db environment at '%s' could not be opened. " + "BerkeleyDB error: %s", + argv[2], db_strerror(bdbhptres)); + result = ISC_R_FAILURE; + goto init_cleanup; + } + + /* open dlz_data database. */ + result = bdbhpt_opendb(db->log, db->dbenv, DB_UNKNOWN, &db->data, + dlz_data, argv[3], DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) + goto init_cleanup; + + /* open dlz_xfr database. */ + result = bdbhpt_opendb(db->log, db->dbenv, DB_UNKNOWN, &db->xfr, + dlz_xfr, argv[3], DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) + goto init_cleanup; + + /* open dlz_zone database. */ + result = bdbhpt_opendb(db->log, db->dbenv, DB_UNKNOWN, &db->zone, + dlz_zone, argv[3], 0); + if (result != ISC_R_SUCCESS) + goto init_cleanup; + + /* open dlz_client database. */ + result = bdbhpt_opendb(db->log, db->dbenv, DB_UNKNOWN, &db->client, + dlz_client, argv[3], DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) + goto init_cleanup; + + *dbdata = db; + + db->log(ISC_LOG_INFO, + "bdbhpt_dynamic: version %s, started", + dlz_bdbhpt_dynamic_version); + return(ISC_R_SUCCESS); + + init_cleanup: + bdbhpt_cleanup(db); + return result; } /* - * Perform a zone transfer - * - * BDB does not allow a secondary index on a database that allows - * duplicates. We have a few options: - * - * 1) kill speed by having lookup method use a secondary db which - * is associated to the primary DB with the DNS data. Then have - * another secondary db for zone transfer which also points to - * the dns_data primary. NO - The point of this driver is - * lookup performance. - * - * 2) Blow up database size by storing DNS data twice. Once for - * the lookup (dns_data) database, and a second time for the zone - * transfer (dns_xfr) database. NO - That would probably require - * a larger cache to provide good performance. Also, that would - * make the DB larger on disk potentially slowing it as well. - * - * 3) Loop through the dns_xfr database with a cursor to get - * all the different hosts in a zone. Then use the zone & host - * together to lookup the data in the dns_data database. YES - - * This may slow down zone xfr's a little, but that's ok they - * don't happen as often and don't need to be as fast. We can - * also use this table when deleting a zone (The BDB driver - * is read only - the delete would be used during replication - * updates by a separate process). + * Shut down the backend */ -isc_result_t -dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) { - isc_result_t result = ISC_R_NOTFOUND; - bdbhpt_instance_t *db = (bdbhpt_instance_t *) dbdata; - DBC *xfr_cursor = NULL; - DBC *dns_cursor = NULL; - DBT xfr_key, xfr_data, dns_key, dns_data; - int xfr_flags; - int dns_flags; - int bdbhptres; - bdbhpt_parsed_data_t pd; - char *tmp = NULL, *tmp_zone, *tmp_zone_host = NULL; - - memset(&xfr_key, 0, sizeof(DBT)); - memset(&xfr_data, 0, sizeof(DBT)); - memset(&dns_key, 0, sizeof(DBT)); - memset(&dns_data, 0, sizeof(DBT)); - - xfr_key.data = tmp_zone = strdup(zone); - if (xfr_key.data == NULL) - return (ISC_R_NOMEMORY); - - xfr_key.size = strlen(xfr_key.data); - - /* get a cursor to loop through dns_xfr table */ - if (db->xfr->cursor(db->xfr, NULL, &xfr_cursor, 0) != 0) { - result = ISC_R_FAILURE; - goto allnodes_cleanup; - } - - /* get a cursor to loop through dns_data table */ - if (db->data->cursor(db->data, NULL, &dns_cursor, 0) != 0) { - result = ISC_R_FAILURE; - goto allnodes_cleanup; - } - - xfr_flags = DB_SET; - - /* loop through xfr table for specified zone. */ - while ((bdbhptres = xfr_cursor->c_get(xfr_cursor, &xfr_key, &xfr_data, - xfr_flags)) == 0) { - - xfr_flags = DB_NEXT_DUP; - - /* +1 to allow for space between zone and host names */ - dns_key.size = xfr_data.size + xfr_key.size + 1; - - /* +1 to allow for null term at end of string. */ - dns_key.data = tmp_zone_host = malloc(dns_key.size + 1); - if (dns_key.data == NULL) - goto allnodes_cleanup; - - /* - * construct search key for dns_data. - * zone_name(a space)host_name - */ - strcpy(dns_key.data, zone); - strcat(dns_key.data, " "); - strncat(dns_key.data, xfr_data.data, xfr_data.size); - - dns_flags = DB_SET; - - while ((bdbhptres = dns_cursor->c_get(dns_cursor, &dns_key, - &dns_data, - dns_flags)) == 0) { - - dns_flags = DB_NEXT_DUP; - - /* +1 to allow for null term at end of string. */ - tmp = realloc(tmp, dns_data.size + 1); - if (tmp == NULL) - goto allnodes_cleanup; - - /* copy data to tmp string, and append null term. */ - strncpy(tmp, dns_data.data, dns_data.size); - tmp[dns_data.size] = '\0'; - - /* split string into dns data parts. */ - if (bdbhpt_parse_data(db->log, tmp, &pd) != ISC_R_SUCCESS) - goto allnodes_cleanup; - result = db->putnamedrr(allnodes, pd.host, - pd.type, pd.ttl, pd.data); - if (result != ISC_R_SUCCESS) - goto allnodes_cleanup; - - } /* end inner while loop */ - - /* clean up memory */ - if (tmp_zone_host != NULL) { - free(tmp_zone_host); - tmp_zone_host = NULL; - } - } /* end outer while loop */ - - allnodes_cleanup: - - /* free any memory */ - if (tmp != NULL) - free(tmp); - - if (tmp_zone_host != NULL) - free(tmp_zone_host); - - if (tmp_zone != NULL) - free(tmp_zone); - - /* get rid of cursors */ - if (xfr_cursor != NULL) - xfr_cursor->c_close(xfr_cursor); - - if (dns_cursor != NULL) - dns_cursor->c_close(dns_cursor); - - return result; +void +dlz_destroy(void *dbdata) { + struct bdbhpt_instance *db = (struct bdbhpt_instance *)dbdata; + + db->log(ISC_LOG_INFO, + "dlz_bdbhpt_dynamic (%s): shutting down", + dlz_bdbhpt_dynamic_version); + bdbhpt_cleanup((bdbhpt_instance_t *) dbdata); + free(db); } + +/* + * Return the version of the API + */ +int +dlz_version(unsigned int *flags) { + UNUSED(flags); + return (DLZ_DLOPEN_VERSION); +} + +/* + * Register a helper function from the bind9 dlz_dlopen driver + */ +static void +b9_add_helper(struct bdbhpt_instance *db, const char *helper_name, void *ptr) { + if (strcmp(helper_name, "log") == 0) + db->log = (log_t *)ptr; + if (strcmp(helper_name, "putrr") == 0) + db->putrr = (dns_sdlz_putrr_t *)ptr; + if (strcmp(helper_name, "putnamedrr") == 0) + db->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr; + if (strcmp(helper_name, "writeable_zone") == 0) + db->writeable_zone = (dns_dlz_writeablezone_t *)ptr; +} + diff --git a/contrib/dlz/modules/common/dlz_dbi.c b/contrib/dlz/modules/common/dlz_dbi.c new file mode 100644 index 0000000000..1a5dc0bd9f --- /dev/null +++ b/contrib/dlz/modules/common/dlz_dbi.c @@ -0,0 +1,461 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 1999-2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and/or 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +/*% + * properly destroys a querylist by de-allocating the + * memory for each query segment, and then the list itself + */ + +void +destroy_querylist(query_list_t **querylist) { + query_segment_t *tseg = NULL; + query_segment_t *nseg = NULL; + + /* if query list is null, nothing to do */ + if (*querylist == NULL) + return; + + /* start at the top of the list */ + nseg = DLZ_LIST_HEAD(**querylist); + while (nseg != NULL) { /* loop, until end of list */ + tseg = nseg; + /* + * free the query segment's text string but only if it + * was really a query segment, and not a pointer to + * %zone%, or %record%, or %client% + */ + if (tseg->cmd != NULL && tseg->direct == ISC_TRUE) + free(tseg->cmd); + /* get the next query segment, before we destroy this one. */ + nseg = DLZ_LIST_NEXT(nseg, link); + /* deallocate this query segment. */ + free(tseg); + } + /* deallocate the query segment list */ + free(*querylist); +} + +/*% constructs a query list by parsing a string into query segments */ +isc_result_t +build_querylist(const char *query_str, char **zone, char **record, + char **client, query_list_t **querylist, unsigned int flags, + log_t log) +{ + isc_result_t result; + isc_boolean_t foundzone = ISC_FALSE; + isc_boolean_t foundrecord = ISC_FALSE; + isc_boolean_t foundclient = ISC_FALSE; + char *temp_str = NULL; + char *right_str = NULL; + query_list_t *tql; + query_segment_t *tseg = NULL; + + /* if query string is null, or zero length */ + if (query_str == NULL || strlen(query_str) < 1) { + if ((flags & REQUIRE_QUERY) == 0) + /* we don't need it were ok. */ + return (ISC_R_SUCCESS); + else + /* we did need it, PROBLEM!!! */ + return (ISC_R_FAILURE); + } + + /* allocate memory for query list */ + tql = calloc(1, sizeof(query_list_t)); + /* couldn't allocate memory. Problem!! */ + if (tql == NULL) + return (ISC_R_NOMEMORY); + + /* initialize the query segment list */ + DLZ_LIST_INIT(*tql); + + /* make a copy of query_str so we can chop it up */ + temp_str = right_str = strdup(query_str); + /* couldn't make a copy, problem!! */ + if (right_str == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* loop through the string and chop it up */ + while (right_str != NULL) { + /* allocate memory for tseg */ + tseg = calloc(1, sizeof(query_segment_t)); + if (tseg == NULL) { /* no memory, clean everything up. */ + result = ISC_R_NOMEMORY; + goto cleanup; + } + tseg->cmd = NULL; + tseg->direct = ISC_FALSE; + /* initialize the query segment link */ + DLZ_LINK_INIT(tseg, link); + /* append the query segment to the list */ + DLZ_LIST_APPEND(*tql, tseg, link); + + /* + * split string at the first "$". set query segment to + * left portion + */ + tseg->cmd = strdup(strsep(&right_str, "$")); + if (tseg->cmd == NULL) { + /* no memory, clean everything up. */ + result = ISC_R_NOMEMORY; + goto cleanup; + } + /* tseg->cmd points directly to a string. */ + tseg->direct = ISC_TRUE; + tseg->strlen = strlen(tseg->cmd); + + /* check if we encountered "$zone$" token */ + if (strcasecmp(tseg->cmd, "zone") == 0) { + /* + * we don't really need, or want the "zone" + * text, so get rid of it. + */ + free(tseg->cmd); + /* set tseg->cmd to in-direct zone string */ + tseg->cmd = (char**) zone; + tseg->strlen = 0; + /* tseg->cmd points in-directly to a string */ + tseg->direct = ISC_FALSE; + foundzone = ISC_TRUE; + /* check if we encountered "$record$" token */ + } else if (strcasecmp(tseg->cmd, "record") == 0) { + /* + * we don't really need, or want the "record" + * text, so get rid of it. + */ + free(tseg->cmd); + /* set tseg->cmd to in-direct record string */ + tseg->cmd = (char**) record; + tseg->strlen = 0; + /* tseg->cmd points in-directly poinsts to a string */ + tseg->direct = ISC_FALSE; + foundrecord = ISC_TRUE; + /* check if we encountered "$client$" token */ + } else if (strcasecmp(tseg->cmd, "client") == 0) { + /* + * we don't really need, or want the "client" + * text, so get rid of it. + */ + free(tseg->cmd); + /* set tseg->cmd to in-direct record string */ + tseg->cmd = (char**) client; + tseg->strlen = 0; + /* tseg->cmd points in-directly poinsts to a string */ + tseg->direct = ISC_FALSE; + foundclient = ISC_TRUE; + } + } + + /* we don't need temp_str any more */ + free(temp_str); + /* + * add checks later to verify zone and record are found if + * necessary. + */ + + /* if this query requires %client%, make sure we found it */ + if (((flags & REQUIRE_CLIENT) != 0) && (!foundclient) ) { + /* Write error message to log */ + if (log != NULL) + log(ISC_LOG_ERROR, + "Required token $client$ not found."); + result = ISC_R_FAILURE; + goto flag_fail; + } + + /* if this query requires %record%, make sure we found it */ + if (((flags & REQUIRE_RECORD) != 0) && (!foundrecord) ) { + /* Write error message to log */ + if (log != NULL) + log(ISC_LOG_ERROR, + "Required token $record$ not found."); + result = ISC_R_FAILURE; + goto flag_fail; + } + + /* if this query requires %zone%, make sure we found it */ + if (((flags & REQUIRE_ZONE) != 0) && (!foundzone) ) { + /* Write error message to log */ + if (log != NULL) + log(ISC_LOG_ERROR, "Required token $zone$ not found."); + result = ISC_R_FAILURE; + goto flag_fail; + } + + /* pass back the query list */ + *querylist = (query_list_t *) tql; + + /* return success */ + return (ISC_R_SUCCESS); + + cleanup: + /* get rid of temp_str */ + if (temp_str != NULL) + free(temp_str); + + flag_fail: + /* get rid of what was build of the query list */ + if (tql != NULL) + destroy_querylist(&tql); + return (result); +} + +/*% + * build a query string from query segments, and dynamic segments + * dynamic segments replace where the tokens %zone%, %record%, %client% + * used to be in our queries from named.conf + */ +char * +build_querystring(query_list_t *querylist) { + query_segment_t *tseg = NULL; + unsigned int length = 0; + char *qs = NULL; + + /* start at the top of the list */ + tseg = DLZ_LIST_HEAD(*querylist); + while (tseg != NULL) { + /* + * if this is a query segment, use the + * precalculated string length + */ + if (tseg->direct == ISC_TRUE) + length += tseg->strlen; + else /* calculate string length for dynamic segments. */ + length += strlen(* (char**) tseg->cmd); + /* get the next segment */ + tseg = DLZ_LIST_NEXT(tseg, link); + } + + /* allocate memory for the string */ + qs = malloc(length + 1); + /* couldn't allocate memory, We need more ram! */ + if (qs == NULL) + return (NULL); + + *qs = 0; + /* start at the top of the list again */ + tseg = DLZ_LIST_HEAD(*querylist); + while (tseg != NULL) { + if (tseg->direct == ISC_TRUE) + /* query segments */ + strcat(qs, tseg->cmd); + else + /* dynamic segments */ + strcat(qs, * (char**) tseg->cmd); + /* get the next segment */ + tseg = DLZ_LIST_NEXT(tseg, link); + } + + return (qs); +} + +/*% constructs a dbinstance (DBI) */ +isc_result_t +build_dbinstance(const char *allnodes_str, const char *allowxfr_str, + const char *authority_str, const char *findzone_str, + const char *lookup_str, const char *countzone_str, + dbinstance_t **dbi, log_t log) +{ + + isc_result_t result; + dbinstance_t *db = NULL; + int err; + + /* allocate and zero memory for driver structure */ + db = calloc(1, sizeof(dbinstance_t)); + if (db == NULL) { + if (log != NULL) + log(ISC_LOG_ERROR, + "Could not allocate memory for " + "database instance object."); + return (ISC_R_NOMEMORY); + } + memset(db, 0, sizeof(dbinstance_t)); + db->dbconn = NULL; + db->client = NULL; + db->record = NULL; + db->zone = NULL; + db->query_buf = NULL; + db->allnodes_q = NULL; + db->allowxfr_q = NULL; + db->authority_q = NULL; + db->findzone_q = NULL; + db->countzone_q = NULL; + db->lookup_q = NULL; + + /* initialize the reference count mutex */ + err = dlz_mutex_init(&db->lock, NULL); + if (err == ENOMEM) { + result = ISC_R_NOMEMORY; + goto cleanup; + } else if (err != 0) { + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + /* build the all nodes query list */ + result = build_querylist(allnodes_str, &db->zone, &db->record, + &db->client, &db->allnodes_q, + REQUIRE_ZONE, log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) + log(ISC_LOG_ERROR, + "Could not build all nodes query list"); + goto cleanup; + } + + /* build the allow zone transfer query list */ + result = build_querylist(allowxfr_str, &db->zone, &db->record, + &db->client, &db->allowxfr_q, + REQUIRE_ZONE | REQUIRE_CLIENT, + log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) + log(ISC_LOG_ERROR, + "Could not build allow xfr query list"); + goto cleanup; + } + + /* build the authority query, query list */ + result = build_querylist(authority_str, &db->zone, &db->record, + &db->client, &db->authority_q, + REQUIRE_ZONE, log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) + log(ISC_LOG_ERROR, + "Could not build authority query list"); + goto cleanup; + } + + /* build findzone query, query list */ + result = build_querylist(findzone_str, &db->zone, &db->record, + &db->client, &db->findzone_q, + REQUIRE_ZONE, log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) + log(ISC_LOG_ERROR, + "Could not build find zone query list"); + goto cleanup; + } + + /* build countzone query, query list */ + result = build_querylist(countzone_str, &db->zone, &db->record, + &db->client, &db->countzone_q, + REQUIRE_ZONE, log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) + log(ISC_LOG_ERROR, + "Could not build count zone query list"); + goto cleanup; + } + + /* build lookup query, query list */ + result = build_querylist(lookup_str, &db->zone, &db->record, + &db->client, &db->lookup_q, + REQUIRE_RECORD, log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) + log(ISC_LOG_ERROR, + "Could not build lookup query list"); + goto cleanup; + } + + /* pass back the db instance */ + *dbi = (dbinstance_t *) db; + + /* return success */ + return (ISC_R_SUCCESS); + + cleanup: + /* destroy whatever was build of the db instance */ + destroy_dbinstance(db); + /* return failure */ + return (ISC_R_FAILURE); +} + +void +destroy_dbinstance(dbinstance_t *dbi) { + /* destroy any query lists we created */ + destroy_querylist(&dbi->allnodes_q); + destroy_querylist(&dbi->allowxfr_q); + destroy_querylist(&dbi->authority_q); + destroy_querylist(&dbi->findzone_q); + destroy_querylist(&dbi->countzone_q); + destroy_querylist(&dbi->lookup_q); + + /* get rid of the mutex */ + (void) dlz_mutex_destroy(&dbi->lock); + + /* return, and detach the memory */ + free(dbi); +} diff --git a/contrib/dlz/modules/filesystem/Makefile b/contrib/dlz/modules/filesystem/Makefile new file mode 100644 index 0000000000..fd87ee89b2 --- /dev/null +++ b/contrib/dlz/modules/filesystem/Makefile @@ -0,0 +1,20 @@ +prefix = /usr +libdir = $(prefix)/lib/bind9 + +CFLAGS=-fPIC -g -I../include + +all: dlz_filesystem_dynamic.so + +dir.o: dir.c + $(CC) $(CFLAGS) -c dir.c + +dlz_filesystem_dynamic.so: dlz_filesystem_dynamic.c dir.o + $(CC) $(CFLAGS) -shared -o dlz_filesystem_dynamic.so \ + dlz_filesystem_dynamic.c dir.o + +clean: + rm -f dlz_filesystem_dynamic.so *.o + +install: dlz_filesystem_dynamic.so + mkdir -p $(DESTDIR)$(libdir) + install dlz_filesystem_dynamic.so $(DESTDIR)$(libdir) diff --git a/contrib/dlz/modules/filesystem/dir.c b/contrib/dlz/modules/filesystem/dir.c new file mode 100644 index 0000000000..e5f1ac4f6f --- /dev/null +++ b/contrib/dlz/modules/filesystem/dir.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include +#include + +#include "dlz_minimal.h" +#include "dir.h" + +void +dir_init(dir_t *dir) { + dir->entry.name[0] = '\0'; + dir->entry.length = 0; + + dir->handle = NULL; +} + +isc_result_t +dir_open(dir_t *dir, const char *dirname) { + char *p; + isc_result_t result = ISC_R_SUCCESS; + + if (strlen(dirname) + 3 > sizeof(dir->dirname)) + return (ISC_R_NOSPACE); + strcpy(dir->dirname, dirname); + + p = dir->dirname + strlen(dir->dirname); + if (dir->dirname < p && *(p - 1) != '/') + *p++ = '/'; + *p++ = '*'; + *p = '\0'; + + dir->handle = opendir(dirname); + if (dir->handle == NULL) { + switch (errno) { + case ENOTDIR: + case ELOOP: + case EINVAL: + case ENAMETOOLONG: + case EBADF: + result = ISC_R_INVALIDFILE; + case ENOENT: + result = ISC_R_FILENOTFOUND; + case EACCES: + case EPERM: + result = ISC_R_NOPERM; + case ENOMEM: + result = ISC_R_NOMEMORY; + default: + result = ISC_R_UNEXPECTED; + } + } + + return (result); +} + +/*! + * \brief Return previously retrieved file or get next one. + + * Unix's dirent has + * separate open and read functions, but the Win32 and DOS interfaces open + * the dir stream and reads the first file in one operation. + */ +isc_result_t +dir_read(dir_t *dir) { + struct dirent *entry; + + entry = readdir(dir->handle); + if (entry == NULL) + return (ISC_R_NOMORE); + + if (sizeof(dir->entry.name) <= strlen(entry->d_name)) + return (ISC_R_UNEXPECTED); + + strcpy(dir->entry.name, entry->d_name); + + dir->entry.length = strlen(entry->d_name); + return (ISC_R_SUCCESS); +} + +/*! + * \brief Close directory stream. + */ +void +dir_close(dir_t *dir) { + (void)closedir(dir->handle); + dir->handle = NULL; +} + +/*! + * \brief Reposition directory stream at start. + */ +isc_result_t +dir_reset(dir_t *dir) { + rewinddir(dir->handle); + + return (ISC_R_SUCCESS); +} diff --git a/contrib/dlz/modules/filesystem/dir.h b/contrib/dlz/modules/filesystem/dir.h new file mode 100644 index 0000000000..66041fac52 --- /dev/null +++ b/contrib/dlz/modules/filesystem/dir.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#define DIR_NAMEMAX 256 +#define DIR_PATHMAX 1024 + +typedef struct direntry { + char name[DIR_NAMEMAX]; + unsigned int length; +} direntry_t; + +typedef struct dir { + char dirname[DIR_PATHMAX]; + direntry_t entry; + DIR * handle; +} dir_t; + +void +dir_init(dir_t *dir); + +isc_result_t +dir_open(dir_t *dir, const char *dirname); + +isc_result_t +dir_read(dir_t *dir); + +isc_result_t +dir_reset(dir_t *dir); + +void +dir_close(dir_t *dir); diff --git a/contrib/dlz/modules/filesystem/dlz_filesystem_dynamic.c b/contrib/dlz/modules/filesystem/dlz_filesystem_dynamic.c new file mode 100644 index 0000000000..d199d3979a --- /dev/null +++ b/contrib/dlz/modules/filesystem/dlz_filesystem_dynamic.c @@ -0,0 +1,979 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 1999-2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and/or 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This provides the externally loadable filesystem DLZ module, without + * update support + */ + +#include +#include +#include +#include +#include + +#include + +#include "dlz_minimal.h" +#include "dlz_list.h" +#include "dir.h" + +typedef struct config_data { + char *basedir; + int basedirsize; + char *datadir; + int datadirsize; + char *xfrdir; + int xfrdirsize; + int splitcnt; + char separator; + char pathsep; + + /* Helper functions from the dlz_dlopen driver */ + log_t *log; + dns_sdlz_putrr_t *putrr; + dns_sdlz_putnamedrr_t *putnamedrr; + dns_dlz_writeablezone_t *writeable_zone; +} config_data_t; + +typedef struct dir_entry dir_entry_t; + +struct dir_entry { + char dirpath[DIR_PATHMAX]; + DLZ_LINK(dir_entry_t) link; +}; + +typedef DLZ_LIST(dir_entry_t) dlist_t; + +/* forward reference */ + +static void +b9_add_helper(struct config_data *cd, const char *helper_name, void *ptr); + +/* + * Private methods + */ +static isc_boolean_t +is_safe(const char *input) { + unsigned int i; + unsigned int len = strlen(input); + + /* check that only allowed characters are in the domain name */ + for (i = 0; i < len; i++) { + /* '.' is allowed, but has special requirements */ + if (input[i] == '.') { + /* '.' is not allowed as first char */ + if (i == 0) + return (ISC_FALSE); + /* '..', two dots together is not allowed. */ + else if (input[i-1] == '.') + return (ISC_FALSE); + /* '.' is not allowed as last char */ + if (i == len) + return (ISC_FALSE); + /* only 1 dot in ok location, continue at next char */ + continue; + } + /* '-' is allowed, continue at next char */ + if (input[i] == '-') + continue; + /* 0-9 is allowed, continue at next char */ + if (input[i] >= '0' && input[i] <= '9') + continue; + /* A-Z uppercase is allowed, continue at next char */ + if (input[i] >= 'A' && input[i] <= 'Z') + continue; + /* a-z lowercase is allowed, continue at next char */ + if (input[i] >= 'a' && input[i] <= 'z') + continue; + + /* + * colon needs to be allowed for IPV6 client + * addresses. Not dangerous in domain names, as not a + * special char. + */ + if (input[i] == ':') + continue; + + /* + * '@' needs to be allowed for in zone data. Not + * dangerous in domain names, as not a special char. + */ + if (input[i] == '@') + continue; + + /* + * if we reach this point we have encountered a + * disallowed char! + */ + return (ISC_FALSE); + } + /* everything ok. */ + return (ISC_TRUE); +} + +static isc_result_t +create_path_helper(char *out, const char *in, config_data_t *cd) { + char *tmpString; + char *tmpPtr; + int i; + + tmpString = strdup(in); + if (tmpString == NULL) + return (ISC_R_NOMEMORY); + + /* + * don't forget is_safe guarantees '.' will NOT be the + * first/last char + */ + while ((tmpPtr = strrchr(tmpString, '.')) != NULL) { + i = 0; + while (tmpPtr[i+1] != '\0') { + if (cd->splitcnt < 1) + strcat(out, (char *) &tmpPtr[i+1]); + else + strncat(out, (char *) &tmpPtr[i+1], + cd->splitcnt); + strncat(out, (char *) &cd->pathsep, 1); + if (cd->splitcnt == 0) + break; + if (strlen((char *) &tmpPtr[i+1]) <= + (unsigned int) cd->splitcnt) + break; + i += cd->splitcnt; + } + tmpPtr[0] = '\0'; + } + + /* handle the "first" label properly */ + i=0; + tmpPtr = tmpString; + while (tmpPtr[i] != '\0') { + if (cd->splitcnt < 1) + strcat(out, (char *) &tmpPtr[i]); + else + strncat(out, (char *) &tmpPtr[i], cd->splitcnt); + strncat(out, (char *) &cd->pathsep, 1); + if (cd->splitcnt == 0) + break; + if (strlen((char *) &tmpPtr[i]) <= + (unsigned int) cd->splitcnt) + break; + i += cd->splitcnt; + } + + free(tmpString); + return (ISC_R_SUCCESS); +} + +/*% + * Checks to make sure zone and host are safe. If safe, then + * hashes zone and host strings to build a path. If zone / host + * are not safe an error is returned. + */ + +static isc_result_t +create_path(const char *zone, const char *host, const char *client, + config_data_t *cd, char **path) +{ + + char *tmpPath; + int pathsize; + int len; + isc_result_t result; + isc_boolean_t isroot = ISC_FALSE; + + /* special case for root zone */ + if (strcmp(zone, ".") == 0) + isroot = ISC_TRUE; + + /* if the requested zone is "unsafe", return error */ + if (!isroot && !is_safe(zone)) + return (ISC_R_FAILURE); + + /* if host was passed, verify that it is safe */ + if (host != NULL && !is_safe(host)) + return (ISC_R_FAILURE); + + /* if client was passed, verify that it is safe */ + if (client != NULL && !is_safe(client)) + return (ISC_R_FAILURE); + + /* Determine how much memory the split up string will require */ + if (host != NULL) + len = strlen(zone) + strlen(host); + else if (client != NULL) + len = strlen(zone) + strlen(client); + else + len = strlen(zone); + + /* + * even though datadir and xfrdir will never be in the same + * string we only waste a few bytes by allocating for both, + * and then we are safe from buffer overruns. + */ + pathsize = len + cd->basedirsize + + cd->datadirsize + cd->xfrdirsize + 4; + + /* if we are splitting names, we will need extra space. */ + if (cd->splitcnt > 0) + pathsize += len/cd->splitcnt; + + tmpPath = malloc(pathsize * sizeof(char)); + if (tmpPath == NULL) { + /* write error message */ + cd->log(ISC_LOG_ERROR, + "Filesystem driver unable to " + "allocate memory in create_path()."); + result = ISC_R_NOMEMORY; + goto cleanup_mem; + } + + /* + * build path string. + * start out with base directory. + */ + strcpy(tmpPath, cd->basedir); + + /* add zone name - parsed properly */ + if (!isroot) { + result = create_path_helper(tmpPath, zone, cd); + if (result != ISC_R_SUCCESS) + goto cleanup_mem; + } + + /* + * When neither client or host is passed we are building a + * path to see if a zone is supported. We require that a zone + * path have the "data dir" directory contained within it so + * that we know this zone is really supported. Otherwise, + * this zone may not really be supported because we are + * supporting a delagated sub zone. + * + * Example: + * + * We are supporting long.domain.com and using a splitcnt of + * 0. the base dir is "/base-dir/" and the data dir is + * "/.datadir" We want to see if we are authoritative for + * domain.com. Path /base-dir/com/domain/.datadir since + * /base-dir/com/domain/.datadir does not exist, we are not + * authoritative for the domain "domain.com". However we are + * authoritative for the domain "long.domain.com" because the + * path /base-dir/com/domain/long/.datadir does exist! + */ + + /* if client is passed append xfr dir, otherwise append data dir */ + if (client != NULL) { + strcat(tmpPath, cd->xfrdir); + strncat(tmpPath, (char *) &cd->pathsep, 1); + strcat(tmpPath, client); + } else + strcat(tmpPath, cd->datadir); + + /* if host not null, add it. */ + if (host != NULL) { + strncat(tmpPath, (char *) &cd->pathsep, 1); + result = create_path_helper(tmpPath, host, cd); + if (result != ISC_R_SUCCESS) + goto cleanup_mem; + } + + /* return the path we built. */ + *path = tmpPath; + + /* return success */ + result = ISC_R_SUCCESS; + + cleanup_mem: + /* cleanup memory */ + + /* free tmpPath memory */ + if (tmpPath != NULL && result != ISC_R_SUCCESS) + free(tmpPath); + + return (result); +} + +static isc_result_t +process_dir(dir_t *dir, void *passback, config_data_t *cd, + dlist_t *dir_list, unsigned int basedirlen) +{ + + char tmp[DIR_PATHMAX + DIR_NAMEMAX]; + int astPos; + struct stat sb; + isc_result_t result = ISC_R_FAILURE; + char *endp; + char *type; + char *ttlStr; + char *data; + char host[DIR_NAMEMAX]; + char *tmpString; + char *tmpPtr; + int ttl; + int i; + int len; + dir_entry_t *direntry; + isc_boolean_t foundHost; + + tmp[0] = '\0'; /* set 1st byte to '\0' so strcpy works right. */ + host[0] = '\0'; + foundHost = ISC_FALSE; + + /* copy base directory name to tmp. */ + strcpy(tmp, dir->dirname); + + /* dir->dirname will always have '*' as the last char. */ + astPos = strlen(dir->dirname) - 1; + + /* if dir_list != NULL, were are performing a zone xfr */ + if (dir_list != NULL) { + /* if splitcnt == 0, determine host from path. */ + if (cd->splitcnt == 0) { + if (strlen(tmp) - 3 > basedirlen) { + tmp[astPos-1] = '\0'; + tmpString = (char *) &tmp[basedirlen+1]; + /* handle filesystem's special wildcard "-" */ + if (strcmp(tmpString, "-") == 0) { + strcpy(host, "*"); + } else { + /* + * not special wildcard -- normal name + */ + while ((tmpPtr = strrchr(tmpString, + cd->pathsep)) + != NULL) + { + if ((strlen(host) + + strlen(tmpPtr + 1) + 2) + > DIR_NAMEMAX) + continue; + strcat(host, tmpPtr + 1); + strcat(host, "."); + tmpPtr[0] = '\0'; + } + if ((strlen(host) + + strlen(tmpString) + 1) + <= DIR_NAMEMAX) + strcat(host, tmpString); + } + + foundHost = ISC_TRUE; + /* set tmp again for use later */ + strcpy(tmp, dir->dirname); + } + } else { + /* + * if splitcnt != 0 determine host from + * ".host" directory entry + */ + while (dir_read(dir) == ISC_R_SUCCESS) { + if (strncasecmp(".host", + dir->entry.name, 5) == 0) { + /* + * handle filesystem's special + * wildcard "-" + */ + if (strcmp((char *) &dir->entry.name[6], + "-") == 0) + strcpy(host, "*"); + else { + strncpy(host, + (char *) &dir->entry.name[6], + sizeof(host) - 1); + host[255] = '\0'; + } + foundHost = ISC_TRUE; + break; + } + } + /* reset dir list for use later */ + dir_reset(dir); + } /* end of else */ + } + + while (dir_read(dir) == ISC_R_SUCCESS) { + cd->log(ISC_LOG_DEBUG(1), + "Filesystem driver Dir name:" + " '%s' Dir entry: '%s'\n", + dir->dirname, dir->entry.name); + + /* skip any entries starting with "." */ + if (dir->entry.name[0] == '.') + continue; + + /* + * get rid of '*', set to NULL. Effectively trims + * string from previous loop to base directory only + * while still leaving memory for concat to be + * performed next. + */ + + tmp[astPos] = '\0'; + + /* add name to base directory name. */ + strcat(tmp, dir->entry.name); + + /* make sure we can stat entry */ + if (stat(tmp, &sb) == 0 ) { + /* if entry is a directory */ + if ((sb.st_mode & S_IFDIR) != 0) { + /* + * if dir list is NOT NULL, add dir to + * dir list + */ + if (dir_list != NULL) { + direntry = malloc(sizeof(dir_entry_t)); + if (direntry == NULL) + return (ISC_R_NOMEMORY); + strcpy(direntry->dirpath, tmp); + DLZ_LINK_INIT(direntry, link); + DLZ_LIST_APPEND(*dir_list, direntry, + link); + result = ISC_R_SUCCESS; + } + continue; + + /* + * if entry is a file be sure we do + * not add entry to DNS results if we + * are performing a zone xfr and we + * could not find a host entry. + */ + + } else if (dir_list != NULL && + foundHost == ISC_FALSE) { + continue; + } + } else /* if we cannot stat entry, skip it. */ + continue; + + type = dir->entry.name; + ttlStr = strchr(type, cd->separator); + if (ttlStr == NULL) { + cd->log(ISC_LOG_ERROR, + "Filesystem driver: " + "%s could not be parsed properly", tmp); + return (ISC_R_FAILURE); + } + + /* replace separator char with NULL to split string */ + ttlStr[0] = '\0'; + /* start string after NULL of previous string */ + ttlStr = (char *) &ttlStr[1]; + + data = strchr(ttlStr, cd->separator); + if (data == NULL) { + cd->log(ISC_LOG_ERROR, + "Filesystem driver: " + "%s could not be parsed properly", tmp); + return (ISC_R_FAILURE); + } + + /* replace separator char with NULL to split string */ + data[0] = '\0'; + + /* start string after NULL of previous string */ + data = (char *) &data[1]; + + /* replace all cd->separator chars with a space. */ + len = strlen(data); + + for (i=0; i < len; i++) { + if (data[i] == cd->separator) + data[i] = ' '; + } + + /* convert text to int, make sure it worked right */ + ttl = strtol(ttlStr, &endp, 10); + if (*endp != '\0' || ttl < 0) + cd->log(ISC_LOG_ERROR, + "Filesystem driver " + "ttl must be a postive number"); + + /* pass data back to Bind */ + if (dir_list == NULL) + result = cd->putrr((dns_sdlzlookup_t *) passback, + type, ttl, data); + else + result = cd->putnamedrr((dns_sdlzallnodes_t *) passback, + (char *) host, + type, ttl, data); + + /* if error, return error right away */ + if (result != ISC_R_SUCCESS) + return (result); + } /* end of while loop */ + + return (result); +} + +/* + * DLZ methods + */ +isc_result_t +dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { + isc_result_t result; + char *path; + struct stat sb; + config_data_t *cd; + path = NULL; + + cd = (config_data_t *) dbdata; + + if (create_path(name, NULL, client, cd, &path) != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + if (stat(path, &sb) != 0) { + result = ISC_R_NOTFOUND; + goto complete_AXFR; + } + + if ((sb.st_mode & S_IFREG) != 0) { + result = ISC_R_SUCCESS; + goto complete_AXFR; + } + + result = ISC_R_NOTFOUND; + + complete_AXFR: + free(path); + return (result); +} + +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) { + isc_result_t result; + dlist_t *dir_list; + config_data_t *cd = (config_data_t *) dbdata; + char *basepath; + unsigned int basepathlen; + struct stat sb; + dir_t dir; + dir_entry_t *dir_entry; + dir_entry_t *next_de; + + basepath = NULL; + dir_list = NULL; + + /* allocate memory for list */ + dir_list = malloc(sizeof(dlist_t)); + if (dir_list == NULL) { + result = ISC_R_NOTFOUND; + goto complete_allnds; + } + + /* initialize list */ + DLZ_LIST_INIT(*dir_list); + + if (create_path(zone, NULL, NULL, cd, &basepath) != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + /* remove path separator at end of path so stat works properly */ + basepathlen = strlen(basepath); + + if (stat(basepath, &sb) != 0) { + result = ISC_R_NOTFOUND; + goto complete_allnds; + } + + if ((sb.st_mode & S_IFDIR) == 0) { + result = ISC_R_NOTFOUND; + goto complete_allnds; + } + + /* initialize and open directory */ + dir_init(&dir); + result = dir_open(&dir, basepath); + + /* if directory open failed, return error. */ + if (result != ISC_R_SUCCESS) { + cd->log(ISC_LOG_ERROR, + "Unable to open %s directory to read entries.", + basepath); + result = ISC_R_FAILURE; + goto complete_allnds; + } + + /* process the directory */ + result = process_dir(&dir, allnodes, cd, dir_list, basepathlen); + + /* close the directory */ + dir_close(&dir); + + if (result != ISC_R_SUCCESS) + goto complete_allnds; + + /* get first dir entry from list. */ + dir_entry = DLZ_LIST_HEAD(*dir_list); + while (dir_entry != NULL) { + result = dir_open(&dir, dir_entry->dirpath); + /* if directory open failed, return error. */ + if (result != ISC_R_SUCCESS) { + cd->log(ISC_LOG_ERROR, + "Unable to open %s " + "directory to read entries.", basepath); + result = ISC_R_FAILURE; + goto complete_allnds; + } + + /* process the directory */ + result = process_dir(&dir, allnodes, cd, dir_list, basepathlen); + + /* close the directory */ + dir_close(&dir); + + if (result != ISC_R_SUCCESS) + goto complete_allnds; + + dir_entry = DLZ_LIST_NEXT(dir_entry, link); + } /* end while */ + + complete_allnds: + if (dir_list != NULL) { + /* clean up entries from list. */ + dir_entry = DLZ_LIST_HEAD(*dir_list); + while (dir_entry != NULL) { + next_de = DLZ_LIST_NEXT(dir_entry, link); + free(dir_entry); + dir_entry = next_de; + } /* end while */ + free(dir_list); + } + + if (basepath != NULL) + free(basepath); + + return (result); +} + +#if DLZ_DLOPEN_VERSION < 3 +isc_result_t +dlz_findzonedb(void *dbdata, const char *name) +#else +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif +{ + + isc_result_t result; + config_data_t *cd = (config_data_t *) dbdata; + char *path; + struct stat sb; + path = NULL; + +#if DLZ_DLOPEN_VERSION >= 3 + UNUSED(methods); + UNUSED(clientinfo); +#endif + + if (create_path(name, NULL, NULL, cd, &path) != ISC_R_SUCCESS) + return (ISC_R_NOTFOUND); + + cd->log(ISC_LOG_DEBUG(1), + "Filesystem driver Findzone() Checking for path: '%s'\n", path); + + if (stat(path, &sb) != 0) { + result = ISC_R_NOTFOUND; + goto complete_FZ; + } + + if ((sb.st_mode & S_IFDIR) != 0) { + result = ISC_R_SUCCESS; + goto complete_FZ; + } + + result = ISC_R_NOTFOUND; + + complete_FZ: + + free(path); + return (result); +} + +#if DLZ_DLOPEN_VERSION == 1 +isc_result_t +dlz_lookup(const char *zone, const char *name, + void *dbdata, dns_sdlzlookup_t *lookup) +#else +isc_result_t +dlz_lookup(const char *zone, const char *name, + void *dbdata, dns_sdlzlookup_t *lookup, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif +{ + isc_result_t result = ISC_R_NOTFOUND; + config_data_t *cd = (config_data_t *) dbdata; + char *path; + struct stat sb; + dir_t dir; + path = NULL; + + UNUSED(lookup); +#if DLZ_DLOPEN_VERSION >= 2 + UNUSED(methods); + UNUSED(clientinfo); +#endif + + if (strcmp(name, "*") == 0) + /* + * handle filesystem's special wildcard "-" + */ + result = create_path(zone, "-", NULL, cd, &path); + else + result = create_path(zone, name, NULL, cd, &path); + + if (result != ISC_R_SUCCESS) + return (ISC_R_NOTFOUND); + + /* remove path separator at end of path so stat works properly */ + path[strlen(path)-1] = '\0'; + + cd->log(ISC_LOG_DEBUG(1), + "Filesystem driver lookup() Checking for path: '%s'\n", path); + + if (stat(path, &sb) != 0) { + result = ISC_R_NOTFOUND; + goto complete_lkup; + } + + if ((sb.st_mode & S_IFDIR) == 0) { + result = ISC_R_NOTFOUND; + goto complete_lkup; + } + + /* initialize and open directory */ + dir_init(&dir); + result = dir_open(&dir, path); + + /* if directory open failed, return error. */ + if (result != ISC_R_SUCCESS) { + cd->log(ISC_LOG_ERROR, + "Unable to open %s directory to read entries.", path); + result = ISC_R_FAILURE; + goto complete_lkup; + } + + /* process any records in the directory */ + result = process_dir(&dir, lookup, cd, NULL, 0); + + /* close the directory */ + dir_close(&dir); + + complete_lkup: + + free(path); + return (result); +} + +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], + void **dbdata, ...) +{ + config_data_t *cd; + char *endp; + int len; + char pathsep; + const char *helper_name; + va_list ap; + + UNUSED(dlzname); + + /* allocate memory for our config data and helper functions */ + cd = calloc(1, sizeof(config_data_t)); + if (cd == NULL) + goto no_mem; + + /* zero the memory */ + memset(cd, 0, sizeof(config_data_t)); + + /* Fill in the helper functions */ + va_start(ap, dbdata); + while ((helper_name = va_arg(ap, const char*)) != NULL) + b9_add_helper(cd, helper_name, va_arg(ap, void*)); + va_end(ap); + + /* we require 5 command line args. */ + if (argc != 6) { + cd->log(ISC_LOG_ERROR, + "Filesystem driver requires " + "6 command line args."); + return (ISC_R_FAILURE); + } + + if (strlen(argv[5]) > 1) { + cd->log(ISC_LOG_ERROR, + "Filesystem driver can only " + "accept a single character for separator."); + return (ISC_R_FAILURE); + } + + /* verify base dir ends with '/' or '\' */ + len = strlen(argv[1]); + if (argv[1][len-1] != '\\' && argv[1][len-1] != '/') { + cd->log(ISC_LOG_ERROR, + "Base dir parameter for filesystem driver " + "should end with %s", + "either '/' or '\\' "); + return (ISC_R_FAILURE); + } + + /* determine and save path separator for later */ + if (argv[1][len-1] == '\\') + pathsep = '\\'; + else + pathsep = '/'; + + cd->pathsep = pathsep; + + /* get and store our base directory */ + cd->basedir = strdup(argv[1]); + if (cd->basedir == NULL) + goto no_mem; + cd->basedirsize = strlen(cd->basedir); + + /* get and store our data sub-dir */ + cd->datadir = strdup(argv[2]); + if (cd->datadir == NULL) + goto no_mem; + cd->datadirsize = strlen(cd->datadir); + + /* get and store our zone xfr sub-dir */ + cd->xfrdir = strdup(argv[3]); + if (cd->xfrdir == NULL) + goto no_mem; + cd->xfrdirsize = strlen(cd->xfrdir); + + /* get and store our directory split count */ + cd->splitcnt = strtol(argv[4], &endp, 10); + if (*endp != '\0' || cd->splitcnt < 0) + cd->log(ISC_LOG_ERROR, + "Directory split count must be zero (0) " + "or a postive number"); + + /* get and store our separator character */ + cd->separator = *argv[5]; + + /* pass back config data */ + *dbdata = cd; + + /* return success */ + return (ISC_R_SUCCESS); + + /* handle no memory error */ + no_mem: + + /* write error message */ + if (cd != NULL && cd->log != NULL) + cd->log(ISC_LOG_ERROR, + "filesystem_dynamic: Filesystem driver unable to " + "allocate memory for config data."); + + /* if we allocated a config data object clean it up */ + if (cd != NULL) + dlz_destroy(cd); + + /* return error */ + return (ISC_R_NOMEMORY); +} + +void +dlz_destroy(void *dbdata) { + config_data_t *cd; + + cd = (config_data_t *) dbdata; + + /* + * free memory for each section of config data that was + * allocated + */ + if (cd->basedir != NULL) + free(cd->basedir); + + if (cd->datadir != NULL) + free(cd->datadir); + + if (cd->xfrdir != NULL) + free(cd->xfrdir); + + /* free config data memory */ + free(cd); +} + +/* + * Return the version of the API + */ +int +dlz_version(unsigned int *flags) { + UNUSED(flags); + return (DLZ_DLOPEN_VERSION); +} + +/* + * Register a helper function from the bind9 dlz_dlopen driver + */ +static void +b9_add_helper(struct config_data *cd, const char *helper_name, void *ptr) { + if (strcmp(helper_name, "log") == 0) + cd->log = (log_t *)ptr; + if (strcmp(helper_name, "putrr") == 0) + cd->putrr = (dns_sdlz_putrr_t *)ptr; + if (strcmp(helper_name, "putnamedrr") == 0) + cd->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr; + if (strcmp(helper_name, "writeable_zone") == 0) + cd->writeable_zone = (dns_dlz_writeablezone_t *)ptr; +} diff --git a/contrib/dlz/modules/include/dlz_dbi.h b/contrib/dlz/modules/include/dlz_dbi.h new file mode 100644 index 0000000000..5123927c4f --- /dev/null +++ b/contrib/dlz/modules/include/dlz_dbi.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#ifndef DLZ_DBI_H +#define DLZ_DBI_H 1 + +/* + * Types + */ +#define REQUIRE_CLIENT 0x01 +#define REQUIRE_QUERY 0x02 +#define REQUIRE_RECORD 0x04 +#define REQUIRE_ZONE 0x08 + +typedef struct query_segment query_segment_t; +typedef DLZ_LIST(query_segment_t) query_list_t; +typedef struct dbinstance dbinstance_t; +typedef DLZ_LIST(dbinstance_t) db_list_t; +typedef struct driverinstance driverinstance_t; + +/*% + * a query segment is all the text between our special tokens + * special tokens are %zone%, %record%, %client% + */ +struct query_segment { + void *cmd; + unsigned int strlen; + isc_boolean_t direct; + DLZ_LINK(query_segment_t) link; +}; + +/*% + * a database instance contains everything we need for running + * a query against the database. Using it each separate thread + * can dynamically construct a query and execute it against the + * database. The "instance_lock" and locking code in the driver's + * make sure no two threads try to use the same DBI at a time. + */ +struct dbinstance { + void *dbconn; + query_list_t *allnodes_q; + query_list_t *allowxfr_q; + query_list_t *authority_q; + query_list_t *findzone_q; + query_list_t *lookup_q; + query_list_t *countzone_q; + char *query_buf; + char *zone; + char *record; + char *client; + dlz_mutex_t lock; + DLZ_LINK(dbinstance_t) link; +}; + +/* + * Method declarations + */ + +void +destroy_querylist(query_list_t **querylist); + +isc_result_t +build_querylist(const char *query_str, char **zone, char **record, + char **client, query_list_t **querylist, unsigned int flags, + log_t log); + +char * +build_querystring(query_list_t *querylist); + +isc_result_t +build_dbinstance(const char *allnodes_str, const char *allowxfr_str, + const char *authority_str, const char *findzone_str, + const char *lookup_str, const char *countzone_str, + dbinstance_t **dbi, log_t log); + +void +destroy_dbinstance(dbinstance_t *dbi); + +char * +get_parameter_value(const char *input, const char* key); + +#endif /* DLZ_DBI_H */ diff --git a/contrib/dlz/modules/include/dlz_list.h b/contrib/dlz/modules/include/dlz_list.h new file mode 100644 index 0000000000..50e3dec399 --- /dev/null +++ b/contrib/dlz/modules/include/dlz_list.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2004, 2006, 2007, 2011-2013 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 1997-2002 Internet Software Consortium. + * + * Permission to use, copy, modify, and/or 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DLZ_LIST_H +#define DLZ_LIST_H 1 + +#define DLZ_LIST(type) struct { type *head, *tail; } +#define DLZ_LIST_INIT(list) \ + do { (list).head = NULL; (list).tail = NULL; } while (0) + +#define DLZ_LINK(type) struct { type *prev, *next; } +#define DLZ_LINK_INIT(elt, link) \ + do { \ + (elt)->link.prev = (void *)(-1); \ + (elt)->link.next = (void *)(-1); \ + } while (0) + +#define DLZ_LIST_HEAD(list) ((list).head) +#define DLZ_LIST_TAIL(list) ((list).tail) + +#define DLZ_LIST_APPEND(list, elt, link) \ + do { \ + if ((list).tail != NULL) \ + (list).tail->link.next = (elt); \ + else \ + (list).head = (elt); \ + (elt)->link.prev = (list).tail; \ + (elt)->link.next = NULL; \ + (list).tail = (elt); \ + } while (0) + +#define DLZ_LIST_PREV(elt, link) ((elt)->link.prev) +#define DLZ_LIST_NEXT(elt, link) ((elt)->link.next) + +#endif /* DLZ_LIST_H */ diff --git a/contrib/dlz/modules/dlz_minimal.h b/contrib/dlz/modules/include/dlz_minimal.h similarity index 93% rename from contrib/dlz/modules/dlz_minimal.h rename to contrib/dlz/modules/include/dlz_minimal.h index d90e6a7ae7..6019a1236f 100644 --- a/contrib/dlz/modules/dlz_minimal.h +++ b/contrib/dlz/modules/include/dlz_minimal.h @@ -23,6 +23,9 @@ * tree. */ +#ifndef DLZ_MINIMAL_H +#define DLZ_MINIMAL_H 1 + #include #include #ifdef ISC_PLATFORM_HAVESYSUNH @@ -45,8 +48,10 @@ typedef uint32_t dns_ttl_t; #define DLZ_DLOPEN_AGE 0 #endif -/* return this in flags to dlz_version() if thread safe */ +/* return these in flags from dlz_version() */ #define DNS_SDLZFLAG_THREADSAFE 0x00000001U +#define DNS_SDLZFLAG_RELATIVEOWNER 0x00000002U +#define DNS_SDLZFLAG_RELATIVERDATA 0x00000004U /* result codes */ #define ISC_R_SUCCESS 0 @@ -57,6 +62,9 @@ typedef uint32_t dns_ttl_t; #define ISC_R_FAILURE 25 #define ISC_R_NOTIMPLEMENTED 27 #define ISC_R_NOMORE 29 +#define ISC_R_INVALIDFILE 30 +#define ISC_R_UNEXPECTED 34 +#define ISC_R_FILENOTFOUND 38 /* boolean values */ #define ISC_TRUE 1 @@ -68,6 +76,7 @@ typedef uint32_t dns_ttl_t; #define ISC_LOG_WARNING (-3) #define ISC_LOG_ERROR (-4) #define ISC_LOG_CRITICAL (-5) +#define ISC_LOG_DEBUG(level) (level) /* other useful definitions */ #define UNUSED(x) (void)(x) @@ -196,6 +205,13 @@ dlz_lookup(const char *zone, const char *name, void *dbdata, dns_clientinfo_t *clientinfo); #endif /* DLZ_DLOPEN_VERSION */ +/* + * dlz_authority() is optional if dlz_lookup() supplies + * authority information (i.e., SOA, NS) for the dns record + */ +isc_result_t +dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup); + /* * dlz_allowzonexfr() is optional, and should be supplied if you want to * support zone transfers @@ -269,3 +285,5 @@ dlz_subrdataset(const char *name, const char *rdatastr, void *dbdata, isc_result_t dlz_delrdataset(const char *name, const char *type, void *dbdata, void *version); + +#endif /* DLZ_MINIMAL_H */ diff --git a/contrib/dlz/modules/include/dlz_pthread.h b/contrib/dlz/modules/include/dlz_pthread.h new file mode 100644 index 0000000000..993e9fc4cc --- /dev/null +++ b/contrib/dlz/modules/include/dlz_pthread.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DLZ_PTHREAD_H +#define DLZ_PTHREAD_H 1 + +#ifndef PTHREADS +#define PTHREADS 1 +#endif + +#ifdef PTHREADS +#define dlz_mutex_t pthread_mutex_t +#define dlz_mutex_init pthread_mutex_init +#define dlz_mutex_destroy pthread_mutex_destroy +#define dlz_mutex_trylock pthread_mutex_trylock +#define dlz_mutex_unlock pthread_mutex_unlock +#else /* !PTHREADS */ +#define dlz_mutex_t void +#define dlz_mutex_init(a, b) (0) +#define dlz_mutex_destroy(a) (0) +#define dlz_mutex_trylock(a) (0) +#define dlz_mutex_unlock(a) (0) +#endif + +#endif /* DLZ_PTHREAD_H */ diff --git a/contrib/dlz/modules/ldap/Makefile b/contrib/dlz/modules/ldap/Makefile new file mode 100644 index 0000000000..8926abf881 --- /dev/null +++ b/contrib/dlz/modules/ldap/Makefile @@ -0,0 +1,21 @@ +prefix = /usr +libdir = $(prefix)/lib/bind9 + +CFLAGS=-fPIC -g -I../include +LDAP_LIBS=-lldap + +all: dlz_ldap_dynamic.so + +dlz_dbi.o: ../common/dlz_dbi.c + $(CC) $(CFLAGS) -c ../common/dlz_dbi.c + +dlz_ldap_dynamic.so: dlz_ldap_dynamic.c dlz_dbi.o + $(CC) $(CFLAGS) -shared -o dlz_ldap_dynamic.so \ + dlz_ldap_dynamic.c dlz_dbi.o $(LDAP_LIBS) + +clean: + rm -f dlz_ldap_dynamic.so *.o + +install: dlz_ldap_dynamic.so + mkdir -p $(DESTDIR)$(libdir) + install dlz_ldap_dynamic.so $(DESTDIR)$(libdir) diff --git a/contrib/dlz/modules/ldap/dlz_ldap_dynamic.c b/contrib/dlz/modules/ldap/dlz_ldap_dynamic.c new file mode 100644 index 0000000000..d5d5ad1502 --- /dev/null +++ b/contrib/dlz/modules/ldap/dlz_ldap_dynamic.c @@ -0,0 +1,1205 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for BIND 9 was + * conceived and contributed by Rob Butler. + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 1999-2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and/or 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This provides the externally loadable ldap DLZ module, without + * update support + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* + * Need older API functions from ldap.h. + */ +#define LDAP_DEPRECATED 1 + +#include + +#define SIMPLE "simple" +#define KRB41 "krb41" +#define KRB42 "krb42" +#define V2 "v2" +#define V3 "v3" + +#define dbc_search_limit 30 +#define ALLNODES 1 +#define ALLOWXFR 2 +#define AUTHORITY 3 +#define FINDZONE 4 +#define LOOKUP 5 + +/*% + * Structure to hold everthing needed by this "instance" of the LDAP + * driver remember, the driver code is only loaded once, but may have + * many separate instances. + */ +typedef struct { +#if PTHREADS + db_list_t *db; /*%< handle to a list of DB */ +#else + dbinstance_t *db; /*%< handle to db */ +#endif + int method; /*%< security authentication method */ + char *user; /*%< who is authenticating */ + char *cred; /*%< password for simple authentication method */ + int protocol; /*%< LDAP communication protocol version */ + char *hosts; /*%< LDAP server hosts */ + + /* Helper functions from the dlz_dlopen driver */ + log_t *log; + dns_sdlz_putrr_t *putrr; + dns_sdlz_putnamedrr_t *putnamedrr; + dns_dlz_writeablezone_t *writeable_zone; +} ldap_instance_t; + +/* forward references */ + +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); + +void +dlz_destroy(void *dbdata); + +static void +b9_add_helper(ldap_instance_t *db, const char *helper_name, void *ptr); + +/* + * Private methods + */ + +/*% checks that the LDAP URL parameters make sense */ +static isc_result_t +ldap_checkURL(ldap_instance_t *db, char *URL, int attrCnt, const char *msg) { + isc_result_t result = ISC_R_SUCCESS; + int ldap_result; + LDAPURLDesc *ldap_url = NULL; + + if (!ldap_is_ldap_url(URL)) { + db->log(ISC_LOG_ERROR, + "%s query is not a valid LDAP URL", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + ldap_result = ldap_url_parse(URL, &ldap_url); + if (ldap_result != LDAP_SUCCESS || ldap_url == NULL) { + db->log(ISC_LOG_ERROR, "parsing %s query failed", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_count_values(ldap_url->lud_attrs) < attrCnt) { + db->log(ISC_LOG_ERROR, + "%s query must specify at least " + "%d attributes to return", msg, attrCnt); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_url->lud_host != NULL) { + db->log(ISC_LOG_ERROR, + "%s query must not specify a host", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_url->lud_port != 389) { + db->log(ISC_LOG_ERROR, + "%s query must not specify a port", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_url->lud_dn == NULL || strlen (ldap_url->lud_dn) < 1) { + db->log(ISC_LOG_ERROR, + "%s query must specify a search base", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_url->lud_exts != NULL || ldap_url->lud_crit_exts != 0) { + db->log(ISC_LOG_ERROR, + "%s uses extensions. " + "The driver does not support LDAP extensions.", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + cleanup: + if (ldap_url != NULL) + ldap_free_urldesc(ldap_url); + + return (result); +} + +/*% Connects / reconnects to LDAP server */ +static isc_result_t +ldap_connect(ldap_instance_t *dbi, dbinstance_t *dbc) { + isc_result_t result; + int ldap_result; + + /* if we have a connection, get ride of it. */ + if (dbc->dbconn != NULL) { + ldap_unbind_s((LDAP *) dbc->dbconn); + dbc->dbconn = NULL; + } + + /* now connect / reconnect. */ + + /* initialize. */ + dbc->dbconn = ldap_init(dbi->hosts, LDAP_PORT); + if (dbc->dbconn == NULL) + return (ISC_R_NOMEMORY); + + /* set protocol version. */ + ldap_result = ldap_set_option((LDAP *) dbc->dbconn, + LDAP_OPT_PROTOCOL_VERSION, + &(dbi->protocol)); + if (ldap_result != LDAP_SUCCESS) { + result = ISC_R_NOPERM; + goto cleanup; + } + + /* "bind" to server. i.e. send username / pass */ + ldap_result = ldap_bind_s((LDAP *) dbc->dbconn, dbi->user, + dbi->cred, dbi->method); + if (ldap_result != LDAP_SUCCESS) { + result = ISC_R_FAILURE; + goto cleanup; + } + + return (ISC_R_SUCCESS); + + cleanup: + + /* cleanup if failure. */ + if (dbc->dbconn != NULL) { + ldap_unbind_s((LDAP *) dbc->dbconn); + dbc->dbconn = NULL; + } + + return (result); +} + +#if PTHREADS +/*% + * Properly cleans up a list of database instances. + * This function is only used when the driver is compiled for + * multithreaded operation. + */ +static void +ldap_destroy_dblist(db_list_t *dblist) { + dbinstance_t *ndbi = NULL; + dbinstance_t *dbi = NULL; + + /* get the first DBI in the list */ + ndbi = DLZ_LIST_HEAD(*dblist); + + /* loop through the list */ + while (ndbi != NULL) { + dbi = ndbi; + /* get the next DBI in the list */ + ndbi = DLZ_LIST_NEXT(dbi, link); + /* release DB connection */ + if (dbi->dbconn != NULL) + ldap_unbind_s((LDAP *) dbi->dbconn); + /* release all memory that comprised a DBI */ + destroy_dbinstance(dbi); + } + /* release memory for the list structure */ + free(dblist); +} + +/*% + * Loops through the list of DB instances, attempting to lock + * on the mutex. If successful, the DBI is reserved for use + * and the thread can perform queries against the database. + * If the lock fails, the next one in the list is tried. + * looping continues until a lock is obtained, or until + * the list has been searched dbc_search_limit times. + * This function is only used when the driver is compiled for + * multithreaded operation. + */ +static dbinstance_t * +ldap_find_avail_conn(ldap_instance_t *ldap) { + dbinstance_t *dbi = NULL; + dbinstance_t *head; + int count = 0; + + /* get top of list */ + head = dbi = DLZ_LIST_HEAD(*ldap->db); + + /* loop through list */ + while (count < dbc_search_limit) { + /* try to lock on the mutex */ + if (dlz_mutex_trylock(&dbi->lock) == 0) + return (dbi); /* success, return the DBI for use. */ + + /* not successful, keep trying */ + dbi = DLZ_LIST_NEXT(dbi, link); + + /* check to see if we have gone to the top of the list. */ + if (dbi == NULL) { + count++; + dbi = head; + } + } + + ldap->log(ISC_LOG_INFO, + "LDAP driver unable to find available connection " + "after searching %d times", count); + return (NULL); +} +#endif /* PTHREADS */ + +static isc_result_t +ldap_process_results(ldap_instance_t *db, LDAP *dbc, LDAPMessage *msg, + char **attrs, void *ptr, isc_boolean_t allnodes) +{ + isc_result_t result = ISC_R_SUCCESS; + int i = 0; + int j; + int len; + char *attribute = NULL; + LDAPMessage *entry; + char *endp = NULL; + char *host = NULL; + char *type = NULL; + char *data = NULL; + char **vals = NULL; + int ttl; + + /* get the first entry to process */ + entry = ldap_first_entry(dbc, msg); + if (entry == NULL) { + db->log(ISC_LOG_INFO, "LDAP no entries to process."); + return (ISC_R_FAILURE); + } + + /* loop through all entries returned */ + while (entry != NULL) { + /* reset for this loop */ + ttl = 0; + len = 0; + i = 0; + attribute = attrs[i]; + + /* determine how much space we need for data string */ + for (j = 0; attrs[j] != NULL; j++) { + /* get the list of values for this attribute. */ + vals = ldap_get_values(dbc, entry, attrs[j]); + /* skip empty attributes. */ + if (vals == NULL || ldap_count_values(vals) < 1) + continue; + /* + * we only use the first value. this driver + * does not support multi-valued attributes. + */ + len = len + strlen(vals[0]) + 1; + /* free vals for next loop */ + ldap_value_free(vals); + } + + /* allocate memory for data string */ + data = malloc(len + 1); + if (data == NULL) { + db->log(ISC_LOG_ERROR, + "LDAP driver unable to allocate memory " + "while processing results"); + result = ISC_R_FAILURE; + goto cleanup; + } + + /* + * Make sure data is null termed at the beginning so + * we can check if any data was stored to it later. + */ + data[0] = '\0'; + + /* reset j to re-use below */ + j = 0; + + /* loop through the attributes in the order specified. */ + while (attribute != NULL) { + /* get the list of values for this attribute. */ + vals = ldap_get_values(dbc, entry, attribute); + + /* skip empty attributes. */ + if (vals == NULL || vals[0] == NULL) { + /* increment attibute pointer */ + attribute = attrs[++i]; + /* start loop over */ + continue; + } + + /* + * j initially = 0. Increment j each time we + * set a field that way next loop will set + * next field. + */ + switch (j) { + case 0: + j++; + /* + * convert text to int, make sure it + * worked right + */ + ttl = strtol(vals[0], &endp, 10); + if (*endp != '\0' || ttl < 0) { + db->log(ISC_LOG_ERROR, + "LDAP driver ttl must " + "be a postive number"); + goto cleanup; + } + break; + case 1: + j++; + type = strdup(vals[0]); + break; + case 2: + j++; + if (allnodes) + host = strdup(vals[0]); + else + strcpy(data, vals[0]); + break; + case 3: + j++; + if (allnodes) + strcpy(data, vals[0]); + else { + strcat(data, " "); + strcat(data, vals[0]); + } + break; + default: + strcat(data, " "); + strcat(data, vals[0]); + break; + } + + /* free values */ + ldap_value_free(vals); + vals = NULL; + + /* increment attibute pointer */ + attribute = attrs[++i]; + } + + if (type == NULL) { + db->log(ISC_LOG_ERROR, + "LDAP driver unable to retrieve DNS type"); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (strlen(data) < 1) { + db->log(ISC_LOG_ERROR, + "LDAP driver unable to retrieve DNS data"); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (allnodes && host != NULL) { + dns_sdlzallnodes_t *an = (dns_sdlzallnodes_t *) ptr; + if (strcasecmp(host, "~") == 0) + result = db->putnamedrr(an, "*", type, + ttl, data); + else + result = db->putnamedrr(an, host, type, + ttl, data); + if (result != ISC_R_SUCCESS) + db->log(ISC_LOG_ERROR, + "ldap_dynamic: putnamedrr failed " + "for \"%s %s %u %s\" (%d)", + host, type, ttl, data, result); + } else { + dns_sdlzlookup_t *lookup = (dns_sdlzlookup_t *) ptr; + result = db->putrr(lookup, type, ttl, data); + if (result != ISC_R_SUCCESS) + db->log(ISC_LOG_ERROR, + "ldap_dynamic: putrr failed " + "for \"%s %u %s\" (%s)", + type, ttl, data, result); + } + + if (result != ISC_R_SUCCESS) { + db->log(ISC_LOG_ERROR, + "LDAP driver failed " + "while sending data to BIND."); + goto cleanup; + } + + /* free memory for type, data and host for next loop */ + free(type); + type = NULL; + + free(data); + data = NULL; + + if (host != NULL) { + free(host); + host = NULL; + } + + /* get the next entry to process */ + entry = ldap_next_entry(dbc, entry); + } + + cleanup: + /* de-allocate memory */ + if (vals != NULL) + ldap_value_free(vals); + if (host != NULL) + free(host); + if (type != NULL) + free(type); + if (data != NULL) + free(data); + + return (result); +} + +/*% + * This function is the real core of the driver. Zone, record + * and client strings are passed in (or NULL is passed if the + * string is not available). The type of query we want to run + * is indicated by the query flag, and the dbdata object is passed + * passed in to. dbdata really holds either: + * 1) a list of database instances (in multithreaded mode) OR + * 2) a single database instance (in single threaded mode) + * The function will construct the query and obtain an available + * database instance (DBI). It will then run the query and hopefully + * obtain a result set. + */ +static isc_result_t +ldap_get_results(const char *zone, const char *record, + const char *client, unsigned int query, + void *dbdata, void *ptr) +{ + isc_result_t result; + ldap_instance_t *db = (ldap_instance_t *)dbdata; + dbinstance_t *dbi = NULL; + char *querystring = NULL; + LDAPURLDesc *ldap_url = NULL; + int ldap_result = 0; + LDAPMessage *ldap_msg = NULL; + int i; + int entries; + + /* get db instance / connection */ +#if PTHREADS + /* find an available DBI from the list */ + dbi = ldap_find_avail_conn(db); +#else /* PTHREADS */ + /* + * only 1 DBI - no need to lock instance lock either + * only 1 thread in the whole process, no possible contention. + */ + dbi = (dbinstance_t *)(db->db); +#endif /* PTHREADS */ + + /* if DBI is null, can't do anything else */ + if (dbi == NULL) + return (ISC_R_FAILURE); + + /* set fields */ + if (zone != NULL) { + dbi->zone = strdup(zone); + if (dbi->zone == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else + dbi->zone = NULL; + + if (record != NULL) { + dbi->record = strdup(record); + if (dbi->record == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else + dbi->record = NULL; + + if (client != NULL) { + dbi->client = strdup(client); + if (dbi->client == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else + dbi->client = NULL; + + + /* what type of query are we going to run? */ + switch (query) { + case ALLNODES: + /* + * if the query was not passed in from the config file + * then we can't run it. return not_implemented, so + * it's like the code for that operation was never + * built into the driver.... AHHH flexibility!!! + */ + if (dbi->allnodes_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } else + querystring = build_querystring(dbi->allnodes_q); + break; + case ALLOWXFR: + /* same as comments as ALLNODES */ + if (dbi->allowxfr_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } else + querystring = build_querystring(dbi->allowxfr_q); + break; + case AUTHORITY: + /* same as comments as ALLNODES */ + if (dbi->authority_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } else + querystring = build_querystring(dbi->authority_q); + break; + case FINDZONE: + /* this is required. It's the whole point of DLZ! */ + if (dbi->findzone_q == NULL) { + db->log(ISC_LOG_DEBUG(2), + "No query specified for findzone. " + "Findzone requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } else + querystring = build_querystring(dbi->findzone_q); + break; + case LOOKUP: + /* this is required. It's also a major point of DLZ! */ + if (dbi->lookup_q == NULL) { + db->log(ISC_LOG_DEBUG(2), + "No query specified for lookup. " + "Lookup requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } else + querystring = build_querystring(dbi->lookup_q); + break; + default: + /* + * this should never happen. If it does, the code is + * screwed up! + */ + db->log(ISC_LOG_ERROR, + "Incorrect query flag passed to ldap_get_results"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + /* if the querystring is null, Bummer, outta RAM. UPGRADE TIME!!! */ + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* + * output the full query string during debug so we can see + * what lame error the query has. + */ + db->log(ISC_LOG_DEBUG(1), "Query String: %s", querystring); + + /* break URL down into it's component parts, if error cleanup */ + ldap_result = ldap_url_parse(querystring, &ldap_url); + if (ldap_result != LDAP_SUCCESS || ldap_url == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + for (i = 0; i < 3; i++) { + /* + * dbi->dbconn may be null if trying to reconnect on a + * previous query failed. + */ + if (dbi->dbconn == NULL) { + db->log(ISC_LOG_INFO, + "LDAP driver attempting to re-connect"); + + result = ldap_connect((ldap_instance_t *) dbdata, dbi); + if (result != ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + continue; + } + } + + /* perform ldap search syncronously */ + ldap_result = ldap_search_s((LDAP *) dbi->dbconn, + ldap_url->lud_dn, + ldap_url->lud_scope, + ldap_url->lud_filter, + ldap_url->lud_attrs, 0, &ldap_msg); + + /* + * check return code. No such object is ok, just + * didn't find what we wanted + */ + switch (ldap_result) { + case LDAP_NO_SUCH_OBJECT: + db->log(ISC_LOG_DEBUG(1), + "No object found matching query requirements"); + result = ISC_R_NOTFOUND; + goto cleanup; + break; + case LDAP_SUCCESS: /* on success do nothing */ + result = ISC_R_SUCCESS; + i = 3; + break; + case LDAP_SERVER_DOWN: + db->log(ISC_LOG_INFO, + "LDAP driver attempting to re-connect"); + result = ldap_connect((ldap_instance_t *) dbdata, dbi); + if (result != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + break; + default: + /* + * other errors not ok. Log error message and + * get out + */ + db->log(ISC_LOG_ERROR, "LDAP error: %s", + ldap_err2string(ldap_result)); + result = ISC_R_FAILURE; + goto cleanup; + break; + } + } + + if (result != ISC_R_SUCCESS) + goto cleanup; + + switch (query) { + case ALLNODES: + result = ldap_process_results(db, (LDAP *) dbi->dbconn, + ldap_msg, ldap_url->lud_attrs, + ptr, ISC_TRUE); + break; + case AUTHORITY: + case LOOKUP: + result = ldap_process_results(db, (LDAP *) dbi->dbconn, + ldap_msg, ldap_url->lud_attrs, + ptr, ISC_FALSE); + break; + case ALLOWXFR: + entries = ldap_count_entries((LDAP *) dbi->dbconn, ldap_msg); + if (entries == 0) + result = ISC_R_NOPERM; + else if (entries > 0) + result = ISC_R_SUCCESS; + else + result = ISC_R_FAILURE; + break; + case FINDZONE: + entries = ldap_count_entries((LDAP *) dbi->dbconn, ldap_msg); + if (entries == 0) + result = ISC_R_NOTFOUND; + else if (entries > 0) + result = ISC_R_SUCCESS; + else + result = ISC_R_FAILURE; + break; + default: + /* + * this should never happen. If it does, the code is + * screwed up! + */ + db->log(ISC_LOG_ERROR, + "Incorrect query flag passed to ldap_get_results"); + result = ISC_R_UNEXPECTED; + } + + cleanup: + /* it's always good to cleanup after yourself */ + + /* if we retrieved results, free them */ + if (ldap_msg != NULL) + ldap_msgfree(ldap_msg); + + if (ldap_url != NULL) + ldap_free_urldesc(ldap_url); + + /* cleanup */ + if (dbi->zone != NULL) + free(dbi->zone); + if (dbi->record != NULL) + free(dbi->record); + if (dbi->client != NULL) + free(dbi->client); + dbi->zone = dbi->record = dbi->client = NULL; + + /* release the lock so another thread can use this dbi */ + (void) dlz_mutex_unlock(&dbi->lock); + + /* release query string */ + if (querystring != NULL) + free(querystring); + + /* return result */ + return (result); +} + +/* + * DLZ methods + */ +isc_result_t +dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { + isc_result_t result; + + /* check to see if we are authoritative for the zone first */ + result = dlz_findzonedb(dbdata, name, NULL, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* get all the zone data */ + result = ldap_get_results(name, NULL, client, ALLOWXFR, dbdata, NULL); + return (result); +} + +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) +{ + return (ldap_get_results(zone, NULL, NULL, ALLNODES, dbdata, allnodes)); +} + +isc_result_t +dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup) { + return (ldap_get_results(zone, NULL, NULL, AUTHORITY, dbdata, lookup)); +} + +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +{ + UNUSED(methods); + UNUSED(clientinfo); + return (ldap_get_results(name, NULL, NULL, FINDZONE, dbdata, NULL)); +} + +isc_result_t +dlz_lookup(const char *zone, const char *name, + void *dbdata, dns_sdlzlookup_t *lookup, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +{ + isc_result_t result; + + UNUSED(methods); + UNUSED(clientinfo); + + if (strcmp(name, "*") == 0) + result = ldap_get_results(zone, "~", NULL, LOOKUP, + dbdata, lookup); + else + result = ldap_get_results(zone, name, NULL, LOOKUP, + dbdata, lookup); + return (result); +} + + +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], + void **dbdata, ...) +{ + isc_result_t result = ISC_R_FAILURE; + ldap_instance_t *ldap = NULL; + dbinstance_t *dbi = NULL; + const char *helper_name; + int protocol; + int method; +#if PTHREADS + int dbcount; + char *endp; + int i; +#endif /* PTHREADS */ + va_list ap; + + UNUSED(dlzname); + + /* allocate memory for LDAP instance */ + ldap = calloc(1, sizeof(ldap_instance_t)); + if (ldap == NULL) + return (ISC_R_NOMEMORY); + memset(ldap, 0, sizeof(ldap_instance_t)); + + /* Fill in the helper functions */ + va_start(ap, dbdata); + while ((helper_name = va_arg(ap, const char*)) != NULL) + b9_add_helper(ldap, helper_name, va_arg(ap, void*)); + va_end(ap); + +#if PTHREADS + /* if debugging, let user know we are multithreaded. */ + ldap->log(ISC_LOG_DEBUG(1), "LDAP driver running multithreaded"); +#else /* PTHREADS */ + /* if debugging, let user know we are single threaded. */ + ldap->log(ISC_LOG_DEBUG(1), "LDAP driver running single threaded"); +#endif /* PTHREADS */ + + if (argc < 9) { + ldap->log(ISC_LOG_ERROR, + "LDAP driver requires at least " + "8 command line args."); + goto cleanup; + } + + /* no more than 13 arg's should be passed to the driver */ + if (argc > 12) { + ldap->log(ISC_LOG_ERROR, + "LDAP driver cannot accept more than " + "11 command line args."); + goto cleanup; + } + + /* determine protocol version. */ + if (strncasecmp(argv[2], V2, strlen(V2)) == 0) + protocol = 2; + else if (strncasecmp(argv[2], V3, strlen(V3)) == 0) + protocol = 3; + else { + ldap->log(ISC_LOG_ERROR, + "LDAP driver protocol must be either %s or %s", + V2, V3); + goto cleanup; + } + + /* determine connection method. */ + if (strncasecmp(argv[3], SIMPLE, strlen(SIMPLE)) == 0) + method = LDAP_AUTH_SIMPLE; + else if (strncasecmp(argv[3], KRB41, strlen(KRB41)) == 0) + method = LDAP_AUTH_KRBV41; + else if (strncasecmp(argv[3], KRB42, strlen(KRB42)) == 0) + method = LDAP_AUTH_KRBV42; + else { + ldap->log(ISC_LOG_ERROR, + "LDAP driver authentication method must be " + "one of %s, %s or %s", SIMPLE, KRB41, KRB42); + goto cleanup; + } + + /* multithreaded build can have multiple DB connections */ +#if PTHREADS + /* check how many db connections we should create */ + dbcount = strtol(argv[1], &endp, 10); + if (*endp != '\0' || dbcount < 0) { + ldap->log(ISC_LOG_ERROR, + "LDAP driver database connection count " + "must be positive."); + goto cleanup; + } +#endif + + /* check that LDAP URL parameters make sense */ + switch (argc) { + case 12: + result = ldap_checkURL(ldap, argv[11], 0, + "allow zone transfer"); + if (result != ISC_R_SUCCESS) + goto cleanup; + case 11: + result = ldap_checkURL(ldap, argv[10], 3, "all nodes"); + if (result != ISC_R_SUCCESS) + goto cleanup; + case 10: + if (strlen(argv[9]) > 0) { + result = ldap_checkURL(ldap, argv[9], 3, "authority"); + if (result != ISC_R_SUCCESS) + goto cleanup; + } + case 9: + result = ldap_checkURL(ldap, argv[8], 3, "lookup"); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = ldap_checkURL(ldap, argv[7], 0, "find zone"); + if (result != ISC_R_SUCCESS) + goto cleanup; + break; + default: + /* not really needed, should shut up compiler. */ + result = ISC_R_FAILURE; + } + + /* store info needed to automatically re-connect. */ + ldap->protocol = protocol; + ldap->method = method; + ldap->hosts = strdup(argv[6]); + if (ldap->hosts == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + ldap->user = strdup(argv[4]); + if (ldap->user == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + ldap->cred = strdup(argv[5]); + if (ldap->cred == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + +#if PTHREADS + /* allocate memory for database connection list */ + ldap->db = calloc(1, sizeof(db_list_t)); + if (ldap->db == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* initialize DB connection list */ + DLZ_LIST_INIT(*(ldap->db)); + + /* + * create the appropriate number of database instances (DBI) + * append each new DBI to the end of the list + */ + for (i = 0; i < dbcount; i++) { +#endif /* PTHREADS */ + /* how many queries were passed in from config file? */ + switch (argc) { + case 9: + result = build_dbinstance(NULL, NULL, NULL, argv[7], + argv[8], NULL, &dbi, + ldap->log); + break; + case 10: + result = build_dbinstance(NULL, NULL, argv[9], + argv[7], argv[8], + NULL, &dbi, ldap->log); + break; + case 11: + result = build_dbinstance(argv[10], NULL, argv[9], + argv[7], argv[8], + NULL, &dbi, ldap->log); + break; + case 12: + result = build_dbinstance(argv[10], argv[11], + argv[9], argv[7], + argv[8], NULL, &dbi, + ldap->log); + break; + default: + /* not really needed, should shut up compiler. */ + result = ISC_R_FAILURE; + } + + if (result == ISC_R_SUCCESS) { + ldap->log(ISC_LOG_DEBUG(2), + "LDAP driver created " + "database instance object."); + } else { /* unsuccessful?, log err msg and cleanup. */ + ldap->log(ISC_LOG_ERROR, + "LDAP driver could not create " + "database instance object."); + goto cleanup; + } + +#if PTHREADS + /* when multithreaded, build a list of DBI's */ + DLZ_LINK_INIT(dbi, link); + DLZ_LIST_APPEND(*(ldap->db), dbi, link); +#else + /* + * when single threaded, hold onto the one connection + * instance. + */ + ldap->db = dbi; +#endif + /* attempt to connect */ + result = ldap_connect(ldap, dbi); + + /* + * if db connection cannot be created, log err msg and + * cleanup. + */ + switch (result) { + /* success, do nothing */ + case ISC_R_SUCCESS: + break; + /* + * no memory means ldap_init could not + * allocate memory + */ + case ISC_R_NOMEMORY: +#if PTHREADS + ldap->log(ISC_LOG_ERROR, + "LDAP driver could not allocate memory " + "for connection number %u", i + 1); +#else + ldap->log(ISC_LOG_ERROR, + "LDAP driver could not allocate memory " + "for connection"); +#endif + goto cleanup; + /* + * no perm means ldap_set_option could not set + * protocol version + */ + case ISC_R_NOPERM: + ldap->log(ISC_LOG_ERROR, + "LDAP driver could not " + "set protocol version."); + result = ISC_R_FAILURE; + goto cleanup; + /* failure means couldn't connect to ldap server */ + case ISC_R_FAILURE: +#if PTHREADS + ldap->log(ISC_LOG_ERROR, + "LDAP driver could not bind " + "connection number %u to server.", i + 1); +#else + ldap->log(ISC_LOG_ERROR, + "LDAP driver could not " + "bind connection to server."); +#endif + goto cleanup; + /* + * default should never happen. If it does, + * major errors. + */ + default: + ldap->log(ISC_LOG_ERROR, + "dlz_create() failed (%d)", result); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + +#if PTHREADS + /* set DBI = null for next loop through. */ + dbi = NULL; + } +#endif /* PTHREADS */ + + /* set dbdata to the ldap_instance we created. */ + *dbdata = ldap; + + return (ISC_R_SUCCESS); + + cleanup: + dlz_destroy(ldap); + + return (result); +} + +void +dlz_destroy(void *dbdata) { + if (dbdata != NULL) { + ldap_instance_t *db = (ldap_instance_t *)dbdata; +#if PTHREADS + /* cleanup the list of DBI's */ + ldap_destroy_dblist((db_list_t *)(db->db)); +#else /* PTHREADS */ + if (db->db->dbconn != NULL) + ldap_unbind_s((LDAP *)(db->db->dbconn)); + + /* destroy single DB instance */ + destroy_dbinstance(db->db); +#endif /* PTHREADS */ + + if (db->hosts != NULL) + free(db->hosts); + if (db->user != NULL) + free(db->user); + if (db->cred != NULL) + free(db->cred); + free(dbdata); + } +} + +/* + * Return the version of the API + */ +int +dlz_version(unsigned int *flags) { + *flags |= DNS_SDLZFLAG_RELATIVERDATA; +#if PTHREADS + *flags |= DNS_SDLZFLAG_THREADSAFE; +#else + *flags &= ~DNS_SDLZFLAG_THREADSAFE; +#endif + return (DLZ_DLOPEN_VERSION); +} + +/* + * Register a helper function from the bind9 dlz_dlopen driver + */ +static void +b9_add_helper(ldap_instance_t *db, const char *helper_name, void *ptr) { + if (strcmp(helper_name, "log") == 0) + db->log = (log_t *)ptr; + if (strcmp(helper_name, "putrr") == 0) + db->putrr = (dns_sdlz_putrr_t *)ptr; + if (strcmp(helper_name, "putnamedrr") == 0) + db->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr; + if (strcmp(helper_name, "writeable_zone") == 0) + db->writeable_zone = (dns_dlz_writeablezone_t *)ptr; +} diff --git a/contrib/dlz/modules/ldap/testing/README b/contrib/dlz/modules/ldap/testing/README new file mode 100644 index 0000000000..69b138198e --- /dev/null +++ b/contrib/dlz/modules/ldap/testing/README @@ -0,0 +1,10 @@ +These files were used for testing on Ubuntu Linux using OpenLDAP. + +- Move aside /etc/ldap/slapd.d +- Move slapd.conf to /etc/ldap +- Move dlz.schema to /etc/ldap/schema/dlz.schema +- Run "/etc/init.d/slapd restart" +- Run "ldapadd -x -f example.ldif -D 'cn=Manager,o=bind-dlz' -w secret" + +LDAP server is now loaded with example.com data from the file example.ldif + diff --git a/contrib/dlz/modules/ldap/testing/dlz.schema b/contrib/dlz/modules/ldap/testing/dlz.schema new file mode 100644 index 0000000000..6c79ab235d --- /dev/null +++ b/contrib/dlz/modules/ldap/testing/dlz.schema @@ -0,0 +1,187 @@ +# +# +# 1.3.6.1.4.1.18420.1.1.X is reserved for attribute types declared by the DLZ project. +# 1.3.6.1.4.1.18420.1.2.X is reserved for object classes declared by the DLZ project. +# 1.3.6.1.4.1.18420.1.3.X is reserved for PRIVATE extensions to the DLZ attribute +# types and object classes that may be needed by end users +# to add security, etc. Attributes and object classes using +# this OID MUST NOT be published outside of an organization +# except to offer them for consideration to become part of the +# standard attributes and object classes published by the DLZ project. + +attributetype ( 1.3.6.1.4.1.18420.1.1.10 + NAME 'dlzZoneName' + DESC 'DNS zone name - domain name not including host name' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.20 + NAME 'dlzHostName' + DESC 'Host portion of a domain name' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.30 + NAME 'dlzData' + DESC 'Data for the resource record' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.40 + NAME 'dlzType' + DESC 'DNS record type - A, SOA, NS, MX, etc...' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.50 + NAME 'dlzSerial' + DESC 'SOA record serial number' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.60 + NAME 'dlzRefresh' + DESC 'SOA record refresh time in seconds' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.70 + NAME 'dlzRetry' + DESC 'SOA retry time in seconds' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.80 + NAME 'dlzExpire' + DESC 'SOA expire time in seconds' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.90 + NAME 'dlzMinimum' + DESC 'SOA minimum time in seconds' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.100 + NAME 'dlzAdminEmail' + DESC 'E-mail address of person responsible for this zone - @ should be replaced with . (period)' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.110 + NAME 'dlzPrimaryNS' + DESC 'Primary name server for this zone - should be host name not IP address' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.120 + NAME 'dlzIPAddr' + DESC 'IP address - IPV4 should be in dot notation xxx.xxx.xxx.xxx IPV6 should be in colon notation xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx' + EQUALITY caseExactIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{40} + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.130 + NAME 'dlzCName' + DESC 'DNS cname' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.140 + NAME 'dlzPreference' + DESC 'DNS MX record preference. Lower numbers have higher preference' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.150 + NAME 'dlzTTL' + DESC 'DNS time to live - how long this record can be cached by caching DNS servers' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.160 + NAME 'dlzRecordID' + DESC 'Unique ID for each DLZ resource record' + SUP name + SINGLE-VALUE ) + +#------------------------------------------------------------------------------ +# Object class definitions +#------------------------------------------------------------------------------ + +objectclass ( 1.3.6.1.4.1.18420.1.2.10 + NAME 'dlzZone' + DESC 'Zone name portion of a domain name' + SUP top STRUCTURAL + MUST ( objectclass $ dlzZoneName ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.20 + NAME 'dlzHost' + DESC 'Host name portion of a domain name' + SUP top STRUCTURAL + MUST ( objectclass $ dlzHostName ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.30 + NAME 'dlzAbstractRecord' + DESC 'Data common to all DNS record types' + SUP top ABSTRACT + MUST ( objectclass $ dlzRecordID $ dlzHostName $ dlzType $ dlzTTL ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.40 + NAME 'dlzGenericRecord' + DESC 'Generic DNS record - useful when a specific object class has not been defined for a DNS record' + SUP dlzAbstractRecord STRUCTURAL + MUST ( dlzData ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.50 + NAME 'dlzARecord' + DESC 'DNS A record' + SUP dlzAbstractrecord STRUCTURAL + MUST ( dlzIPAddr ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.60 + NAME 'dlzNSRecord' + DESC 'DNS NS record' + SUP dlzGenericRecord STRUCTURAL ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.70 + NAME 'dlzMXRecord' + DESC 'DNS MX record' + SUP dlzGenericRecord STRUCTURAL + MUST ( dlzPreference ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.80 + NAME 'dlzSOARecord' + DESC 'DNS SOA record' + SUP dlzAbstractRecord STRUCTURAL + MUST ( dlzSerial $ dlzRefresh $ dlzRetry + $ dlzExpire $ dlzMinimum $ dlzAdminEmail $ dlzPrimaryNS ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.90 + NAME 'dlzTextRecord' + DESC 'Text data with spaces should be wrapped in double quotes' + SUP dlzGenericRecord STRUCTURAL ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.100 + NAME 'dlzPTRRecord' + DESC 'DNS PTR record' + SUP dlzGenericRecord STRUCTURAL ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.110 + NAME 'dlzCNameRecord' + DESC 'DNS CName record' + SUP dlzGenericRecord STRUCTURAL ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.120 + NAME 'dlzXFR' + DESC 'Host allowed to perform zone transfer' + SUP top STRUCTURAL + MUST ( objectclass $ dlzRecordID $ dlzIPAddr ) ) diff --git a/contrib/dlz/modules/ldap/testing/example.ldif b/contrib/dlz/modules/ldap/testing/example.ldif new file mode 100644 index 0000000000..8362b1e472 --- /dev/null +++ b/contrib/dlz/modules/ldap/testing/example.ldif @@ -0,0 +1,168 @@ +# server suffix - o=bind-dlz + +dn: o=bind-dlz +objectclass: organization +o: bind-dlz + +dn: ou=dns,o=bind-dlz +objectclass: organizationalUnit +ou: dns + +dn: dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzZone +dlzZoneName: example.com + +dn: dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: @ + +dn: dlzHostName=www,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: www + +dn: dlzHostName=mail,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: mail + +dn: dlzHostName=backup,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: backup + +dn: dlzHostName=ns1,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: ns1 + +dn: dlzHostName=ns2,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: ns2 + +dn: dlzHostName=~,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: ~ + +dn: dlzRecordID=1,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzGenericRecord +dlzRecordID: 1 +dlzHostName: @ +dlzType: txt +dlzData: "this is a text record" +dlzTTL: 10 + +dn: dlzRecordID=2,dlzHostName=www,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 2 +dlzHostName: www +dlzType: a +dlzIPAddr: 192.168.0.1 +dlzTTL: 10 + +dn: dlzRecordID=3,dlzHostName=mail,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 3 +dlzHostName: mail +dlzType: a +dlzIPAddr: 192.168.0.2 +dlzTTL: 10 + +dn: dlzRecordID=4,dlzHostName=backup,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 4 +dlzHostName: backup +dlzType: a +dlzIPAddr: 192.168.0.3 +dlzTTL: 10 + +dn: dlzRecordID=5,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzMXRecord +dlzRecordID: 5 +dlzHostName: @ +dlzType: mx +dlzData: mail +dlzPreference: 20 +dlzTTL: 10 + +dn: dlzRecordID=6,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzMXRecord +dlzRecordID: 6 +dlzHostName: @ +dlzType: mx +dlzData: backup +dlzPreference: 40 +dlzTTL: 10 + +dn: dlzRecordID=7,dlzHostName=www,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzMXRecord +dlzRecordID: 7 +dlzHostName: www +dlzType: mx +dlzData: backup +dlzPreference: 40 +dlzTTL: 10 + +dn: dlzRecordID=8,dlzHostName=www,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzMXRecord +dlzRecordID: 8 +dlzHostName: www +dlzType: mx +dlzData: mail +dlzPreference: 20 +dlzTTL: 10 + +dn: dlzRecordID=9,dlzHostName=ns1,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 9 +dlzHostName: ns1 +dlzType: a +dlzIPAddr: 192.168.0.4 +dlzTTL: 10 + +dn: dlzRecordID=10,dlzHostName=ns2,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 10 +dlzHostName: ns2 +dlzType: a +dlzIPAddr: 192.168.0.5 +dlzTTL: 10 + +dn: dlzRecordID=11,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzSOARecord +dlzRecordID: 11 +dlzHostName: @ +dlzType: soa +dlzSerial: 2 +dlzRefresh: 2800 +dlzRetry: 7200 +dlzExpire: 604800 +dlzMinimum: 86400 +dlzAdminEmail: root.example.com. +dlzPrimaryns: ns1.example.com. +dlzTTL: 10 + +dn: dlzRecordID=12,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzNSRecord +dlzRecordID: 12 +dlzHostName: @ +dlzType: ns +dlzData: ns1.example.com. +dlzTTL: 10 + +dn: dlzRecordID=13,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzNSRecord +dlzRecordID: 13 +dlzHostName: @ +dlzType: ns +dlzData: ns2 +dlzTTL: 10 + +dn: dlzRecordID=14,dlzHostName=~,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 14 +dlzHostName: ~ +dlzType: a +dlzIPAddr: 192.168.0.250 +dlzTTL: 10 + +dn: dlzRecordID=15,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzXFR +dlzRecordID: 15 +dlzIPAddr: 127.0.0.1 diff --git a/contrib/dlz/modules/ldap/testing/named.conf b/contrib/dlz/modules/ldap/testing/named.conf new file mode 100644 index 0000000000..e79a02e48a --- /dev/null +++ b/contrib/dlz/modules/ldap/testing/named.conf @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +controls { }; + +options { + directory "."; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { any; }; + listen-on-v6 { none; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-md5; +}; + +controls { + inet 127.0.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +dlz "test" { + database "dlopen ../dlz_ldap_dynamic.so 2 + v3 simple {cn=Manager,o=bind-dlz} {secret} {127.0.0.1} + ldap:///dlzZoneName=$zone$,ou=dns,o=bind-dlz???objectclass=dlzZone + ldap:///dlzHostName=$record$,dlzZoneName=$zone$,ou=dns,o=bind-dlz?dlzTTL,dlzType,dlzPreference,dlzData,dlzIPAddr?sub?(&(objectclass=dlzAbstractRecord)(!(dlzType=soa))) + ldap:///dlzHostName=@,dlzZoneName=$zone$,ou=dns,o=bind-dlz?dlzTTL,dlzType,dlzData,dlzPrimaryNS,dlzAdminEmail,dlzSerial,dlzRefresh,dlzRetry,dlzExpire,dlzMinimum?sub?(&(objectclass=dlzAbstractRecord)(dlzType=soa)) + ldap:///dlzZoneName=$zone$,ou=dns,o=bind-dlz?dlzTTL,dlzType,dlzHostName,dlzPreference,dlzData,dlzIPAddr,dlzPrimaryNS,dlzAdminEmail,dlzSerial,dlzRefresh,dlzRetry,dlzExpire,dlzMinimum?sub?(&(objectclass=dlzAbstractRecord)(!(dlzType=soa))) + ldap:///dlzZoneName=$zone$,ou=dns,o=bind-dlz??sub?(&(objectclass=dlzXFR)(dlzIPAddr=$client$))"; +}; diff --git a/contrib/dlz/modules/ldap/testing/slapd.conf b/contrib/dlz/modules/ldap/testing/slapd.conf new file mode 100644 index 0000000000..14c8ffb02a --- /dev/null +++ b/contrib/dlz/modules/ldap/testing/slapd.conf @@ -0,0 +1,44 @@ +# this is the full path to the core.schema +include /etc/ldap/schema/core.schema + +# this is the full path to the dlz.schema +include /etc/ldap/schema/dlz.schema + +# these files hold the slapd process ID and program args when +# slapd is started. +pidfile /var/run/slapd/slapd.pid +argsfile /var/run/slapd/slapd.args + +modulepath /usr/lib/ldap +moduleload back_hdb + +# this allows ldap version 2 connections. You should comment +# it out if you don't need ldap version 2. +allow bind_v2 + +# this sets up the Berkeley DB database backend for LDAP to use. +database hdb + +# This is the root of the LDAP server. You still need to add +# an entry to this location via a LDIF file, or you won't be +# able to add anything else into the LDAP server. +suffix "o=bind-dlz" + +# this is the "username" you have to use when connecting to the +# ldap server to make updates. Type the whole thing exactly +# as you see it as a parameter to ldapadd. +rootdn "cn=Manager,o=bind-dlz" + +# this is the "password" you have to use when connecting to the +# ldap server to make updates. +rootpw secret + +# this is the directory that the LDAP server will create the +# Berkeley DB backend in. +directory /var/lib/ldap + +# this just adds some indexing to the LDAP server. +# probably should have more to better optimize DLZ LDAP searches. +index cn,sn,uid pres,eq +index objectClass eq + diff --git a/contrib/dlz/modules/wildcard/Makefile b/contrib/dlz/modules/wildcard/Makefile new file mode 100644 index 0000000000..20a5d4ee55 --- /dev/null +++ b/contrib/dlz/modules/wildcard/Makefile @@ -0,0 +1,20 @@ +prefix = /usr +libdir = $(prefix)/lib/bind9 + +CFLAGS=-fPIC -g -I../include + +all: dlz_wildcard_dynamic.so + +dlz_dbi.o: ../common/dlz_dbi.c + $(CC) $(CFLAGS) -c ../common/dlz_dbi.c + +dlz_wildcard_dynamic.so: dlz_wildcard_dynamic.c dlz_dbi.o + $(CC) $(CFLAGS) -shared -o dlz_wildcard_dynamic.so \ + dlz_wildcard_dynamic.c dlz_dbi.o + +clean: + rm -f dlz_wildcard_dynamic.so *.o + +install: dlz_wildcard_dynamic.so + mkdir -p $(DESTDIR)$(libdir) + install dlz_wildcard_dynamic.so $(DESTDIR)$(libdir) diff --git a/contrib/dlz/modules/wildcard/README b/contrib/dlz/modules/wildcard/README new file mode 100644 index 0000000000..b19009bed3 --- /dev/null +++ b/contrib/dlz/modules/wildcard/README @@ -0,0 +1,31 @@ +The "wildcard" DLZ module provides a "template" zone for domains matching +a wildcard name. For example, the following DLZ configuration would match +any zone name containing the string "example" and ending with .com, such +as "thisexample.com", "exampleofthat.com", or "anexampleoftheotherthing.com". + + dlz "test" { + database "dlopen ../dlz_wildcard_dynamic.so + *example*.com 10.53.* 1800 + @ 3600 SOA {ns3.example.nil. support.example.nil. 42 14400 7200 2592000 600} + @ 3600 NS ns3.example.nil. + @ 3600 NS ns4.example.nil. + @ 3600 NS ns8.example.nil. + @ 3600 MX {5 mail.example.nil.} + ftp 86400 A 192.0.0.1 + sql 86400 A 192.0.0.2 + tmp {} A 192.0.0.3 + www 86400 A 192.0.0.3 + www 86400 AAAA ::1 + txt 300 TXT {\"you requested $record$ in $zone$\"} + * 86400 A 192.0.0.100"; + }; + +For any zone name matchin the wildcard, it would return the data from +the template. "$zone$" is replaced with zone name: i.e., the shortest +possible string of labels in the query name that matches the wildcard. +"$record$" is replaced with the remainder of the query name. In the +example above, a query for "txt.thisexample.com/TXT" would return the +string "you requested txt in thisexample.com". + +Any client whose source address matches the second wildcard ("10.53.*") +is allowed to request a zone transfer. diff --git a/contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c b/contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c new file mode 100644 index 0000000000..781ebd93ba --- /dev/null +++ b/contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c @@ -0,0 +1,738 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * Copyright (C) 2012 Vadim Goncharov, Russia, vadim_nuclight@mail.ru. + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 1999-2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and/or 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This provides the externally loadable wildcard DLZ module. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define DE_CONST(konst, var) \ + do { \ + union { const void *k; void *v; } _u; \ + _u.k = konst; \ + var = _u.v; \ + } while (0) + +/* fnmatch() return values. */ +#define FNM_NOMATCH 1 /* Match failed. */ + +/* fnmatch() flags. */ +#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */ +#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */ +#define FNM_PERIOD 0x04 /* Period must be matched by period. */ +#define FNM_LEADING_DIR 0x08 /* Ignore / after Imatch. */ +#define FNM_CASEFOLD 0x10 /* Case insensitive search. */ +#define FNM_IGNORECASE FNM_CASEFOLD +#define FNM_FILE_NAME FNM_PATHNAME + +/* + * Our data structures. + */ + +typedef struct named_rr nrr_t; +typedef DLZ_LIST(nrr_t) rr_list_t; + +typedef struct config_data { + char *zone_pattern; + char *axfr_pattern; + rr_list_t rrs_list; + char *zone; + char *record; + char *client; + + /* Helper functions from the dlz_dlopen driver */ + log_t *log; + dns_sdlz_putrr_t *putrr; + dns_sdlz_putnamedrr_t *putnamedrr; + dns_dlz_writeablezone_t *writeable_zone; +} config_data_t; + +struct named_rr { + char *name; + char *type; + int ttl; + query_list_t *data; + DLZ_LINK(nrr_t) link; +}; + +/* + * Forward references + */ +static int +rangematch(const char *, char, int, char **); + +static int +fnmatch(const char *pattern, const char *string, int flags); + +static void +b9_add_helper(struct config_data *cd, const char *helper_name, void *ptr); + +static const char * +shortest_match(const char *pattern, const char *string); + +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) { + config_data_t *cd = (config_data_t *) dbdata; + isc_result_t result; + char *querystring = NULL; + nrr_t *nrec; + int i = 0; + + DE_CONST(zone, cd->zone); + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), + "dlz_wildcard allnodes called for zone '%s'", zone); + + result = ISC_R_FAILURE; + + nrec = DLZ_LIST_HEAD(cd->rrs_list); + while (nrec != NULL) { + cd->record = nrec->name; + + querystring = build_querystring(nrec->data); + + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto done; + } + + cd->log(ISC_LOG_DEBUG(2), + "dlz_wildcard allnodes entry num %d: calling " + "putnamedrr(name=%s type=%s ttl=%d qs=%s)", + i++, nrec->name, nrec->type, nrec->ttl, querystring); + + result = cd->putnamedrr(allnodes, nrec->name, nrec->type, + nrec->ttl, querystring); + if (result != ISC_R_SUCCESS) + goto done; + + nrec = DLZ_LIST_NEXT(nrec, link); + } + +done: + cd->zone = NULL; + + if (querystring != NULL) + free(querystring); + + return (result); +} + +isc_result_t +dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { + config_data_t *cd = (config_data_t *) dbdata; + + UNUSED(name); + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), + "dlz_wildcard allowzonexfr called for client '%s'", client); + + if (fnmatch(cd->axfr_pattern, client, FNM_CASEFOLD) == 0) + return (ISC_R_SUCCESS); + else + return (ISC_R_NOTFOUND); +} + +#if DLZ_DLOPEN_VERSION < 3 +isc_result_t +dlz_findzonedb(void *dbdata, const char *name) +#else +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif +{ + config_data_t *cd = (config_data_t *) dbdata; + const char *p; + +#if DLZ_DLOPEN_VERSION >= 3 + UNUSED(methods); + UNUSED(clientinfo); +#endif + + p = shortest_match(cd->zone_pattern, name); + if (p == NULL) + return (ISC_R_NOTFOUND); + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), + "dlz_wildcard findzonedb matched '%s'", p); + + return (ISC_R_SUCCESS); +} + +#if DLZ_DLOPEN_VERSION == 1 +isc_result_t +dlz_lookup(const char *zone, const char *name, + void *dbdata, dns_sdlzlookup_t *lookup) +#else +isc_result_t +dlz_lookup(const char *zone, const char *name, + void *dbdata, dns_sdlzlookup_t *lookup, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif +{ + isc_result_t result; + config_data_t *cd = (config_data_t *) dbdata; + char *querystring = NULL; + const char *p; + char *namebuf; + nrr_t *nrec; + isc_boolean_t origin = ISC_TRUE; + +#if DLZ_DLOPEN_VERSION >= 2 + UNUSED(methods); + UNUSED(clientinfo); +#endif + + p = shortest_match(cd->zone_pattern, zone); + if (p == NULL) + return (ISC_R_NOTFOUND); + + DE_CONST(name, cd->record); + DE_CONST(p, cd->zone); + + if ((p != zone) && (strcmp(name, "@") == 0 || strcmp(name, zone) == 0)) + { + size_t len = p - zone; + namebuf = malloc(len); + strncpy(namebuf, zone, len - 1); + namebuf[len - 1] = '\0'; + cd->record = namebuf; + origin = ISC_FALSE; + } else if (p == zone) + cd->record = "@"; + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), + "dlz_wildcard_dynamic: lookup for '%s' in '%s': " + "trying '%s' in '%s'", + name, zone, cd->record, cd->zone); + + result = ISC_R_NOTFOUND; + nrec = DLZ_LIST_HEAD(cd->rrs_list); + while (nrec != NULL) { + nrr_t *next = DLZ_LIST_NEXT(nrec, link); + if (strcmp(cd->record, nrec->name) == 0) { + /* We handle authority data in dlz_authority() */ + if (strcmp(nrec->type, "SOA") == 0 || + strcmp(nrec->type, "NS") == 0) + { + nrec = next; + continue; + } + + querystring = build_querystring(nrec->data); + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto done; + } + + result = cd->putrr(lookup, nrec->type, + nrec->ttl, querystring); + if (result != ISC_R_SUCCESS) + goto done; + +fprintf(stderr, "setting result to success on %s/%s\n", nrec->name, nrec->type); + result = ISC_R_SUCCESS; + + free(querystring); + querystring = NULL; + } + nrec = next; + } + +done: + cd->zone = NULL; + cd->record = NULL; + + if (querystring != NULL) + free(querystring); + + return (result); +} + +isc_result_t +dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup) { + isc_result_t result; + config_data_t *cd = (config_data_t *) dbdata; + char *querystring = NULL; + nrr_t *nrec; + const char *p, *name = "@"; + + p = shortest_match(cd->zone_pattern, zone); + if (p == NULL) + return (ISC_R_NOTFOUND); + + DE_CONST(p, cd->zone); + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), + "dlz_wildcard_dynamic: authority for '%s'", zone); + + result = ISC_R_NOTFOUND; + nrec = DLZ_LIST_HEAD(cd->rrs_list); + while (nrec != NULL) { + isc_boolean_t origin; + if (strcmp("@", nrec->name) == 0) { + isc_result_t presult; + + querystring = build_querystring(nrec->data); + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto done; + } + + presult = cd->putrr(lookup, nrec->type, + nrec->ttl, querystring); + if (presult != ISC_R_SUCCESS) { + result = presult; + goto done; + } + + result = ISC_R_SUCCESS; + + free(querystring); + querystring = NULL; + } + nrec = DLZ_LIST_NEXT(nrec, link); + } + +done: + cd->zone = NULL; + + if (querystring != NULL) + free(querystring); + + return (result); +} + +static void +destroy_rrlist(config_data_t *cd) { + nrr_t *trec, *nrec; + + nrec = DLZ_LIST_HEAD(cd->rrs_list); + + while (nrec != NULL) { + trec = nrec; + + destroy_querylist(&trec->data); + + if (trec->name != NULL) + free(trec->name); + if (trec->type != NULL) + free(trec->type); + trec->name = trec->type = NULL; + + /* Get the next record, before we destroy this one. */ + nrec = DLZ_LIST_NEXT(nrec, link); + + free(trec); + } +} + +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], + void **dbdata, ...) +{ + config_data_t *cd; + char *endp; + int i, def_ttl; + nrr_t *trec = NULL; + isc_result_t result; + const char *helper_name; + va_list ap; + + if (argc < 8 || argc % 4 != 0) + return (ISC_R_FAILURE); + + cd = calloc(1, sizeof(config_data_t)); + if (cd == NULL) + return (ISC_R_NOMEMORY); + memset(cd, 0, sizeof(config_data_t)); + + /* Fill in the helper functions */ + va_start(ap, dbdata); + while ((helper_name = va_arg(ap, const char*)) != NULL) + b9_add_helper(cd, helper_name, va_arg(ap, void*)); + va_end(ap); + + /* + * Write info message to log + */ + cd->log(ISC_LOG_INFO, + "Loading '%s' using DLZ_wildcard driver. " + "Zone: %s, AXFR allowed for: %s, $TTL: %s", + dlzname, argv[1], argv[2], argv[3]); + + /* initialize the records list here to simplify cleanup */ + DLZ_LIST_INIT(cd->rrs_list); + + cd->zone_pattern = strdup(argv[1]); + if (cd->zone_pattern == NULL) + goto cleanup; + + cd->axfr_pattern = strdup(argv[2]); + if (cd->axfr_pattern == NULL) + goto cleanup; + + def_ttl = strtol(argv[3], &endp, 10); + if (*endp != '\0' || def_ttl < 0) { + def_ttl = 3600; + cd->log(ISC_LOG_ERROR, "default TTL invalid, using 3600"); + } + + for (i = 4; i < argc; i += 4) { + result = ISC_R_NOMEMORY; + + trec = malloc(sizeof(nrr_t)); + if (trec == NULL) + goto full_cleanup; + + memset(trec, 0, sizeof(nrr_t)); + + /* Initialize the record link */ + DLZ_LINK_INIT(trec, link); + /* Append the record to the list */ + DLZ_LIST_APPEND(cd->rrs_list, trec, link); + + trec->name = strdup(argv[i]); + if (trec->name == NULL) + goto full_cleanup; + + trec->type = strdup(argv[i + 2]); + if (trec->type == NULL) + goto full_cleanup; + + trec->ttl = strtol(argv[i + 1], &endp, 10); + if (argv[i + 1][0] == '\0' || *endp != '\0' || trec->ttl < 0) + trec->ttl = def_ttl; + + result = build_querylist(argv[i + 3], &cd->zone, + &cd->record, &cd->client, + &trec->data, 0, cd->log); + /* If unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + cd->log(ISC_LOG_ERROR, + "Could not build RR data list at argv[%d]", + i + 3); + goto full_cleanup; + } + } + + *dbdata = cd; + + return (ISC_R_SUCCESS); + +full_cleanup: + destroy_rrlist(cd); + +cleanup: + if (cd->zone_pattern != NULL) + free(cd->zone_pattern); + if (cd->axfr_pattern != NULL) + free(cd->axfr_pattern); + free(cd); + + return (result); +} + +void +dlz_destroy(void *dbdata) { + config_data_t *cd = (config_data_t *) dbdata; + + /* + * Write debugging message to log + */ + cd->log(ISC_LOG_DEBUG(2), "Unloading DLZ_wildcard driver."); + + destroy_rrlist(cd); + + free(cd->zone_pattern); + free(cd->axfr_pattern); + free(cd); +} + + +/* + * Return the version of the API + */ +int +dlz_version(unsigned int *flags) { + UNUSED(flags); + /* XXX: ok to set DNS_SDLZFLAG_THREADSAFE here? */ + return (DLZ_DLOPEN_VERSION); +} + +/* + * Register a helper function from the bind9 dlz_dlopen driver + */ +static void +b9_add_helper(struct config_data *cd, const char *helper_name, void *ptr) { + if (strcmp(helper_name, "log") == 0) + cd->log = (log_t *)ptr; + if (strcmp(helper_name, "putrr") == 0) + cd->putrr = (dns_sdlz_putrr_t *)ptr; + if (strcmp(helper_name, "putnamedrr") == 0) + cd->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr; + if (strcmp(helper_name, "writeable_zone") == 0) + cd->writeable_zone = (dns_dlz_writeablezone_t *)ptr; +} + +static const char * +shortest_match(const char *pattern, const char *string) { + const char *p = string; + if (pattern == NULL || p == NULL || *p == '\0') + return (NULL); + + p += strlen(p); + while (p-- > string) { + if (*p == '.') { + if (fnmatch(pattern, p + 1, FNM_CASEFOLD) == 0) + return (p + 1); + } + } + if (fnmatch(pattern, string, FNM_CASEFOLD) == 0) + return (string); + + return (NULL); +} + +/* + * The helper functions stolen from the FreeBSD kernel (sys/libkern/fnmatch.c). + * + * Why don't we use fnmatch(3) from libc? Because it is not thread-safe, and + * it is not thread-safe because it supports multibyte characters. But here, + * in BIND, we want to be thread-safe and don't need multibyte - DNS names are + * always ASCII. + */ +#define EOS '\0' + +#define RANGE_MATCH 1 +#define RANGE_NOMATCH 0 +#define RANGE_ERROR (-1) + +static int +fnmatch(const char *pattern, const char *string, int flags) { + const char *stringstart; + char *newp; + char c, test; + + for (stringstart = string;;) + switch (c = *pattern++) { + case EOS: + if ((flags & FNM_LEADING_DIR) && *string == '/') + return (0); + return (*string == EOS ? 0 : FNM_NOMATCH); + case '?': + if (*string == EOS) + return (FNM_NOMATCH); + if (*string == '/' && (flags & FNM_PATHNAME)) + return (FNM_NOMATCH); + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + ++string; + break; + case '*': + c = *pattern; + /* Collapse multiple stars. */ + while (c == '*') + c = *++pattern; + + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + + /* Optimize for pattern with * at end or before /. */ + if (c == EOS) + if (flags & FNM_PATHNAME) + return ((flags & FNM_LEADING_DIR) || + index(string, '/') == NULL ? + 0 : FNM_NOMATCH); + else + return (0); + else if (c == '/' && flags & FNM_PATHNAME) { + if ((string = index(string, '/')) == NULL) + return (FNM_NOMATCH); + break; + } + + /* General case, use recursion. */ + while ((test = *string) != EOS) { + if (!fnmatch(pattern, string, + flags & ~FNM_PERIOD)) + return (0); + if (test == '/' && flags & FNM_PATHNAME) + break; + ++string; + } + return (FNM_NOMATCH); + case '[': + if (*string == EOS) + return (FNM_NOMATCH); + if (*string == '/' && (flags & FNM_PATHNAME)) + return (FNM_NOMATCH); + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + + switch (rangematch(pattern, *string, flags, &newp)) { + case RANGE_ERROR: + goto norm; + case RANGE_MATCH: + pattern = newp; + break; + case RANGE_NOMATCH: + return (FNM_NOMATCH); + } + ++string; + break; + case '\\': + if (!(flags & FNM_NOESCAPE)) { + if ((c = *pattern++) == EOS) { + c = '\\'; + --pattern; + } + } + /* FALLTHROUGH */ + default: + norm: + if (c == *string) + ; + else if ((flags & FNM_CASEFOLD) && + (tolower((unsigned char)c) == + tolower((unsigned char)*string))) + ; + else + return (FNM_NOMATCH); + string++; + break; + } + /* NOTREACHED */ +} + +static int +rangematch(const char *pattern, char test, int flags, char **newp) { + int negate, ok; + char c, c2; + + /* + * A bracket expression starting with an unquoted circumflex + * character produces unspecified results (IEEE 1003.2-1992, + * 3.13.2). This implementation treats it like '!', for + * consistency with the regular expression syntax. + * J.T. Conklin (conklin@ngai.kaleida.com) + */ + if ( (negate = (*pattern == '!' || *pattern == '^')) ) + ++pattern; + + if (flags & FNM_CASEFOLD) + test = tolower((unsigned char)test); + + /* + * A right bracket shall lose its special meaning and represent + * itself in a bracket expression if it occurs first in the list. + * -- POSIX.2 2.8.3.2 + */ + ok = 0; + c = *pattern++; + do { + if (c == '\\' && !(flags & FNM_NOESCAPE)) + c = *pattern++; + if (c == EOS) + return (RANGE_ERROR); + + if (c == '/' && (flags & FNM_PATHNAME)) + return (RANGE_NOMATCH); + + if (flags & FNM_CASEFOLD) + c = tolower((unsigned char)c); + + if (*pattern == '-' + && (c2 = *(pattern+1)) != EOS && c2 != ']') { + pattern += 2; + if (c2 == '\\' && !(flags & FNM_NOESCAPE)) + c2 = *pattern++; + if (c2 == EOS) + return (RANGE_ERROR); + + if (flags & FNM_CASEFOLD) + c2 = tolower((unsigned char)c2); + + if (c <= test && test <= c2) + ok = 1; + } else if (c == test) + ok = 1; + } while ((c = *pattern++) != ']'); + + *newp = (char *)(uintptr_t)pattern; + return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH); +} diff --git a/contrib/dlz/modules/wildcard/testing/named.conf b/contrib/dlz/modules/wildcard/testing/named.conf new file mode 100644 index 0000000000..0192e18c46 --- /dev/null +++ b/contrib/dlz/modules/wildcard/testing/named.conf @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +controls { }; + +options { + directory "."; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { any; }; + listen-on-v6 { none; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-md5; +}; + +controls { + inet 127.0.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +/* + * This will match any zone name containing the string "example" and + * ending with .com, such as "thisexample.com", "exampleofthat.com", + * or "anexampleoftheotherthing.com". + */ +dlz "test" { + database "dlopen ../dlz_wildcard_dynamic.so + *example*.com 10.53.* 1800 + @ 3600 SOA {ns3.example.nil. support.example.nil. 42 14400 7200 2592000 600} + @ 3600 NS ns3.example.nil. + @ 3600 NS ns4.example.nil. + @ 3600 NS ns8.example.nil. + @ 3600 MX {5 mail.example.nil.} + ftp 86400 A 192.0.0.1 + sql 86400 A 192.0.0.2 + tmp {} A 192.0.0.3 + www 86400 A 192.0.0.3 + www 86400 AAAA ::1 + txt 300 TXT {\"you requested $record$ in $zone$\"} + * 86400 A 192.0.0.100"; +}; diff --git a/lib/dns/sdlz.c b/lib/dns/sdlz.c index 9197f923ef..dcf39f7a0e 100644 --- a/lib/dns/sdlz.c +++ b/lib/dns/sdlz.c @@ -1205,7 +1205,6 @@ settask(dns_db_t *db, isc_task_t *task) { UNUSED(task); } - /* * getoriginnode() is used by the update code to find the * dns_rdatatype_dnskey record for a zone @@ -1222,7 +1221,7 @@ getoriginnode(dns_db_t *db, dns_dbnode_t **nodep) { result = findnodeext(db, &sdlz->common.origin, ISC_FALSE, NULL, NULL, nodep); if (result != ISC_R_SUCCESS) - sdlz_log(ISC_LOG_ERROR, "sdlz getoriginnode failed : %s", + sdlz_log(ISC_LOG_ERROR, "sdlz getoriginnode failed: %s", isc_result_totext(result)); return (result); }