diff options
Diffstat (limited to '')
-rw-r--r-- | bin/tests/system/dlzexternal/Makefile.in | 49 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/clean.sh | 26 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/driver.c | 845 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/driver.h | 35 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/ns1/dlzs.conf.in | 44 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/ns1/named.conf.in | 54 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/ns1/root.db | 26 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/prereq.sh | 27 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/setup.sh | 19 | ||||
-rw-r--r-- | bin/tests/system/dlzexternal/tests.sh | 230 |
10 files changed, 1355 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..cfdda3e --- /dev/null +++ b/bin/tests/system/dlzexternal/Makefile.in @@ -0,0 +1,49 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# 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 https://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} \ + ${OPENSSL_CFLAGS} +CDEFINES = +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..2bbf75b --- /dev/null +++ b/bin/tests/system/dlzexternal/clean.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# 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 https://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 +rm -f ns*/managed-keys.bind* diff --git a/bin/tests/system/dlzexternal/driver.c b/bin/tests/system/dlzexternal/driver.c new file mode 100644 index 0000000..14f02bc --- /dev/null +++ b/bin/tests/system/dlzexternal/driver.c @@ -0,0 +1,845 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * 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 https://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 "driver.h" +#include <inttypes.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> + +#include <isc/log.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/types.h> +#include <isc/util.h> + +#include <dns/dlz_dlopen.h> +#include <dns/types.h> + +#define CHECK(x) \ + do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + +#define loginfo(...) \ + ({ \ + if ((state != NULL) && (state->log != NULL)) \ + state->log(ISC_LOG_INFO, __VA_ARGS__); \ + }) +#define logerr(...) \ + ({ \ + if ((state != NULL) && (state->log != NULL)) \ + state->log(ISC_LOG_ERROR, __VA_ARGS__); \ + }) + +/* 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++) { + INSIST(list[i].name != NULL); + 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) { + logerr("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) - 1); + list[i].name[sizeof(list[i].name) - 1] = '\0'; + + strncpy(list[i].type, type, sizeof(list[i].type) - 1); + list[i].type[sizeof(list[i].type) - 1] = '\0'; + + strncpy(list[i].data, data, sizeof(list[i].data) - 1); + 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[INET6_ADDRSTRLEN]; + 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[sizeof("@ hostmaster.root 123 900 600 86400 3600")]; + isc_result_t result; + size_t 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') { + logerr("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]); + } + + /* + * Use relative names to trigger ISC_R_NOSPACE in dns_sdlz_putrr. + */ + if (strcmp(state->zone_name, ".") == 0) { + n = strlcpy(soa_data, + "@ hostmaster.root 123 900 600 86400 3600", + sizeof(soa_data)); + } else { + n = strlcpy(soa_data, "@ hostmaster 123 900 600 86400 3600", + sizeof(soa_data)); + } + + if (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"); + + loginfo("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; + + loginfo("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)); + } + + loginfo("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); + } + loginfo("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 >= 2) { + dbversion = clientinfo->dbversion; + if (dbversion != NULL && *(bool *)dbversion) { + loginfo("dlz_example: lookup against live transaction"); + } + } + + if (strcmp(name, "source-addr") == 0) { + char ecsbuf[DNS_ECS_FORMATSIZE] = "not supported"; + 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)); + } + if (clientinfo != NULL && clientinfo->version >= 3) { + if (clientinfo->ecs.addr.family != AF_UNSPEC) { + dns_ecs_format(&clientinfo->ecs, ecsbuf, + sizeof(ecsbuf)); + } else { + snprintf(ecsbuf, sizeof(ecsbuf), "%s", + "not present"); + } + } + i = strlen(buf); + snprintf(buf + i, sizeof(buf) - i - 1, " ECS %s", ecsbuf); + + loginfo("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) { + struct dlz_example_data *state = (struct dlz_example_data *)dbdata; + isc_result_t result; + + loginfo("dlz_example: dlz_allowzonexfr called for %s", name); + + result = dlz_findzonedb(dbdata, name, NULL, NULL); + if (result != ISC_R_SUCCESS) { + loginfo("dlz_example: findzonedb returned %s", + isc_result_totext(result)); + return (result); + } + + /* + * Exception for "example.org" so we can test the use of + * the view ACL. + */ + if (strcmp(name, "example.org") == 0) { + loginfo("dlz_example: use view ACL for example.org"); + return (ISC_R_DEFAULT); + } + + /* + * Exception for 10.53.0.5 so we can test that allow-transfer + * is effective. + */ + if (strcmp(client, "10.53.0.5") == 0) { + loginfo("dlz_example: disallow transfer to 10.53.0.5"); + return (ISC_R_NOPERM); + } + + loginfo("dlz_example: transfer allowed for %s", name); + + 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) { + loginfo("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) { + loginfo("dlz_example: transaction not started for zone %s", + zone); + *versionp = NULL; + return; + } + + state->transaction_started = false; + + *versionp = NULL; + + if (commit) { + int i; + loginfo("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 { + loginfo("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; + + loginfo("dlz_example: starting configure"); + + if (state->writeable_zone == NULL) { + loginfo("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) { + loginfo("dlz_example: failed to configure zone %s", + state->zone_name); + return (result); + } + + loginfo("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) { + loginfo("dlz_example: denying update of name=%s by %s", name, + signer); + return (false); + } + loginfo("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; + char *saveptr = NULL; + + 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); + } + + loginfo("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); + } + + loginfo("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); + } + + loginfo("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..2c1a594 --- /dev/null +++ b/bin/tests/system/dlzexternal/driver.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * 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 https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +#include <dns/dlz_dlopen.h> + +/* + * 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..9166cd6 --- /dev/null +++ b/bin/tests/system/dlzexternal/ns1/dlzs.conf.in @@ -0,0 +1,44 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * 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 "example three" { + database "dlopen ../driver.@SO@ example.org"; +}; + +dlz "example four" { + // Long zone name to trigger ISC_R_NOSPACE in dns_sdlz_putrr. + database "dlopen ../driver.@SO@ 123456789.123456789.123456789.123456789.123456789.example.foo"; +}; + +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..b30ae8e --- /dev/null +++ b/bin/tests/system/dlzexternal/ns1/named.conf.in @@ -0,0 +1,54 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * 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 https://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; }; + allow-transfer { !10.53.0.1; any; }; + 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 primary; + dlz unsearched2; +}; + +zone "." { + type redirect; + dlz redzone; +}; + +zone "." { + type primary; + 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..6cbe579 --- /dev/null +++ b/bin/tests/system/dlzexternal/ns1/root.db @@ -0,0 +1,26 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; 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 https://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..9c161c2 --- /dev/null +++ b/bin/tests/system/dlzexternal/prereq.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# 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 https://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 +} + +$FEATURETEST --tsan && { + echo_i "TSAN - 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..6d6b4d4 --- /dev/null +++ b/bin/tests/system/dlzexternal/setup.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# 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 https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +$DDNSCONFGEN -q -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..ab35051 --- /dev/null +++ b/bin/tests/system/dlzexternal/tests.sh @@ -0,0 +1,230 @@ +#!/bin/sh + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# 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 https://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 | grep -E "^$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" +rndc_reload ns1 10.53.0.1 +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 AXFR denied based on view ACL" +# 10.53.0.1 should be disallowed +$DIG $DIGOPTS -b 10.53.0.1 +noall +answer axfr example.org > dig.out.example.ns1.test$n.1 +grep "; Transfer failed" dig.out.example.ns1.test$n.1 > /dev/null || ret=1 +# 10.53.0.2 should be allowed +$DIG $DIGOPTS -b 10.53.0.2 +noall +answer axfr example.org > dig.out.example.ns1.test$n.2 +grep "; Transfer failed" dig.out.example.ns1.test$n.2 > /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` + +newtest "checking ECS data is passed to driver in clientinfo" +$DIG $DIGOPTS +short +subnet=192.0/16 source-addr.example.nil txt > dig.out.ns1.test$n.1 || ret=1 +grep "192.0.0.0/16/0" dig.out.ns1.test$n.1 > /dev/null || ret=1 +$DIG $DIGOPTS +short source-addr.example.nil txt > dig.out.ns1.test$n.2 || ret=1 +grep "not.*present" dig.out.ns1.test$n.2 > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=`expr $status + $ret` + +echo_i "exit status: $status" +[ $status -eq 0 ] || exit 1 |