diff options
Diffstat (limited to '')
-rw-r--r-- | bin/tests/system/dlzexternal/Makefile.in | 46 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/clean.sh | 23 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/driver.c | 830 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/driver.h | 30 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/ns1/dlzs.conf.in | 33 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/ns1/named.conf.in | 51 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/ns1/root.db | 24 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/prereq.sh | 19 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/setup.sh | 19 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/tests.sh | 210 |
10 files changed, 1285 insertions, 0 deletions
diff --git a/bin/tests/system/dlzexternal/Makefile.in b/bin/tests/system/dlzexternal/Makefile.in new file mode 100644 index 0000000..dd04bd7 --- /dev/null +++ b/bin/tests/system/dlzexternal/Makefile.in @@ -0,0 +1,46 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +VERSION=@BIND9_VERSION@ + +@BIND9_MAKE_INCLUDES@ + +CINCLUDES = ${DNS_INCLUDES} ${ISC_INCLUDES} @DST_OPENSSL_INC@ +CDEFINES = @CRYPTO@ +CWARNINGS = + +LIBS = @LIBS@ + +SO_TARGETS = driver.@SO@ +TARGETS = @SO_TARGETS@ + +SRCS = driver.c + +SO_OBJS = driver.@O@ +SO_SRCS = driver.c + +OBJS = + +@BIND9_MAKE_RULES@ + +CFLAGS = @CFLAGS@ @SO_CFLAGS@ +SO_LDFLAGS = @LDFLAGS@ @SO_LDFLAGS@ + +driver.@SO@: ${SO_OBJS} + ${LIBTOOL_MODE_LINK} @SO_LD@ ${SO_LDFLAGS} -o $@ driver.@O@ + +clean distclean:: + rm -f ${TARGETS} + +distclean:: + rm -f ns1/named.conf diff --git a/bin/tests/system/dlzexternal/clean.sh b/bin/tests/system/dlzexternal/clean.sh new file mode 100644 index 0000000..3e6cb9e --- /dev/null +++ b/bin/tests/system/dlzexternal/clean.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# +# Clean up after dlzexternal tests. +# + +rm -f ns1/update.txt +rm -f */named.memstats +rm -f */named.conf +rm -f */named.run +rm -f ns1/ddns.key +rm -f dig.out* +rm -f ns*/named.lock +rm -f ns1/session.key diff --git a/bin/tests/system/dlzexternal/driver.c b/bin/tests/system/dlzexternal/driver.c new file mode 100644 index 0000000..4a08dd6 --- /dev/null +++ b/bin/tests/system/dlzexternal/driver.c @@ -0,0 +1,830 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * This provides a very simple example of an external loadable DLZ + * driver, with update support. + */ + +#include <config.h> + +#include <stdbool.h> +#include <stdio.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdarg.h> + +#include <isc/log.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/types.h> +#include <isc/util.h> + +#include <dns/types.h> +#include <dns/dlz_dlopen.h> + +#include "driver.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 + +#define CHECK(x) \ + do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +/* For this simple example, use fixed sized strings */ +struct record { + char name[100]; + char type[10]; + char data[200]; + dns_ttl_t ttl; +}; + +#define MAX_RECORDS 100 + +typedef void log_t(int level, const char *fmt, ...); + +struct dlz_example_data { + char *zone_name; + + /* An example driver doesn't need good memory management :-) */ + struct record current[MAX_RECORDS]; + struct record adds[MAX_RECORDS]; + struct record deletes[MAX_RECORDS]; + + bool transaction_started; + + /* 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; +}; + +static bool +single_valued(const char *type) { + const char *single[] = { "soa", "cname", NULL }; + int i; + + for (i = 0; single[i]; i++) { + if (strcasecmp(single[i], type) == 0) { + return (true); + } + } + return (false); +} + +/* + * Add a record to a list + */ +static isc_result_t +add_name(struct dlz_example_data *state, struct record *list, + const char *name, const char *type, dns_ttl_t ttl, const char *data) +{ + int i; + bool single = single_valued(type); + int first_empty = -1; + + for (i = 0; i < MAX_RECORDS; i++) { + if (first_empty == -1 && strlen(list[i].name) == 0U) { + first_empty = i; + } + if (strcasecmp(list[i].name, name) != 0) + continue; + if (strcasecmp(list[i].type, type) != 0) + continue; + if (!single && strcasecmp(list[i].data, data) != 0) + continue; + break; + } + if (i == MAX_RECORDS && first_empty != -1) { + i = first_empty; + } + if (i == MAX_RECORDS) { + if (state->log != NULL) + state->log(ISC_LOG_ERROR, + "dlz_example: out of record space"); + return (ISC_R_FAILURE); + } + + if (strlen(name) >= sizeof(list[i].name) || + strlen(type) >= sizeof(list[i].type) || + strlen(data) >= sizeof(list[i].data)) + return (ISC_R_NOSPACE); + + strncpy(list[i].name, name, sizeof(list[i].name)); + list[i].name[sizeof(list[i].name) - 1] = '\0'; + + strncpy(list[i].type, type, sizeof(list[i].type)); + list[i].type[sizeof(list[i].type) - 1] = '\0'; + + strncpy(list[i].data, data, sizeof(list[i].data)); + list[i].data[sizeof(list[i].data) - 1] = '\0'; + + list[i].ttl = ttl; + + return (ISC_R_SUCCESS); +} + +/* + * Delete a record from a list + */ +static isc_result_t +del_name(struct dlz_example_data *state, struct record *list, + const char *name, const char *type, dns_ttl_t ttl, + const char *data) +{ + int i; + + UNUSED(state); + + for (i = 0; i < MAX_RECORDS; i++) { + if (strcasecmp(name, list[i].name) == 0 && + strcasecmp(type, list[i].type) == 0 && + strcasecmp(data, list[i].data) == 0 && + ttl == list[i].ttl) { + break; + } + } + if (i == MAX_RECORDS) { + return (ISC_R_NOTFOUND); + } + memset(&list[i], 0, sizeof(struct record)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +fmt_address(isc_sockaddr_t *addr, char *buffer, size_t size) { + char addr_buf[100]; + const char *ret; + uint16_t port = 0; + + switch (addr->type.sa.sa_family) { + case AF_INET: + port = ntohs(addr->type.sin.sin_port); + ret = inet_ntop(AF_INET, &addr->type.sin.sin_addr, addr_buf, + sizeof(addr_buf)); + break; + case AF_INET6: + port = ntohs(addr->type.sin6.sin6_port); + ret = inet_ntop(AF_INET6, &addr->type.sin6.sin6_addr, addr_buf, + sizeof(addr_buf)); + break; + default: + return (ISC_R_FAILURE); + } + + if (ret == NULL) + return (ISC_R_FAILURE); + + snprintf(buffer, size, "%s#%u", addr_buf, port); + return (ISC_R_SUCCESS); +} + +/* + * Return the version of the API + */ +int +dlz_version(unsigned int *flags) { + UNUSED(flags); + return (DLZ_DLOPEN_VERSION); +} + +/* + * Remember a helper function from the bind9 dlz_dlopen driver + */ +static void +b9_add_helper(struct dlz_example_data *state, + const char *helper_name, void *ptr) +{ + if (strcmp(helper_name, "log") == 0) + state->log = (log_t *)ptr; + if (strcmp(helper_name, "putrr") == 0) + state->putrr = (dns_sdlz_putrr_t *)ptr; + if (strcmp(helper_name, "putnamedrr") == 0) + state->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr; + if (strcmp(helper_name, "writeable_zone") == 0) + state->writeable_zone = (dns_dlz_writeablezone_t *)ptr; +} + +/* + * Called to initialize the driver + */ +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], + void **dbdata, ...) +{ + struct dlz_example_data *state; + const char *helper_name; + va_list ap; + char soa_data[1024]; + const char *extra; + isc_result_t result; + int n; + + UNUSED(dlzname); + + state = calloc(1, sizeof(struct dlz_example_data)); + if (state == 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(state, helper_name, va_arg(ap, void *)); + } + va_end(ap); + + if (argc < 2 || argv[1][0] == '\0') { + if (state->log != NULL) + state->log(ISC_LOG_ERROR, + "dlz_example: please specify a zone name"); + dlz_destroy(state); + return (ISC_R_FAILURE); + } + + /* Ensure zone name is absolute */ + state->zone_name = malloc(strlen(argv[1]) + 2); + if (state->zone_name == NULL) { + free(state); + return (ISC_R_NOMEMORY); + } + if (argv[1][strlen(argv[1]) - 1] == '.') + strcpy(state->zone_name, argv[1]); + else + sprintf(state->zone_name, "%s.", argv[1]); + + if (strcmp(state->zone_name, ".") == 0) + extra = ".root"; + else + extra = "."; + + n = sprintf(soa_data, "%s hostmaster%s%s 123 900 600 86400 3600", + state->zone_name, extra, state->zone_name); + + if (n < 0) + CHECK(ISC_R_FAILURE); + if ((unsigned)n >= sizeof(soa_data)) + CHECK(ISC_R_NOSPACE); + + add_name(state, &state->current[0], state->zone_name, + "soa", 3600, soa_data); + add_name(state, &state->current[0], state->zone_name, + "ns", 3600, state->zone_name); + add_name(state, &state->current[0], state->zone_name, + "a", 1800, "10.53.0.1"); + + if (state->log != NULL) + state->log(ISC_LOG_INFO, "dlz_example: started for zone %s", + state->zone_name); + + *dbdata = state; + return (ISC_R_SUCCESS); + + failure: + free(state); + return (result); + +} + +/* + * Shut down the backend + */ +void +dlz_destroy(void *dbdata) { + struct dlz_example_data *state = (struct dlz_example_data *)dbdata; + + if (state->log != NULL) + state->log(ISC_LOG_INFO, + "dlz_example: shutting down zone %s", + state->zone_name); + free(state->zone_name); + free(state); +} + +/* + * See if we handle a given zone + */ +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +{ + struct dlz_example_data *state = (struct dlz_example_data *)dbdata; + isc_sockaddr_t *src; + char addrbuf[100]; + char absolute[1024]; + + strcpy(addrbuf, "unknown"); + if (methods != NULL && + methods->sourceip != NULL && + methods->version - methods->age <= DNS_CLIENTINFOMETHODS_VERSION && + DNS_CLIENTINFOMETHODS_VERSION <= methods->version) + { + methods->sourceip(clientinfo, &src); + fmt_address(src, addrbuf, sizeof(addrbuf)); + } + + state->log(ISC_LOG_INFO, + "dlz_example: dlz_findzonedb called with name '%s' " + "in zone DB '%s' from %s", + name, state->zone_name, addrbuf); + + /* + * Returning ISC_R_NOTFOUND will cause the query logic to + * check the database for parent names, looking for zone cuts. + * + * Returning ISC_R_NOMORE prevents the query logic from doing + * this; it will move onto the next database after a single query. + */ + if (strcasecmp(name, "test.example.com") == 0) + return (ISC_R_NOMORE); + + /* + * For example.net, only return ISC_R_NOMORE when queried + * from 10.53.0.1. + */ + if (strcasecmp(name, "test.example.net") == 0 && + strncmp(addrbuf, "10.53.0.1", 9) == 0) + return (ISC_R_NOMORE); + + /* + * For bigcname.domain, return success so it appears to be + * the zone origin; this regression tests a bug in which + * zone origin nodes could fail to return SERVFAIL to the client. + */ + if (strcasecmp(name, "bigcname.domain") == 0) + return (ISC_R_SUCCESS); + + /* + * Return success if we have an exact match between the + * zone name and the qname + */ + if (strcasecmp(state->zone_name, name) == 0) + return (ISC_R_SUCCESS); + + snprintf(absolute, sizeof(absolute), "%s.", name); + if (strcasecmp(state->zone_name, absolute) == 0) + return (ISC_R_SUCCESS); + + return (ISC_R_NOTFOUND); +} + +/* + * Look up one record in the sample database. + * + * If the queryname is "source-addr", send back a TXT record containing + * the address of the client, to test the use of 'methods' and 'clientinfo' + * + * If the queryname is "too-long", send back a TXT record that's too long + * to process; this should result in a SERVFAIL when queried. + */ +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; + struct dlz_example_data *state = (struct dlz_example_data *)dbdata; + bool found = false; + void *dbversion = NULL; + isc_sockaddr_t *src; + char full_name[256]; + char buf[512]; + static char last[256]; + static int count = 0; + int i, size; + + UNUSED(zone); + + if (state->putrr == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + if (strcmp(name, "@") == 0) { + size = snprintf(full_name, sizeof(full_name), + "%s", state->zone_name); + } else if (strcmp(state->zone_name, ".") == 0) { + size = snprintf(full_name, sizeof(full_name), + "%s.", name); + } else { + size = snprintf(full_name, sizeof(full_name), + "%s.%s", name, state->zone_name); + } + + if (size < 0 || + (size_t)size >= sizeof(full_name) || + (size_t)size >= sizeof(last)) + { + return (ISC_R_NOSPACE); + } + + /* + * For test purposes, log all calls to dlz_lookup() + */ + if (strcasecmp(full_name, last) == 0) { + count++; + } else { + count = 1; + memcpy(last, full_name, size + 1); + } + state->log(ISC_LOG_INFO, "lookup #%d for %s", count, full_name); + + /* + * If we need to know the database version (as set in + * the 'newversion' dlz function) we can pick it up from the + * clientinfo. + * + * This allows a lookup to query the correct version of the DNS + * data, if the DLZ can differentiate between versions. + * + * For example, if a new database transaction is created by + * 'newversion', the lookup should query within the same + * transaction scope if it can. + * + * If the DLZ only operates on 'live' data, then version + * wouldn't necessarily be needed. + */ + if (clientinfo != NULL && + clientinfo->version >= DNS_CLIENTINFO_VERSION) { + dbversion = clientinfo->dbversion; + if (dbversion != NULL && *(bool *)dbversion) + state->log(ISC_LOG_INFO, + "dlz_example: lookup against live " + "transaction"); + } + + if (strcmp(name, "source-addr") == 0) { + strncpy(buf, "unknown", sizeof(buf)); + if (methods != NULL && + methods->sourceip != NULL && + (methods->version - methods->age <= + DNS_CLIENTINFOMETHODS_VERSION) && + DNS_CLIENTINFOMETHODS_VERSION <= methods->version) + { + methods->sourceip(clientinfo, &src); + fmt_address(src, buf, sizeof(buf)); + } + + state->log(ISC_LOG_INFO, + "dlz_example: lookup connection from %s", buf); + + found = true; + result = state->putrr(lookup, "TXT", 0, buf); + if (result != ISC_R_SUCCESS) + return (result); + } + + if (strcmp(name, "too-long") == 0 || + strcmp(zone, "bigcname.domain") == 0) + { + for (i = 0; i < 511; i++) + buf[i] = 'x'; + buf[i] = '\0'; + found = true; + result = state->putrr(lookup, "TXT", 0, buf); + if (result != ISC_R_SUCCESS) + return (result); + } + + /* Tests for DLZ redirection zones */ + if (strcmp(name, "*") == 0 && strcmp(zone, ".") == 0) { + result = state->putrr(lookup, "A", 0, "100.100.100.2"); + found = true; + if (result != ISC_R_SUCCESS) + return (result); + } + + if (strcmp(name, "long.name.is.not.there") == 0 && + strcmp(zone, ".") == 0) + { + result = state->putrr(lookup, "A", 0, "100.100.100.3"); + found = true; + if (result != ISC_R_SUCCESS) + return (result); + } + + /* Answer from current records */ + for (i = 0; i < MAX_RECORDS; i++) { + if (strcasecmp(state->current[i].name, full_name) == 0) { + found = true; + result = state->putrr(lookup, state->current[i].type, + state->current[i].ttl, + state->current[i].data); + if (result != ISC_R_SUCCESS) + return (result); + } + } + + if (!found) + return (ISC_R_NOTFOUND); + + return (ISC_R_SUCCESS); +} + + +/* + * See if a zone transfer is allowed + */ +isc_result_t +dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { + isc_result_t result; + + result = dlz_findzonedb(dbdata, name, NULL, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (strcmp(client, "10.53.0.5") == 0) { + return (ISC_R_NOPERM); + } + return (ISC_R_SUCCESS); +} + +/* + * Perform a zone transfer + */ +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) { + struct dlz_example_data *state = (struct dlz_example_data *)dbdata; + int i; + + UNUSED(zone); + + if (state->putnamedrr == NULL) + return (ISC_R_NOTIMPLEMENTED); + + for (i = 0; i < MAX_RECORDS; i++) { + isc_result_t result; + if (strlen(state->current[i].name) == 0U) { + continue; + } + result = state->putnamedrr(allnodes, state->current[i].name, + state->current[i].type, + state->current[i].ttl, + state->current[i].data); + if (result != ISC_R_SUCCESS) + return (result); + } + + return (ISC_R_SUCCESS); +} + + +/* + * Start a transaction + */ +isc_result_t +dlz_newversion(const char *zone, void *dbdata, void **versionp) { + struct dlz_example_data *state = (struct dlz_example_data *)dbdata; + + if (state->transaction_started) { + if (state->log != NULL) + state->log(ISC_LOG_INFO, + "dlz_example: transaction already " + "started for zone %s", zone); + return (ISC_R_FAILURE); + } + + state->transaction_started = true; + *versionp = (void *) &state->transaction_started; + + return (ISC_R_SUCCESS); +} + +/* + * End a transaction + */ +void +dlz_closeversion(const char *zone, bool commit, + void *dbdata, void **versionp) +{ + struct dlz_example_data *state = (struct dlz_example_data *)dbdata; + + if (!state->transaction_started) { + if (state->log != NULL) + state->log(ISC_LOG_INFO, "dlz_example: transaction not " + "started for zone %s", zone); + *versionp = NULL; + return; + } + + state->transaction_started = false; + + *versionp = NULL; + + if (commit) { + int i; + if (state->log != NULL) + state->log(ISC_LOG_INFO, "dlz_example: committing " + "transaction on zone %s", zone); + for (i = 0; i < MAX_RECORDS; i++) { + if (strlen(state->deletes[i].name) > 0U) { + (void)del_name(state, &state->current[0], + state->deletes[i].name, + state->deletes[i].type, + state->deletes[i].ttl, + state->deletes[i].data); + } + } + for (i = 0; i < MAX_RECORDS; i++) { + if (strlen(state->adds[i].name) > 0U) { + (void)add_name(state, &state->current[0], + state->adds[i].name, + state->adds[i].type, + state->adds[i].ttl, + state->adds[i].data); + } + } + } else { + if (state->log != NULL) + state->log(ISC_LOG_INFO, "dlz_example: cancelling " + "transaction on zone %s", zone); + } + memset(state->adds, 0, sizeof(state->adds)); + memset(state->deletes, 0, sizeof(state->deletes)); +} + + +/* + * Configure a writeable zone + */ +isc_result_t +dlz_configure(dns_view_t *view, dns_dlzdb_t *dlzdb, void *dbdata) { + struct dlz_example_data *state = (struct dlz_example_data *)dbdata; + isc_result_t result; + + if (state->log != NULL) + state->log(ISC_LOG_INFO, "dlz_example: starting configure"); + + if (state->writeable_zone == NULL) { + if (state->log != NULL) + state->log(ISC_LOG_INFO, "dlz_example: no " + "writeable_zone method available"); + return (ISC_R_FAILURE); + } + + result = state->writeable_zone(view, dlzdb, state->zone_name); + if (result != ISC_R_SUCCESS) { + if (state->log != NULL) + state->log(ISC_LOG_ERROR, "dlz_example: failed to " + "configure zone %s", state->zone_name); + return (result); + } + + if (state->log != NULL) + state->log(ISC_LOG_INFO, "dlz_example: configured writeable " + "zone %s", state->zone_name); + return (ISC_R_SUCCESS); +} + +/* + * Authorize a zone update + */ +bool +dlz_ssumatch(const char *signer, const char *name, const char *tcpaddr, + const char *type, const char *key, uint32_t keydatalen, + unsigned char *keydata, void *dbdata) +{ + struct dlz_example_data *state = (struct dlz_example_data *)dbdata; + + UNUSED(tcpaddr); + UNUSED(type); + UNUSED(key); + UNUSED(keydatalen); + UNUSED(keydata); + + if (strncmp(name, "deny.", 5) == 0) { + if (state->log != NULL) + state->log(ISC_LOG_INFO, "dlz_example: denying update " + "of name=%s by %s", name, signer); + return (false); + } + if (state->log != NULL) + state->log(ISC_LOG_INFO, "dlz_example: allowing update of " + "name=%s by %s", name, signer); + return (true); +} + + +static isc_result_t +modrdataset(struct dlz_example_data *state, const char *name, + const char *rdatastr, struct record *list) +{ + char *full_name, *dclass, *type, *data, *ttlstr, *buf; + char absolute[1024]; + isc_result_t result; +#if defined(WIN32) || defined(_REENTRANT) + char *saveptr = NULL; +#endif + + buf = strdup(rdatastr); + if (buf == NULL) + return (ISC_R_FAILURE); + + /* + * The format is: + * FULLNAME\tTTL\tDCLASS\tTYPE\tDATA + * + * The DATA field is space separated, and is in the data format + * for the type used by dig + */ + + full_name = STRTOK_R(buf, "\t", &saveptr); + if (full_name == NULL) + goto error; + + ttlstr = STRTOK_R(NULL, "\t", &saveptr); + if (ttlstr == NULL) + goto error; + + dclass = STRTOK_R(NULL, "\t", &saveptr); + if (dclass == NULL) + goto error; + + type = STRTOK_R(NULL, "\t", &saveptr); + if (type == NULL) + goto error; + + data = STRTOK_R(NULL, "\t", &saveptr); + if (data == NULL) + goto error; + + if (name[strlen(name) - 1] != '.') { + snprintf(absolute, sizeof(absolute), "%s.", name); + name = absolute; + } + + result = add_name(state, list, name, type, + strtoul(ttlstr, NULL, 10), data); + free(buf); + return (result); + + error: + free(buf); + return (ISC_R_FAILURE); +} + + +isc_result_t +dlz_addrdataset(const char *name, const char *rdatastr, + void *dbdata, void *version) +{ + struct dlz_example_data *state = (struct dlz_example_data *)dbdata; + + if (version != (void *) &state->transaction_started) + return (ISC_R_FAILURE); + + if (state->log != NULL) + state->log(ISC_LOG_INFO, "dlz_example: adding rdataset %s '%s'", + name, rdatastr); + + return (modrdataset(state, name, rdatastr, &state->adds[0])); +} + +isc_result_t +dlz_subrdataset(const char *name, const char *rdatastr, + void *dbdata, void *version) +{ + struct dlz_example_data *state = (struct dlz_example_data *)dbdata; + + if (version != (void *) &state->transaction_started) + return (ISC_R_FAILURE); + + if (state->log != NULL) + state->log(ISC_LOG_INFO, "dlz_example: subtracting rdataset " + "%s '%s'", name, rdatastr); + + return (modrdataset(state, name, rdatastr, &state->deletes[0])); +} + +isc_result_t +dlz_delrdataset(const char *name, const char *type, + void *dbdata, void *version) +{ + struct dlz_example_data *state = (struct dlz_example_data *)dbdata; + + if (version != (void *) &state->transaction_started) + return (ISC_R_FAILURE); + + if (state->log != NULL) + state->log(ISC_LOG_INFO, "dlz_example: deleting rdataset %s " + "of type %s", name, type); + + return (ISC_R_SUCCESS); +} diff --git a/bin/tests/system/dlzexternal/driver.h b/bin/tests/system/dlzexternal/driver.h new file mode 100644 index 0000000..7a0ec61 --- /dev/null +++ b/bin/tests/system/dlzexternal/driver.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +/* + * This header includes the declarations of entry points. + */ + +dlz_dlopen_version_t dlz_version; +dlz_dlopen_create_t dlz_create; +dlz_dlopen_destroy_t dlz_destroy; +dlz_dlopen_findzonedb_t dlz_findzonedb; +dlz_dlopen_lookup_t dlz_lookup; +dlz_dlopen_allowzonexfr_t dlz_allowzonexfr; +dlz_dlopen_allnodes_t dlz_allnodes; +dlz_dlopen_newversion_t dlz_newversion; +dlz_dlopen_closeversion_t dlz_closeversion; +dlz_dlopen_configure_t dlz_configure; +dlz_dlopen_ssumatch_t dlz_ssumatch; +dlz_dlopen_addrdataset_t dlz_addrdataset; +dlz_dlopen_subrdataset_t dlz_subrdataset; +dlz_dlopen_delrdataset_t dlz_delrdataset; diff --git a/bin/tests/system/dlzexternal/ns1/dlzs.conf.in b/bin/tests/system/dlzexternal/ns1/dlzs.conf.in new file mode 100644 index 0000000..d583cb4 --- /dev/null +++ b/bin/tests/system/dlzexternal/ns1/dlzs.conf.in @@ -0,0 +1,33 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +dlz "example one" { + database "dlopen ../driver.@SO@ example.nil"; +}; + +dlz "example two" { + database "dlopen ../driver.@SO@ alternate.nil"; +}; + +dlz "unsearched1" { + database "dlopen ../driver.@SO@ other.nil"; + search no; +}; + +dlz "unsearched2" { + database "dlopen ../driver.@SO@ zone.nil"; + search no; +}; + +dlz redzone { + database "dlopen ../driver.@SO@ ."; + search no; +}; diff --git a/bin/tests/system/dlzexternal/ns1/named.conf.in b/bin/tests/system/dlzexternal/ns1/named.conf.in new file mode 100644 index 0000000..d35061a --- /dev/null +++ b/bin/tests/system/dlzexternal/ns1/named.conf.in @@ -0,0 +1,51 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + query-source address 10.53.0.1; + notify-source 10.53.0.1; + transfer-source 10.53.0.1; + port @PORT@; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { 10.53.0.1; 127.0.0.1; }; + listen-on-v6 { none; }; + recursion no; + notify yes; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +include "ddns.key"; + +controls { + inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +include "dlzs.conf"; + +zone zone.nil { + type master; + dlz unsearched2; +}; + +zone "." { + type redirect; + dlz redzone; +}; + +zone "." { + type master; + file "root.db"; +}; diff --git a/bin/tests/system/dlzexternal/ns1/root.db b/bin/tests/system/dlzexternal/ns1/root.db new file mode 100644 index 0000000..45a6b04 --- /dev/null +++ b/bin/tests/system/dlzexternal/ns1/root.db @@ -0,0 +1,24 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +. IN SOA gson.nominum.com. a.root.servers.nil. ( + 2000042100 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +. NS a.root-servers.nil. +a.root-servers.nil. A 10.53.0.1 + +example. NS ns1.example. +ns1.example. A 10.53.0.1 + +exists. A 10.10.10.10 diff --git a/bin/tests/system/dlzexternal/prereq.sh b/bin/tests/system/dlzexternal/prereq.sh new file mode 100644 index 0000000..b3c73c3 --- /dev/null +++ b/bin/tests/system/dlzexternal/prereq.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +$FEATURETEST --have-dlopen || { + echo_i "dlopen() not supported - skipping dlzexternal test" + exit 255 +} +exit 0 diff --git a/bin/tests/system/dlzexternal/setup.sh b/bin/tests/system/dlzexternal/setup.sh new file mode 100644 index 0000000..e22e8aa --- /dev/null +++ b/bin/tests/system/dlzexternal/setup.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +test -r $RANDFILE || $GENRANDOM 400 $RANDFILE + +$DDNSCONFGEN -q -r $RANDFILE -z example.nil > ns1/ddns.key + +copy_setports ns1/named.conf.in ns1/named.conf diff --git a/bin/tests/system/dlzexternal/tests.sh b/bin/tests/system/dlzexternal/tests.sh new file mode 100644 index 0000000..1754aaa --- /dev/null +++ b/bin/tests/system/dlzexternal/tests.sh @@ -0,0 +1,210 @@ +#!/bin/sh +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +status=0 +n=0 + +DIGOPTS="@10.53.0.1 -p ${PORT} +nocookie" +RNDCCMD="$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p ${CONTROLPORT} -s" + +newtest() { + n=`expr $n + 1` + echo_i "${1} (${n})" + ret=0 +} + +test_update() { + host="$1" + type="$2" + cmd="$3" + digout="$4" + should_fail="$5" + + cat <<EOF > ns1/update.txt +server 10.53.0.1 ${PORT} +update add $host $cmd +send +EOF + + newtest "testing update for $host $type $cmd${comment:+ }$comment" + $NSUPDATE -k ns1/ddns.key ns1/update.txt > /dev/null 2>&1 || { + [ "$should_fail" ] || \ + echo_i "update failed for $host $type $cmd" + return 1 + } + + out=`$DIG $DIGOPTS -t $type -q $host | egrep "^$host"` + lines=`echo "$out" | grep "$digout" | wc -l` + [ $lines -eq 1 ] || { + [ "$should_fail" ] || \ + echo_i "dig output incorrect for $host $type $cmd: $out" + return 1 + } + return 0 +} + +test_update testdc1.example.nil. A "86400 A 10.53.0.10" "10.53.0.10" || ret=1 +status=`expr $status + $ret` + +test_update testdc2.example.nil. A "86400 A 10.53.0.11" "10.53.0.11" || ret=1 +status=`expr $status + $ret` + +test_update testdc3.example.nil. A "86400 A 10.53.0.10" "10.53.0.10" || ret=1 +status=`expr $status + $ret` + +test_update deny.example.nil. TXT "86400 TXT helloworld" "helloworld" should_fail && ret=1 +status=`expr $status + $ret` + +newtest "testing nxrrset" +$DIG $DIGOPTS testdc1.example.nil AAAA > dig.out.$n +grep "status: NOERROR" dig.out.$n > /dev/null || ret=1 +grep "ANSWER: 0" dig.out.$n > /dev/null || ret=1 +status=`expr $status + $ret` + +newtest "testing prerequisites are checked correctly" +cat > ns1/update.txt << EOF +server 10.53.0.1 ${PORT} +prereq nxdomain testdc3.example.nil +update add testdc3.example.nil 86500 in a 10.53.0.12 +send +EOF +$NSUPDATE -k ns1/ddns.key ns1/update.txt > /dev/null 2>&1 && ret=1 +out=`$DIG $DIGOPTS +short a testdc3.example.nil` +[ "$out" = "10.53.0.12" ] && ret=1 +[ "$ret" -eq 0 ] || echo_i "failed" +status=`expr $status + $ret` + +newtest "testing passing client info into DLZ driver" +out=`$DIG $DIGOPTS +short -t txt -q source-addr.example.nil | grep -v '^;'` +addr=`eval echo "$out" | cut -f1 -d'#'` +[ "$addr" = "10.53.0.1" ] || ret=1 +[ "$ret" -eq 0 ] || echo_i "failed" +status=`expr $status + $ret` + +newtest "testing DLZ driver is cleaned up on reload" +$RNDCCMD 10.53.0.1 reload 2>&1 | sed 's/^/ns1 /' | cat_i +for i in 0 1 2 3 4 5 6 7 8 9; do + ret=0 + grep 'dlz_example: shutting down zone example.nil' ns1/named.run > /dev/null 2>&1 || ret=1 + [ "$ret" -eq 0 ] && break + sleep 1 +done +[ "$ret" -eq 0 ] || echo_i "failed" +status=`expr $status + $ret` + +newtest "testing multiple DLZ drivers" +test_update testdc1.alternate.nil. A "86400 A 10.53.0.10" "10.53.0.10" || ret=1 +status=`expr $status + $ret` + +newtest "testing AXFR from DLZ drivers" +$DIG $DIGOPTS +noall +answer axfr example.nil > dig.out.example.ns1.test$n +lines=`cat dig.out.example.ns1.test$n | wc -l` +[ ${lines:-0} -eq 4 ] || ret=1 +$DIG $DIGOPTS +noall +answer axfr alternate.nil > dig.out.alternate.ns1.test$n +lines=`cat dig.out.alternate.ns1.test$n | wc -l` +[ ${lines:-0} -eq 5 ] || ret=1 +[ "$ret" -eq 0 ] || echo_i "failed" +status=`expr $status + $ret` + +newtest "testing AXFR denied from DLZ drivers" +$DIG $DIGOPTS -b 10.53.0.5 +noall +answer axfr example.nil > dig.out.example.ns1.test$n +grep "; Transfer failed" dig.out.example.ns1.test$n > /dev/null || ret=1 +$DIG $DIGOPTS -b 10.53.0.5 +noall +answer axfr alternate.nil > dig.out.alternate.ns1.test$n +grep "; Transfer failed" dig.out.alternate.ns1.test$n > /dev/null || ret=1 +[ "$ret" -eq 0 ] || echo_i "failed" +status=`expr $status + $ret` + +newtest "testing unsearched/unregistered DLZ zone is not found" +$DIG $DIGOPTS +noall +answer ns other.nil > dig.out.ns1.test$n +grep "3600.IN.NS.other.nil." dig.out.ns1.test$n > /dev/null && ret=1 +[ "$ret" -eq 0 ] || echo_i "failed" +status=`expr $status + $ret` + +newtest "testing unsearched/registered DLZ zone is found" +$DIG $DIGOPTS +noall +answer ns zone.nil > dig.out.ns1.test$n +grep "3600.IN.NS.zone.nil." dig.out.ns1.test$n > /dev/null || ret=1 +[ "$ret" -eq 0 ] || echo_i "failed" +status=`expr $status + $ret` + +newtest "testing unsearched/registered DLZ zone is found" +$DIG $DIGOPTS +noall +answer ns zone.nil > dig.out.ns1.test$n +grep "3600.IN.NS.zone.nil." dig.out.ns1.test$n > /dev/null || ret=1 +[ "$ret" -eq 0 ] || echo_i "failed" +status=`expr $status + $ret` + +newtest "testing correct behavior with findzone returning ISC_R_NOMORE" +$DIG $DIGOPTS +noall a test.example.com > /dev/null 2>&1 || ret=1 +# we should only find one logged lookup per searched DLZ database +lines=`grep "dlz_findzonedb.*test\.example\.com.*example.nil" ns1/named.run | wc -l` +[ $lines -eq 1 ] || ret=1 +lines=`grep "dlz_findzonedb.*test\.example\.com.*alternate.nil" ns1/named.run | wc -l` +[ $lines -eq 1 ] || ret=1 +[ "$ret" -eq 0 ] || echo_i "failed" +status=`expr $status + $ret` + +newtest "testing findzone can return different results per client" +$DIG $DIGOPTS -b 10.53.0.1 +noall a test.example.net > /dev/null 2>&1 || ret=1 +# we should only find one logged lookup per searched DLZ database +lines=`grep "dlz_findzonedb.*example\.net.*example.nil" ns1/named.run | wc -l` +[ $lines -eq 1 ] || ret=1 +lines=`grep "dlz_findzonedb.*example\.net.*alternate.nil" ns1/named.run | wc -l` +[ $lines -eq 1 ] || ret=1 +$DIG $DIGOPTS -b 10.53.0.2 +noall a test.example.net > /dev/null 2>&1 || ret=1 +# we should find several logged lookups this time +lines=`grep "dlz_findzonedb.*example\.net.*example.nil" ns1/named.run | wc -l` +[ $lines -gt 2 ] || ret=1 +lines=`grep "dlz_findzonedb.*example\.net.*alternate.nil" ns1/named.run | wc -l` +[ $lines -gt 2 ] || ret=1 +[ "$ret" -eq 0 ] || echo_i "failed" +status=`expr $status + $ret` + +newtest "testing zone returning oversized data" +$DIG $DIGOPTS txt too-long.example.nil > dig.out.ns1.test$n 2>&1 || ret=1 +grep "status: SERVFAIL" dig.out.ns1.test$n > /dev/null || ret=1 +[ "$ret" -eq 0 ] || echo_i "failed" +status=`expr $status + $ret` + +newtest "testing zone returning oversized data at zone origin" +$DIG $DIGOPTS txt bigcname.domain > dig.out.ns1.test$n 2>&1 || ret=1 +grep "status: SERVFAIL" dig.out.ns1.test$n > /dev/null || ret=1 +[ "$ret" -eq 0 ] || echo_i "failed" +status=`expr $status + $ret` + +newtest "checking redirected lookup for nonexistent name" +$DIG $DIGOPTS @10.53.0.1 unexists a > dig.out.ns1.test$n || ret=1 +grep "status: NOERROR" dig.out.ns1.test$n > /dev/null || ret=1 +grep "^unexists.*A.*100.100.100.2" dig.out.ns1.test$n > /dev/null || ret=1 +grep "flags:[^;]* aa[ ;]" dig.out.ns1.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` + +newtest "checking no redirected lookup for nonexistent type" +$DIG $DIGOPTS @10.53.0.1 exists aaaa > dig.out.ns1.test$n || ret=1 +grep "status: NOERROR" dig.out.ns1.test$n > /dev/null || ret=1 +grep "ANSWER: 0" dig.out.ns1.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` + +newtest "checking redirected lookup for a long nonexistent name" +$DIG $DIGOPTS @10.53.0.1 long.name.is.not.there a > dig.out.ns1.test$n || ret=1 +grep "status: NOERROR" dig.out.ns1.test$n > /dev/null || ret=1 +grep "^long.name.*A.*100.100.100.3" dig.out.ns1.test$n > /dev/null || ret=1 +grep "flags:[^;]* aa[ ;]" dig.out.ns1.test$n > /dev/null || ret=1 +lookups=`grep "lookup #.*\.not\.there" ns1/named.run | wc -l` +[ "$lookups" -eq 1 ] || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` + +echo_i "exit status: $status" +[ $status -eq 0 ] || exit 1 |