diff options
Diffstat (limited to '')
-rw-r--r-- | contrib/dlz/example/Makefile | 27 | ||||
-rw-r--r-- | contrib/dlz/example/README | 256 | ||||
-rw-r--r-- | contrib/dlz/example/dlz_example.c | 820 | ||||
-rw-r--r-- | contrib/dlz/example/named.conf | 60 |
4 files changed, 1163 insertions, 0 deletions
diff --git a/contrib/dlz/example/Makefile b/contrib/dlz/example/Makefile new file mode 100644 index 0000000..5c66ba1 --- /dev/null +++ b/contrib/dlz/example/Makefile @@ -0,0 +1,27 @@ +# 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. + +# for building the dlz_example driver we don't use +# the bind9 build structure as the aim is to provide an +# example that is separable from the bind9 source tree + +# this means this Makefile is not portable, so the testsuite +# skips this test on platforms where it doesn't build + +CFLAGS += -Wall -fPIC -g + +all: dlz_example.so + +dlz_example.so: dlz_example.o + $(CC) $(CFLAGS) -shared -o dlz_example.so dlz_example.o + +clean: + rm -f dlz_example.o dlz_example.so diff --git a/contrib/dlz/example/README b/contrib/dlz/example/README new file mode 100644 index 0000000..d87812a --- /dev/null +++ b/contrib/dlz/example/README @@ -0,0 +1,256 @@ +<!-- +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. +--> + +OVERVIEW: + +DLZ (Dynamically Loadable Zones) is an extension to BIND 9 that +allows zone data to be retrieved directly from an external database. +There is no required format or schema. DLZ drivers exist for several +different database backends including PostgreSQL, MySQL, and LDAP and +can be written for any other. + +Historically, DLZ drivers had to be statically linked with the named +binary and were turned on via a configure option at compile time (for +example, "configure --with-dlz-ldap"). Currently, the drivers provided +in the BIND 9 tarball in contrib/dlz/drivers are still linked this way. + +However, as of BIND 9.8, it is also possible to link some DLZ modules +dynamically at runtime, via the DLZ "dlopen" driver, which acts as a +generic wrapper around a shared object that implements the DLZ API. The +"dlopen" driver is linked into named by default, so configure options are +no longer necessary unless using older DLZ drivers. + +When the DLZ module provides data to named, it does so in text format. +The response is converted to DNS wire format by named. This conversion, +and the lack of any internal caching, places significant limits on the +query performance of DLZ modules. Consequently, DLZ is not recommended +for use on high-volume servers. However, it can be used in a hidden +primary configuration, with secondaries retrieving zone updates via AXFR. +(Note, however, that DLZ has no built-in support for DNS notify; secondaries +are not automatically informed of changes to the zones in the database.) + +CONFIGURING DLZ: + +A DLZ database is configured with a "dlz" statement in named.conf. + + dlz example { + database "dlopen driver.so <args>"; + search yes; + }; + +This specifies a DLZ module to search when answering queries; the module +is implemented in "driver.so" and is loaded at runtime by the dlopen DLZ +driver. Multiple "dlz" statements can be specified; when answering a +query, all DLZ modules with the "search" option set to "yes" will be +checked for an answer, and the best available answer will be returned +to the client. + +The "search" option in this example can be omitted, as "yes" is the +default value. If it is set to "no", then this DLZ module is *not* +searched for best-match when a query is received. Instead, zones in +this DLZ must be separately specified in a zone statement. This can +be useful when conventional zone semantics are desired but you wish +to use a different back-end storage mechanism than the standard zone +database. For example, to use a DLZ module for an NXDOMAIN redirection +zone: + + dlz other { + database "dlopen driver.so <args>"; + search no; + }; + + zone "." { + type redirect; + dlz other; + }; + +EXAMPLE DRIVER: + +This directory contains an example of an externally-lodable DLZ module, +dlz_example.c, which demonstrates the features of the DLZ API. It sets up +a single zone, whose name is configured in named.conf. The zone can answer +queries and AXFR requests, and accept DDNS updates. + +By default, at runtime, the zone implemented by this driver will contain +an SOA, NS, and a single A record at the apex. If configured in named.conf +to use the name "example.nil", then, the zone will look like this: + + example.nil. 3600 IN SOA example.nil. hostmaster.example.nil. ( + 123 900 600 86400 3600 + ) + example.nil. 3600 IN NS example.nil. + example.nil. 1800 IN A 10.53.0.1 + +The driver is also capable of retrieving information about the querying +client, and altering its response on the basis of this information. To +demonstrate this feature, the example driver responds to queries for +"source-addr.<zonename>/TXT" with the source address of the query. +Note, however, that this record will *not* be included in AXFR or ANY +responses. (Normally, this feature would be used to alter responses in +some other fashion, e.g., by providing different address records for +a particular name depending on the network from which the query arrived.) + +DYNAMIC UPDATES AND TRANSACTIONS: + +If a DLZ module wants to implement dynamic DNS updates (DDNS), the +normal calling sequence is + - dlz_newversion (start a 'transaction') + - dlz_addrdataset (add records) + - dlz_subrdataset (remove records) + - dlz_closeversion (end a 'transaction') + +However, BIND may also query the database during the transaction +(e.g., to check prerequisites), and your DLZ might need to know whether +the lookup is against the pre-existing data, or the new data. +dlz_lookup() doesn't give you access to the 'versionp' pointer +directly, so it must be passed via 'clientinfo' structure if +it is needed. + +The dlz_example.c code has sample code to show how to get the 'versionp' +pointer from within dlz_lookup(). If it's set to NULL, we query +the standard database; if non-NULL, we query against the in-flight +data within the appropriate uncommitted transaction. + +IMPLEMENTATION NOTES: + +The minimal set of type definitions, prototypes, and macros needed +for implementing a DLZ driver is in ../modules/dlz_minimal.h. Copy this +header file into your source tree when creating an external DLZ module. + +The DLZ dlopen driver provides a set of callback functions: + + - void log(int level, const char *fmt, ...); + + Writes the specified string to the named log, at the specified + log level. Uses printf() format semantics. + + - isc_result_t putrr(dns_sdlzlookup_t *lookup, const char *type, + dns_ttl_t ttl, const char *data); + + Puts a DNS resource record into the query response, which + referenced by the opaque structure 'lookup' provided by named. + + - isc_result_t putnamedrr(dns_sdlzallnotes_t *allnodes, + const char *name, const char *type, + dns_ttl_t ttl, const char *data); + + Puts a DNS resource record into an AXFR response, which is + referenced by the opaque structure 'allnodes' provided by named. + + - isc_result_t writeable_zone(dns_view_t *view, const char *zone_name); + + Allows the DLZ module to inform named that a given zone can receive + DDNS updates. (Note: This is not currently supported for DLZ + databases that are configured as 'search no;') + +The external DLZ module can define the following functions (some of these +are mandatory, others optional). + + - int dlz_version(unsigned int *flags); + + Required for alL external DLZ modules, to indicate the version number + of the DLZ dlopen driver that this module supports. It should return + the value DLZ_DLOPEN_VERSION, which is defined in the file + contrib/dlz/modules/dlz_minimal.h and is currently 3. 'flags' is + updated to indicate capabilities of the module. In particular, if + the module is thread-safe then it sets 'flags' to include + DNS_SDLZFLAG_THREADSAFE. (Other capability flags may be added in + the future.) + + - isc_result_t dlz_create(const char *dlzname, + unsigned int argc, char *argv[], + void **dbdata, ...); + + Required for all external DLZ modules; this call initializes the + module. + + - void dlz_destroy(void *dbdata); + + Optional. If supplied, this will be called when the driver is + unloaded. + + - isc_result_t dlz_findzonedb(void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); + + Required for all external DLZ modules. This indicates whether + the DLZ module can answer for the given name. Returns ISC_R_SUCCESS + if so, and ISC_R_NOTFOUND if not. As an optimization, it can + also return ISC_R_NOMORE: this indicates that the DLZ module has + no data for the given name or for any name above it in the DNS. + This prevents named from searching for a zone cut. + + - 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); + + Required for all external DLZ modules. This carries out the database + lookup for a query. + + - isc_result_t dlz_allowzonexfr(void *dbdata, const char *name, + const char *client); + + Optional. Supply this if you want the module to support AXFR + for the specified zone and client. A return value of ISC_R_SUCCESS + means AXFR is allowed, any other value means it isn't. + + - isc_result_t dlz_allnodes(const char *zone, void *dbdata, + dns_sdlzallnodes_t *allnodes); + + Optional, but must be supplied dlz_allowzonexfr() is. This function + returns all nodes in the zone in order to perform a zone transfer. + + - isc_result_t dlz_newversion(const char *zone, void *dbdata, + void **versionp); + + Optional. Supply this if you want the module to support DDNS + updates. This function starts a transaction in the database. + + + - void dlz_closeversion(const char *zone, bool commit, + void *dbdata, void **versionp); + + Optional, but must be supplied if dlz_newversion() is. This function + closes a transaction. 'commit' indicates whether to commit the changes + to the database, or ignore them. + + - isc_result_t dlz_configure(dns_view_t *view, void *dbdata); + + Optional, but must be supplied in order to support DDNS updates. + + - bool dlz_ssumatch(const char *signer, const char *name, + const char *tcpaddr, const char *type, + const char *key, uint32_t keydatalen, + uint8_t *keydata, void *dbdata); + + Optional, but must be supplied in order to support DDNS updates. + + - isc_result_t dlz_addrdataset(const char *name, const char *rdatastr, + void *dbdata, void *version); + + Optional, but must be supplied in order to support DDNS updates. + Adds the data in 'rdatastr' to a database node. + + - isc_result_t dlz_subrdataset(const char *name, const char *rdatastr, + void *dbdata, void *version); + + Optional, but must be supplied in order to support DDNS updates. + Removes the data in 'rdatastr' from a database node. + + - isc_result_t dlz_delrdataset(const char *name, const char *rdatastr, + void *dbdata, void *version); + + Optional, but must be supplied in order to support DDNS updates. + Deletes all data matching the type specified in 'rdatastr' from + the database. diff --git a/contrib/dlz/example/dlz_example.c b/contrib/dlz/example/dlz_example.c new file mode 100644 index 0000000..8be3afb --- /dev/null +++ b/contrib/dlz/example/dlz_example.c @@ -0,0 +1,820 @@ +/* + * Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: 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. + */ + +/* + * This provides a very simple example of an external loadable DLZ + * driver, with update support. + */ + +#include <inttypes.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../modules/include/dlz_minimal.h" + +#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 + +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[200]; + 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: findzonedb connection from: %s", + addrbuf); + + state->log(ISC_LOG_INFO, + "dlz_example: dlz_findzonedb called with name '%s' " + "in zone DB '%s'", + name, state->zone_name); + + /* + * 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); + } + + 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; this demonstrates 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]; + int i; + + UNUSED(zone); + + if (state->putrr == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + if (strcmp(name, "@") == 0) { + strncpy(full_name, state->zone_name, 255); + full_name[255] = '\0'; + } else { + snprintf(full_name, 255, "%s.%s", name, state->zone_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) { + state->log(ISC_LOG_INFO, "dlz_example: lookup against " + "live " + "transaction\n"); + } + } + + 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); + + state->log(ISC_LOG_INFO, + "dlz_example: lookup connection from: %s", buf); + + found = true; + result = state->putrr(lookup, "TXT", 0, buf); + /* We could also generate a CNAME RR: + snprintf(buf, sizeof(buf), "%s.redirect.example.", ecsbuf); + result = state->putrr(lookup, "CNAME", 0, buf); */ + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + if (strcmp(name, "too-long") == 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); + } + } + + 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) { + UNUSED(client); + + /* Just say yes for all our zones */ + return (dlz_findzonedb(dbdata, name, NULL, NULL)); +} + +/* + * 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; + 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); + } + + 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/contrib/dlz/example/named.conf b/contrib/dlz/example/named.conf new file mode 100644 index 0000000..eba464c --- /dev/null +++ b/contrib/dlz/example/named.conf @@ -0,0 +1,60 @@ +/* + * 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 is a sample named.conf file that uses the DLZ module defined in + * dlz_example.c. It sets up a zone 'example.nil' which can accept DDNS + * updates. + * + * By default, when run, the zone contains the following records: + * + * example.nil. 3600 IN SOA example.nil. hostmaster.example.nil. ( + * 123 900 600 86400 3600 + * ) + * example.nil. 3600 IN NS example.nil. + * example.nil. 1800 IN A 10.53.0.1 + * + * Additionally, a query for 'source-addr.example.nil/TXT' is always + * answered with the source address of the query. This is used to + * demonstrate the code that retrieves client information from the + * caller. + * + * To use this driver, "dlz_external.so" must be moved into the working + * directory for named. + */ + +options { + allow-transfer { any; }; + allow-query { any; }; + notify yes; + recursion no; +}; + +/* + * To test dynamic updates, create a DDNS key: + * + * ddns-confgen -q -z example.nil > ddns.key + * + * Then uncomment the following line: + * + * include "ddns.key"; + * + * Use "nsupdate -k ddns.key" when sending updates. (NOTE: This driver does + * not check the key that's used: as long as the update is signed by a key + * known to named, the update will be accepted. Only updates to names + * that begin with "deny." are rejected.) + */ + +dlz "example" { + database "dlopen ./dlz_example.so example.nil"; +}; |