diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 07:24:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 07:24:22 +0000 |
commit | 45d6379135504814ab723b57f0eb8be23393a51d (patch) | |
tree | d4f2ec4acca824a8446387a758b0ce4238a4dffa /contrib | |
parent | Initial commit. (diff) | |
download | bind9-45d6379135504814ab723b57f0eb8be23393a51d.tar.xz bind9-45d6379135504814ab723b57f0eb8be23393a51d.zip |
Adding upstream version 1:9.16.44.upstream/1%9.16.44
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'contrib')
108 files changed, 26166 insertions, 0 deletions
diff --git a/contrib/README b/contrib/README new file mode 100644 index 0000000..f8fc639 --- /dev/null +++ b/contrib/README @@ -0,0 +1,52 @@ +<!-- +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 directory contains scripts, tools, and other useful accessories to +BIND 9. Contrib software is not supported by ISC, but reported bugs will +be fixed as time permits. + + - scripts/ + + Assorted useful scripts, including 'nanny' which monitors + named and restarts it in the event of a crash, 'zone-edit' + which enables editing of a dynamic zone, and others. + + - dane/ + + mkdane.sh generates TLSA records for use with DNS-based + Authentication of Named Entities (DANE). + + - dnspriv/ + + Sample configuration for setting up a DNS-over-TLS server + using BIND with Nginx as a TLS proxy. + + - kasp/ + + Scripts for converting key and signature policies from OpenDNSSEC + KASP format to the policy.conf format used by dnssec-keymgr. + + - dlz/modules + + Dynamically linkable DLZ modules that can be configured into + named at runtime, enabling access to external data sources including + LDAP, MySQL, Berkeley DB, perl scripts, etc. + + - dlz/drivers + + Old-style DLZ drivers that can be linked into named at compile + time. (These are no longer actively maintained and are expected + to be removed eventually.) + +Some links to useful software and other resources related to BIND 9 and +DNS can be found at https://www.isc.org/dns-tools-and-resources. diff --git a/contrib/dane/mkdane.sh b/contrib/dane/mkdane.sh new file mode 100755 index 0000000..7c33b7c --- /dev/null +++ b/contrib/dane/mkdane.sh @@ -0,0 +1,130 @@ +#!/bin/sh + +# Copyright (C) 2010, 2012 Internet Systems Consortium, Inc. ("ISC") +# 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. + +USAGE="$BASENAME [options] <filename> +Options: + -f <input format>: PEM | DLR + -n <name>: record name (default: _443._tcp) + -o <origin>: zone origin (default: none; name will be relative) + -m <matching type>: NONE (0) | SHA256 (1) | SHA512 (2) + -r <RR type>: TLSA + -s <selector>: FULL (0) | PK (1) + -t <ttl>: TTL of the TLSA record (default: none) + -u <certificate usage>: CA (0) | SERVICE (1) | TA (2) | DOMAIN (3)" + +NM="_443._tcp" +CU=2 +SELECTOR=0 +MTYPE=1 +IN= +FORM=PEM +TTL= +RRTYPE=TLSA +BASENAME=`basename $0`; + +while getopts "xn:o:u:s:t:m:i:f:r:" c; do + case $c in + x) set -x; DEBUG=-x;; + m) MTYPE="$OPTARG";; + n) NM="$OPTARG";; + o) ORIGIN="$OPTARG";; + r) RRTYPE="$OPTARG";; + s) SELECTOR="$OPTARG";; + t) TTL="$OPTARG";; + u) CU="$OPTARG";; + *) echo "$USAGE" 1>&2; exit 1;; + esac +done +shift `expr $OPTIND - 1 || true` + +if test "$#" -eq 1; then + IN=$1 +else + echo "$USAGE" 1>&2; exit 1 +fi + +ORIGIN=`echo $ORIGIN | sed 's/\([^.]$\)/\1./'` +if [ -n "$ORIGIN" ]; then + NM=`echo $NM | sed 's/\.$//'` + NM="$NM.$ORIGIN" +fi + +case "$CU" in + [Cc][Aa]) CU=0;; + [Ss][Ee][Rr][Vv]*) CU=1;; + [Tt][Aa]) CU=2;; + [Dd][Oo][Mm]*) CU=3;; + [0123]) ;; + *) echo "bad certificate usage -u \"$CU\"" 1>&2; exit 1;; +esac + +case "$SELECTOR" in + [Ff][Uu][Ll][Ll]) SELECTOR=0;; + [Pp][Kk]) SELECTOR=1;; + [01]) ;; + *) echo "bad selector -s \"$SELECTOR\"" 1>&2; exit 1;; +esac + +case "$MTYPE" in + 0|[Nn][Oo][Nn][Ee]) HASH='od -A n -v -t xC';; + 1|[Ss][Hh][Aa]256) HASH='openssl dgst -sha256';; + 2|[Ss][Hh][Aa]512) HASH='openssl dgst -sha512';; + *) echo "bad matching type -m \"$MTYPE\"" 1>&2; exit 1;; +esac + +case "$FORM" in + [Pp][Ee][Mm]) FORM=PEM;; + [Dd][Ll][Rr]) FORM=DLR;; + *) echo "bad input file format -f \"$FORM\"" 1>&2; exit 1 +esac + +case "$RRTYPE" in + [Tt][Ll][Ss][Aa]) RRTYPE=TLSA;; + *) echo "invalid RR type" 1>&2; exit 1 +esac + +if test -z "$IN" -o ! -s "$IN"; then + echo "bad input file -i \"$IN\"" 1>&2; exit 1 +fi + +echo "; $BASENAME -o$NM -u$CU -s$SELECTOR -m$MTYPE -f$FORM $IN" + +(if test "$SELECTOR" = 0; then + openssl x509 -in "$IN" -inform "$FORM" -outform DER +else + openssl x509 -in "$IN" -inform "$FORM" -noout -pubkey \ + | sed -e '/PUBLIC KEY/d' \ + | openssl base64 -d +fi) \ + | $HASH \ + | awk ' + # format Association Data as in Appendix C of the DANE RFC + BEGIN { + print "'"$NM\t\t$TTL\tIN TLSA\t$CU $SELECTOR $MTYPE"' ("; + leader = "\t\t\t\t\t"; + } + /.+/ { + gsub(/ +/, "", $0); + buf = buf $0; + while (length(buf) >= 36) { + print leader substr(buf, 1, 36); + buf = substr(buf, 37); + } + } + END { + if (length(buf) > 34) + print leader buf "\n" leader ")"; + else + print leader buf " )"; + }' diff --git a/contrib/dane/tlsa6698.pem b/contrib/dane/tlsa6698.pem new file mode 100644 index 0000000..9b9c1ee --- /dev/null +++ b/contrib/dane/tlsa6698.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEVDCCArwCCQCrWNJOd60q9jANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQGEwJO +TDEWMBQGA1UECBMNTm9vcmQtSG9sbGFuZDESMBAGA1UEBxMJQW1zdGVyZGFtMQww +CgYDVQQKEwNPUzMxIzAhBgNVBAMTGmRhbmUua2lldi5wcmFjdGljdW0ub3MzLm5s +MB4XDTEyMDExNjE2NTcwM1oXDTIyMDExMzE2NTcwM1owbDELMAkGA1UEBhMCTkwx +FjAUBgNVBAgTDU5vb3JkLUhvbGxhbmQxEjAQBgNVBAcTCUFtc3RlcmRhbTEMMAoG +A1UEChMDT1MzMSMwIQYDVQQDExpkYW5lLmtpZXYucHJhY3RpY3VtLm9zMy5ubDCC +AaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAOYshKWv5Z8KKmslDe5oesjF +xgT1fSbOshGRQP+sOMS5y76JIwguf4Fia2rV3qDIdxx048qn9hMFSu+jZz5I/+R7 +P3r5h94oGmgjCyS52hqY3L5RGVtg5C/XUXwyjZg+JqgnyHerkU7kwb/erUi9Jb5f +LEc7qcHLvd2gw3TQ1Yw4nMPW2MIGYuGc92jzJEG399FK6olmznwyoXIqs4Yj0AgC +mp5HAog/i5d6Gh5Skr+K1yI51AOTN7hqOsYPoAEpBFIXe/F5hgmgWhMPAzRXpSEm +KfvduOcOKp5lVoc8T3ykauSosXjwX7MZAF4cHH1L1336NANVY8EmqiwzKLkA55kK +yXh/AdqC90w9S2Z0zOzh/Uxu+eZkT0Y17e2jnYsOL3yOBtrndWITvT1ggxF1vikE +QrSvxa5vRrdphVoGfBCX5heWJSnhZvIq7hDduYG4zW/xfT1wcjFpA42/vBpEnI0N +MbxoPF884mFI5C7Ju9TZ8mFWmyW1PB1/wt3/a0ysBQIDAQABMA0GCSqGSIb3DQEB +BQUAA4IBgQArKr4GPpyGrEofeDU3IJEHnIJ2qcLF0exXZN5SP92r3qs/005v5sug +VFgKZ4WmY1ldkBMrk9Rzkp6B+giH0v/3ioHH0BS5d3irasnl5pD29anpK7X7q3G4 +V65ptuGL3MsLpvzZ1LCEo082NRSMSV1I/mNZA7iI7B3rJhBUjt1I1j+GUTpFYkaY +MUjA1duC1zpMNQpCu2YddjQw/GyOX50T6ht2qlKkw1jl6gQAD3lGGDA6ts7qTpqO +nHTXPBsLe68W3t52lrXi8gb3dxAPVyfhaE1BMvXmkvR69nVuqLQhAAvgMbXY8CIO +Q2tR+xVP6VlTM8E6JAP53gjl3cWiL9YYLjOVk+JjdEUCILwU8+QP8z8IRSawnDQl +BwLoo1KzMszLD53izysziCO5KvxhwLa4q9ta9xjtjdqXwpjka4KgGxSBSGjPpPLD +Ymi//0pZH0Jli/dZGJAtPkJt/h1f8PxqISBx9tqL2DP+LlYNh3dejukzPAW2+461 +ZYnZENteqQM= +-----END CERTIFICATE----- diff --git a/contrib/dlz/bin/dlzbdb/Makefile.in b/contrib/dlz/bin/dlzbdb/Makefile.in new file mode 100644 index 0000000..345c54b --- /dev/null +++ b/contrib/dlz/bin/dlzbdb/Makefile.in @@ -0,0 +1,56 @@ +# Copyright (C) 1998-2001, 2016 Internet Systems Consortium, Inc. ("ISC") +# 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@ + +DLZINCLUDES = @DLZ_DRIVER_INCLUDES@ + +CINCLUDES = -I${srcdir}/include -I${srcdir}/unix/include \ + ${ISC_INCLUDES} ${DLZINCLUDES} + +CDEFINES = @CONTRIB_DLZ@ +CWARNINGS = + +DLZLIBS = @DLZ_DRIVER_LIBS@ +ISCLIBS = ../../../../lib/isc/libisc.@A@ @NO_LIBTOOL_ISCLIBS@ + +DEPLIBS = ${ISCDEPLIBS} + +LIBS = ${ISCLIBS} ${DLZLIBS} @LIBS@ + +TARGETS = dlzbdb + +SRCS = dlzbdb.c + +@BIND9_MAKE_RULES@ + +dlzbdb.@O@: dlzbdb.c + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -DVERSION=\"${VERSION}\" \ + -c ${srcdir}/dlzbdb.c + +dlzbdb: dlzbdb.@O@ ${DEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} -o $@ dlzbdb.@O@ ${LIBS} + +clean distclean maintainer-clean:: + rm -f ${TARGETS} + +installdirs: + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${sbindir} + +install:: dlzbdb installdirs + ${LIBTOOL_MODE_INSTALL} ${INSTALL_PROGRAM} dlzbdb ${DESTDIR}${sbindir} diff --git a/contrib/dlz/bin/dlzbdb/dlzbdb.c b/contrib/dlz/bin/dlzbdb/dlzbdb.c new file mode 100644 index 0000000..122d496 --- /dev/null +++ b/contrib/dlz/bin/dlzbdb/dlzbdb.c @@ -0,0 +1,1277 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +/* + * Copyright (C) 1999-2001, 2016 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/. + */ + +#ifdef DLZ_BDB + +/* + * exit codes + * 0 everything ok + * 1 error parsing command line + * 2 Missing, too many or invalid combination of command line parameters + * 3 Unable to open BDB database. + * 4 Unable to allocate memory for, or create lexer. + * 5 unable to perform BDB cursor operation + */ +#include <db.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <isc/buffer.h> +#include <isc/commandline.h> +#include <isc/formatcheck.h> +#include <isc/lex.h> +#include <isc/mem.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/util.h> + +/* shut up compiler warnings about no previous prototype */ + +static void +show_usage(void); + +int +getzone(DB *dbp, const DBT *pkey, const DBT *pdata, DBT *skey); + +int +gethost(DB *dbp, const DBT *pkey, const DBT *pdata, DBT *skey); + +void +bdb_cleanup(void); + +isc_result_t +bdb_opendb(DBTYPE db_type, DB **db_out, const char *db_name, int flags); + +void +put_data(bool dns_data, char *input_key, char *input_data); + +void +insert_data(void); + +isc_result_t +openBDB(void); + +isc_result_t +open_lexer(void); + +void +close_lexer(void); + +isc_result_t +bulk_write(char type, DB *database, DBC *dbcursor, DBT *bdbkey, DBT *bdbdata); + +void +operation_add(void); + +void +operation_bulk(void); + +void +operation_listOrDelete(bool dlt); + +/*% + * Maximum length of a single data line that + * may be inserted into database by this program. + * If you need to insert a line of data that is more + * than 10,000 characters change this definition. + */ + +#define max_data_len 10000 + +/*% + * BDB database names. If you want to use different + * database names change them here. + */ + +#define dlz_data "dns_data" +#define dlz_zone "dns_zone" +#define dlz_host "dns_host" +#define dlz_client "dns_client" + +/*% + * Error code returned by BDB secondary index callback functions. + * This error is returned if the callback function could not create + * the secondary index for any reason. + */ + +#define BDBparseErr 1 + +/* A struct to hold all the relevant info about the database */ + +typedef struct bdb_instance { + DB_ENV *dbenv; /* BDB environment */ + DB *data; /* dns_data database handle */ + DBC *cursor; /* database cursor */ + DBC *cursor2; /* second cursor used during list operation. */ + DBC *cursor3; /* third cursor used during list operation */ + DBC *cursor4; /* fourth cursor used during list operation */ + DB *zone; /* zone database handle */ + DB *host; /* host database handle */ + DB *client; /* client database handle */ +} bdb_instance_t; + +/* Possible operations */ + +#define list 1 /* list data */ +#define dele 2 /* delete data */ +#define add 3 /* add a single piece of data */ +#define bulk 4 /* bulk load data */ + +/*% + * quit macro is used instead of exit. quit always tries to close the lexer + * and the BDB database before exiting. + */ + +#define quit(i) \ + close_lexer(); \ + bdb_cleanup(); \ + exit(i); + +/*% + * checkOp is used to verify that only one operation (list, del, add, + * bulk from file, bulk from stdin) is specified on the command line. + * This prevents a user from specifying two operations on the command + * line, which would make no sense anyway. + */ + +#define checkOp(x) \ + if (x != 0) { \ + fprintf(stderr, "\nonly one operation " \ + "(l e d a f s) may be specified\n"); \ + quit(2); \ + } + +/*% + * checkParam is used to only allow a parameter to be specified once. + * I.E. the parameter key can only be used on the command line once. + * any attempt to use it twice causes an error. + */ + +#define checkParam(x, y) \ + if (x != NULL) { \ + fprintf(stderr, \ + "\n%s may only " \ + "be specified once\n", \ + y); \ + quit(2); \ + } + +/*% + * checkInvalidParam is used to only allow parameters which make sense for + * the operation selected. I.E. passing the key parameter makes no sense + * for the add operation, and thus it isn't allowed. + */ + +#define checkInvalidParam(x, y, z) \ + if (x != NULL) { \ + fprintf(stderr, \ + "\n%s " \ + "may not be specified %s\n", \ + y, z); \ + quit(2); \ + } + +/*% + * checkInvalidOption is used to only allow parameters which make sense for + * the operation selected - but checks boolean options. + * I.E. passing the "b" bare_list parameter makes no sense for the add + * operation, and thus it isn't allowed. + * if w == x then output error message "flag", "message" + */ + +#define checkInvalidOption(w, x, y, z) \ + if (w == x) { \ + fprintf(stderr, \ + "\n%s " \ + "may not be specified %s\n", \ + y, z); \ + quit(2); \ + } + +/* Global Variables */ + +int operation = 0; /*%< operation to perform. */ +/*% allow new lock files or DB to be created. */ +bool create_allowed = false; +char *key = NULL; /*%< key to use in list & del operations */ + +/*% dump DB in DLZBDB bulk format */ +bool list_everything = false; +unsigned int key_val; /*%< key as unsigned int used in list & del operations */ +char *zone = NULL; /*%< zone to use in list operations */ +char *host = NULL; /*%< host to use in list operations */ +char *c_zone = NULL; /*%< client zone to use in list operations */ +char *c_ip = NULL; /*%< client IP to use in list operations */ +char *a_data = NULL; /*%< data in add operation */ +char *bulk_file = NULL; /*%< bulk data file to load */ +char *db_envdir = NULL; /*%< BDB environment location */ +char *db_file = NULL; /*%< BDB database file location. */ +bdb_instance_t db; /* BDB instance we are operating on */ +isc_lex_t *lexer = NULL; /*%< lexer for use to use in parsing input */ +isc_mem_t *lex_mctx = NULL; /*%< memory context for lexer */ +char lex_data_buf[max_data_len]; /*%< data array to use for lex_buffer below */ +isc_buffer_t lex_buffer; /*%< buffer for lexer during add operation */ + +/*% + * Displays usage message + */ + +static void +show_usage(void) { + fprintf(stderr, "\n\n\ +---Usage:---------------------------------------------------------------------\ +\n\n\ + List data:\n\ + dlzbdb -l [-k key] [-z zone] [-h host] [-c client_zone] [-i client_ip]\n\ + BDB_environment BDB_database\n\n\ + Delete data:\n\ + dlzbdb -d [-k key] [-c client_zone] [-i client_ip]\n\ + BDB_environment BDB_database\n\n\ + Bulk load data from file:\n\ + dlzbdb -f file_to_load BDB_environment BDB_database\n\n\ + Bulk load data from stdin\n\ + dlzbdb -s BDB_environment BDB_database\n\n\ + Add data:\n\ + dlzbdb -a \"dns data to be added\" BDB_environment BDB_database\n\n\ + Export data:\n\ + dlzbdb -e BDB_environment BDB_database\n\n\ + Normally operations can only be performed on an existing database files.\n\ + Use the -n flag with any operation to allow files to be created.\n\ + Existing files will NOT be truncated by using the -n flag.\n\ + The -n flag will allow a new database to be created, or allow new\n\ + environment files to be created for an existing database.\n\n\ +---Format for -f & -a options:------------------------------------------------\ +\n\n\ +db_type zone host dns_type ttl ip\n\ +db_type zone host dns_type ttl mx_priority mail_host\n\ +db_type zone host dns_type ttl nm_svr resp_psn serial refresh retry expire min\ +\n\ +db_type zone client_ip\n\n\ +---Examples:------------------------------------------------------------------\ +\n\n\ +d mynm.com www A 10 127.0.0.1\n\ +d mynm.com @ MX 10 5 mail\n\ +d mynm.com @ SOA 10 ns1.mynm.com. root.mynm.com. 2 28800 7200 604800 86400\n\ +c mynm.com 127.0.0.1\n\ +c mynm.com 192.168.0.10\n\ +"); + quit(1); +} + +/*% BDB callback to create zone secondary index */ + +int +getzone(DB *dbp, const DBT *pkey, const DBT *pdata, DBT *skey) { + char *token, *last; + + UNUSED(dbp); + UNUSED(pkey); + + if ((token = strtok_r(pdata->data, " ", &last)) == NULL) { + return (BDBparseErr); + } + + /* copy string for "zone" secondary index */ + if ((skey->data = strdup(token)) == NULL) { + return (BDBparseErr); + } + /* set required values for BDB */ + skey->size = strlen(skey->data); + skey->flags = DB_DBT_APPMALLOC; + + return (0); +} + +/*% + * BDB callback to create host secondary index + */ + +int +gethost(DB *dbp, const DBT *pkey, const DBT *pdata, DBT *skey) { + char *token, *last; + + UNUSED(dbp); + UNUSED(pkey); + + /* we don't care about first token. */ + if ((token = strtok_r(right, " ", &last)) == NULL) { + return (BDBparseErr); + } + + /* get "host" from data string */ + if ((token = strtok_r(NULL, " ", &last)) == NULL) { + return (BDBparseErr); + } + + /* copy string for "host" secondary index */ + if ((skey->data = strdup(token)) == NULL) { + return (BDBparseErr); + } + /* set required values for BDB */ + skey->size = strlen(skey->data); + skey->flags = DB_DBT_APPMALLOC; + + return (0); +} + +/*% + * Performs BDB cleanup. Close each database that we opened. + * Close environment. Set each var to NULL so we know they + * were closed and don't accidentally try to close them twice. + */ + +void +bdb_cleanup(void) { + /* close cursors */ + if (db.cursor4 != NULL) { + db.cursor4->c_close(db.cursor4); + db.cursor4 = NULL; + } + + if (db.cursor3 != NULL) { + db.cursor3->c_close(db.cursor3); + db.cursor3 = NULL; + } + + if (db.cursor2 != NULL) { + db.cursor2->c_close(db.cursor2); + db.cursor2 = NULL; + } + + if (db.cursor != NULL) { + db.cursor->c_close(db.cursor); + db.cursor = NULL; + } + + /* close databases */ + if (db.data != NULL) { + db.data->close(db.data, 0); + db.data = NULL; + } + if (db.host != NULL) { + db.host->close(db.host, 0); + db.host = NULL; + } + if (db.zone != NULL) { + db.zone->close(db.zone, 0); + db.zone = NULL; + } + if (db.client != NULL) { + db.client->close(db.client, 0); + db.client = NULL; + } + + /* close environment */ + if (db.dbenv != NULL) { + db.dbenv->close(db.dbenv, 0); + db.dbenv = NULL; + } +} + +/*% Initializes, sets flags and then opens Berkeley databases. */ + +isc_result_t +bdb_opendb(DBTYPE db_type, DB **db_out, const char *db_name, int flags) { + int result; + int createFlag = 0; + + /* Initialize the database. */ + if ((result = db_create(db_out, db.dbenv, 0)) != 0) { + fprintf(stderr, + "BDB could not initialize %s database. BDB error: %s", + db_name, db_strerror(result)); + return (ISC_R_FAILURE); + } + + /* set database flags. */ + if ((result = (*db_out)->set_flags(*db_out, flags)) != 0) { + fprintf(stderr, + "BDB could not set flags for %s database. BDB error: " + "%s", + db_name, db_strerror(result)); + return (ISC_R_FAILURE); + } + + if (create_allowed) { + createFlag = DB_CREATE; + } + /* open the database. */ + if ((result = (*db_out)->open(*db_out, NULL, db_file, db_name, db_type, + createFlag, 0)) != 0) + { + fprintf(stderr, + "BDB could not open %s database in %s. BDB error: %s", + db_name, db_file, db_strerror(result)); + return (ISC_R_FAILURE); + } + + return (ISC_R_SUCCESS); +} + +/*% + * parses input and adds it to the BDB database + * Lexer should be instantiated, and either a file or buffer opened for it. + * The insert_data function is used by both the add, and bulk insert + * operations + */ + +void +put_data(bool dns_data, char *input_key, char *input_data) { + int bdbres; + DBT key, data; + + /* make sure key & data are completely empty */ + memset(&key, 0, sizeof(key)); + memset(&data, 0, sizeof(data)); + + /* if client data, setup key for insertion */ + if (!dns_data && input_key != NULL) { + key.data = input_key; + key.size = strlen(input_key); + key.flags = 0; + } + /* always setup data for insertion */ + data.data = input_data; + data.size = strlen(input_data); + data.flags = 0; + + /* execute insert against appropriate database. */ + if (dns_data) { + bdbres = db.data->put(db.data, NULL, &key, &data, DB_APPEND); + } else { + bdbres = db.client->put(db.client, NULL, &key, &data, 0); + } + + /* if something went wrong, log error and quit */ + if (bdbres != 0) { + fprintf(stderr, "BDB could not insert data. Error: %s", + db_strerror(bdbres)); + quit(5); + } +} + +void +insert_data(void) { + unsigned int opt = ISC_LEXOPT_EOL | /* Want end-of-line token. */ + ISC_LEXOPT_EOF | /* Want end-of-file token. */ + ISC_LEXOPT_QSTRING | /* Recognize qstrings. */ + ISC_LEXOPT_QSTRINGMULTILINE; /* Allow multiline "" + * strings */ + + isc_result_t result; + isc_token_t token; /* token from lexer */ + bool loop = true; + bool have_czone = false; + char data_arr[max_data_len]; + isc_buffer_t buf; + char data_arr2[max_data_len]; + isc_buffer_t buf2; + char data_type = 'u'; /* u =unknown, b =bad token, d/D =DNS, c/C =client + * IP */ + + /* Initialize buffers */ + isc_buffer_init(&buf, &data_arr, max_data_len); + isc_buffer_init(&buf2, &data_arr2, max_data_len); + + while (loop) { + result = isc_lex_gettoken(lexer, opt, &token); + if (result != ISC_R_SUCCESS) { + goto data_cleanup; + } + + switch (token.type) { + case isc_tokentype_string: + if (data_type == 'u') { + /* store data_type */ + strncpy(&data_type, token.value.as_pointer, 1); + /* verify data_type was specified correctly on + * input */ + if (strlen(token.value.as_pointer) > 1 || + (data_type != 'd' && data_type != 'D' && + data_type != 'c' && data_type != 'C')) + { + /* if not, set to 'b' so this line is + * ignored. */ + data_type = 'b'; + } + } else if (data_type == 'c' || data_type == 'C') { + if (have_czone) { + isc_buffer_putstr( + &buf2, token.value.as_pointer); + /* add string terminator to buffer */ + isc_buffer_putmem(&buf2, "\0", 1); + } else { + isc_buffer_putstr( + &buf, token.value.as_pointer); + /* add string terminator to buffer */ + isc_buffer_putmem(&buf, "\0", 1); + have_czone = true; + } + } else { + isc_buffer_putstr(&buf, token.value.as_pointer); + isc_buffer_putstr(&buf, " "); + } + break; + case isc_tokentype_qstring: + isc_buffer_putstr(&buf, "\""); + isc_buffer_putstr(&buf, token.value.as_pointer); + isc_buffer_putstr(&buf, "\" "); + break; + case isc_tokentype_eol: + case isc_tokentype_eof: + + if ((data_type != 'u' && + isc_buffer_usedlength(&buf) > 0) || + data_type == 'b') + { + /* perform insert operation */ + if (data_type == 'd' || data_type == 'D') { + /* add string terminator to buffer */ + isc_buffer_putmem(&buf, "\0", 1); + put_data(true, NULL, (char *)&data_arr); + } else if (data_type == 'c' || data_type == 'C') + { + put_data(false, (char *)&data_arr, + (char *)&data_arr2); + } else if (data_type == 'b') { + fprintf(stderr, + "Bad / unknown token " + "encountered on line %lu." + " Skipping line.", + isc_lex_getsourceline(lexer) - + 1); + } else { + fprintf(stderr, + "Bad / unknown db data type " + "encountered on " + "line %lu. Skipping line\n", + isc_lex_getsourceline(lexer) - + 1); + } + } + + if (token.type == isc_tokentype_eof) { + loop = false; + } + + /* reset buffer for next insert */ + isc_buffer_clear(&buf); + isc_buffer_clear(&buf2); + have_czone = false; + data_type = 'u'; + break; + default: + data_type = 'b'; + break; + } + } + + return; + +data_cleanup: + /* let user know we had problems */ + fprintf(stderr, + "Unknown error processing tokens during \"add\" or " + "\"bulk\" operation.\nStoped processing on line %lu.", + isc_lex_getsourceline(lexer)); +} + +isc_result_t +openBDB(void) { + int bdbres; + isc_result_t result; + + /* create BDB environment */ + /* Basically BDB allocates and assigns memory to db->dbenv */ + bdbres = db_env_create(&db.dbenv, 0); + if (bdbres != 0) { + fprintf(stderr, + "BDB environment could not be created. BDB error: %s", + db_strerror(bdbres)); + result = ISC_R_FAILURE; + goto openBDB_cleanup; + } + + /* open BDB environment */ + if (create_allowed) { + /* allowed to create new files */ + bdbres = db.dbenv->open(db.dbenv, db_envdir, + DB_INIT_CDB | DB_INIT_MPOOL | DB_CREATE, + 0); + } else { /* not allowed to create new files. */ + bdbres = db.dbenv->open(db.dbenv, db_envdir, + DB_INIT_CDB | DB_INIT_MPOOL, 0); + } + if (bdbres != 0) { + fprintf(stderr, + "BDB environment at '%s' could not be opened. BDB " + "error: %s", + db_envdir, db_strerror(bdbres)); + result = ISC_R_FAILURE; + goto openBDB_cleanup; + } + + /* open dlz_data database. */ + + result = bdb_opendb(DB_RECNO, &db.data, dlz_data, 0); + if (result != ISC_R_SUCCESS) { + goto openBDB_cleanup; + } + + /* open dlz_host database */ + result = bdb_opendb(DB_BTREE, &db.host, dlz_host, DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) { + goto openBDB_cleanup; + } + + /* open dlz_zone database. */ + result = bdb_opendb(DB_BTREE, &db.zone, dlz_zone, DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) { + goto openBDB_cleanup; + } + + /* open dlz_client database. */ + result = bdb_opendb(DB_BTREE, &db.client, dlz_client, + DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) { + goto openBDB_cleanup; + } + + /* associate the host secondary database with the primary database */ + bdbres = db.data->associate(db.data, NULL, db.host, gethost, 0); + if (bdbres != 0) { + fprintf(stderr, + "BDB could not associate %s database with %s. BDB " + "error: %s", + dlz_host, dlz_data, db_strerror(bdbres)); + result = ISC_R_FAILURE; + goto openBDB_cleanup; + } + + /* associate the zone secondary database with the primary database */ + bdbres = db.data->associate(db.data, NULL, db.zone, getzone, 0); + if (bdbres != 0) { + fprintf(stderr, + "BDB could not associate %s database with %s. BDB " + "error: %s", + dlz_zone, dlz_data, db_strerror(bdbres)); + result = ISC_R_FAILURE; + goto openBDB_cleanup; + } + + return (result); + +openBDB_cleanup: + + bdb_cleanup(); + return (result); +} + +/*% Create & open lexer to parse input data */ + +isc_result_t +open_lexer(void) { + isc_result_t result; + + /* check if we already opened the lexer, if we did, return success */ + if (lexer != NULL) { + return (ISC_R_SUCCESS); + } + + /* allocate memory for lexer, and verify it was allocated */ + isc_mem_create(&lex_mctx); + + /* create lexer */ + result = isc_lex_create(lex_mctx, 1500, &lexer); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "unexpected error creating lexer\n"); + } + + /* set allowed commenting style */ + isc_lex_setcomments(lexer, + ISC_LEXCOMMENT_C | /* Allow C comments */ + ISC_LEXCOMMENT_CPLUSPLUS | /* Allow + * C++ + * comments + */ + ISC_LEXCOMMENT_SHELL); /* Allow + * shellcomments + */ + + isc_buffer_init(&lex_buffer, &lex_data_buf, max_data_len); + + return (result); +} + +/*% Close the lexer, and cleanup memory */ + +void +close_lexer(void) { + /* If lexer is still open, close it & destroy it. */ + if (lexer != NULL) { + isc_lex_close(lexer); + isc_lex_destroy(&lexer); + } + + /* if lexer memory is still allocated, destroy it. */ + if (lex_mctx != NULL) { + isc_mem_destroy(&lex_mctx); + } +} + +/*% Perform add operation */ + +void +operation_add(void) { + /* check for any parameters that are not allowed during add */ + checkInvalidParam(key, "k", "for add operation"); + checkInvalidParam(zone, "z", "for add operation"); + checkInvalidParam(host, "h", "for add operation"); + checkInvalidParam(c_zone, "c", "for add operation"); + checkInvalidParam(c_ip, "i", "for add operation"); + checkInvalidOption(list_everything, true, "e", "for add operation"); + + /* if open lexer fails it already prints error messages. */ + if (open_lexer() != ISC_R_SUCCESS) { + quit(4); + } + + /* copy input data to buffer */ + isc_buffer_putstr(&lex_buffer, a_data); + + /* tell lexer to use buffer as input */ + if (isc_lex_openbuffer(lexer, &lex_buffer) != ISC_R_SUCCESS) { + fprintf(stderr, "unexpected error opening lexer buffer"); + quit(4); + } + + /*common logic for "add" & "bulk" operations are handled by insert_data + */ + insert_data(); +} + +/*% Perform bulk insert operation */ + +void +operation_bulk(void) { + /* check for any parameters that are not allowed during bulk */ + checkInvalidParam(key, "k", "for bulk load operation"); + checkInvalidParam(zone, "z", "for bulk load operation"); + checkInvalidParam(host, "h", "for bulk load operation"); + checkInvalidParam(c_zone, "c", "for bulk load operation"); + checkInvalidParam(c_ip, "i", "for bulk load operation"); + checkInvalidOption(list_everything, true, "e", + "for bulk load operation"); + + /* if open lexer fails it already prints error messages. */ + if (open_lexer() != ISC_R_SUCCESS) { + quit(4); + } + + if (bulk_file == NULL) { + if (isc_lex_openstream(lexer, stdin) != ISC_R_SUCCESS) { + fprintf(stderr, "unexpected error opening stdin by " + "lexer."); + quit(4); + } + } else if (isc_lex_openfile(lexer, bulk_file) != ISC_R_SUCCESS) { + fprintf(stderr, "unexpected error opening %s by lexer.", + bulk_file); + quit(4); + } + + /* common logic for "add" & "bulk" operations are handled by insert_data + */ + insert_data(); +} + +isc_result_t +bulk_write(char type, DB *database, DBC *dbcursor, DBT *bdbkey, DBT *bdbdata) { + int bdbres; + db_recno_t recNum; + char *retkey = NULL, *retdata; + size_t retklen = 0, retdlen; + void *p; + + /* use a 5MB buffer for the bulk dump */ + int buffer_size = 5 * 1024 * 1024; + + /* try to allocate a 5 MB buffer, if we fail write err msg, die. */ + bdbdata->data = malloc(buffer_size); + if (bdbdata->data == NULL) { + fprintf(stderr, "Unable to allocate 5 MB buffer for bulk " + "database dump\n"); + return (ISC_R_FAILURE); + } + bdbdata->ulen = buffer_size; + bdbdata->flags = DB_DBT_USERMEM; + + /* get a cursor, make sure it worked. */ + bdbres = database->cursor(database, NULL, &dbcursor, 0); + if (bdbres != 0) { + fprintf(stderr, "Unexpected error. BDB Error: %s\n", + db_strerror(bdbres)); + free(bdbdata->data); + return (ISC_R_FAILURE); + } + + /* loop and dump all data */ + for (;;) { + /* loop through data until DB_NOTFOUND is returned */ + bdbres = dbcursor->c_get(dbcursor, bdbkey, bdbdata, + DB_MULTIPLE_KEY | DB_NEXT); + /* if not successful did we encounter DB_NOTFOUND, or */ + /* have a different problem. */ + if (bdbres != 0) { + if (bdbres != DB_NOTFOUND) { + fprintf(stderr, + "Unexpected error. BDB Error: %s\n", + db_strerror(bdbres)); + free(bdbdata->data); + return (ISC_R_FAILURE); + } + /* Hit DB_NOTFOUND which means end of data. */ + break; + } /* end of if (bdbres !=0) */ + + for (DB_MULTIPLE_INIT(p, bdbdata);;) { + if (type == 'c') { + DB_MULTIPLE_KEY_NEXT(p, bdbdata, retkey, + retklen, retdata, retdlen); + } else { + DB_MULTIPLE_RECNO_NEXT(p, bdbdata, recNum, + retdata, retdlen); + } + + if (p == NULL) { + break; + } + if (type == 'c') { + printf("c %.*s %.*s\n", (int)retklen, retkey, + (int)retdlen, retdata); + } else { + printf("d %.*s\n", (int)retdlen, retdata); + } + } /* end of for (DB_MULTIPLE_INIT....) */ + } /* end of for (;;) */ + + /* free the buffer we created earlier */ + free(bdbdata->data); + + return (ISC_R_SUCCESS); +} + +/*% + * Perform listOrDelete operation + * if dlt == true, delete data + * else list data + */ + +void +operation_listOrDelete(bool dlt) { + int bdbres = 0; + DBC *curList[3]; + DBT bdbkey, bdbdata; + db_recno_t recno; + int curIndex = 0; + + /* verify that only allowed parameters were passed. */ + if (dlt) { + checkInvalidParam(zone, "z", "for delete operation"); + checkInvalidParam(host, "h", "for delete operation"); + checkInvalidOption(list_everything, true, "e", + "for delete operation"); + checkInvalidOption(create_allowed, true, "n", + "for delete operation"); + } else if (key != NULL || zone != NULL || host != NULL) { + checkInvalidParam(c_zone, "c", + "for list when k, z or h are specified"); + checkInvalidParam(c_ip, "i", + "for list when k, z, or h are specified"); + checkInvalidOption(list_everything, true, "e", + "for list when k, z, or h are specified"); + checkInvalidOption(create_allowed, true, "n", + "for list operation"); + } else if (c_ip != NULL || c_zone != NULL) { + checkInvalidOption(list_everything, true, "e", + "for list when c or i are specified"); + checkInvalidOption(create_allowed, true, "n", + "for list operation"); + } + + memset(&bdbkey, 0, sizeof(bdbkey)); + memset(&bdbdata, 0, sizeof(bdbdata)); + + /* Dump database in "dlzbdb" bulk format */ + if (list_everything) { + if (bulk_write('c', db.client, db.cursor, &bdbkey, &bdbdata) != + ISC_R_SUCCESS) + { + return; + } + memset(&bdbkey, 0, sizeof(bdbkey)); + memset(&bdbdata, 0, sizeof(bdbdata)); + bulk_write('d', db.data, db.cursor2, &bdbkey, &bdbdata); + return; + } /* end if (list_everything) */ + + /* set NULL the 2nd and 3rd positions in curList. */ + /* that way later when add cursors to the join list */ + /* it is already null terminated. */ + curList[1] = curList[2] = NULL; + + if (key != NULL) { + /* make sure other parameters weren't */ + checkInvalidParam(zone, "z", "when k is specified"); + checkInvalidParam(host, "h", "when k is specified"); + + recno = key_val; + bdbkey.data = &recno; + bdbkey.size = sizeof(recno); + + if (dlt) { + bdbres = db.data->del(db.data, NULL, &bdbkey, 0); + } else { + bdbdata.flags = DB_DBT_REALLOC; + bdbres = db.data->get(db.data, NULL, &bdbkey, &bdbdata, + 0); + + if (bdbres == 0) { + printf("KEY | DATA\n"); + printf("%lu | %.*s\n", *(u_long *)bdbkey.data, + (int)bdbdata.size, (char *)bdbdata.data); + } + } /* closes else of if (dlt == true) */ + if (bdbres == DB_NOTFOUND) { + printf("Key not found in database"); + } + } /* closes if (key != NULL) */ + + /* if zone is passed */ + if (zone != NULL) { + /* create a cursor and make sure it worked */ + bdbres = db.zone->cursor(db.zone, NULL, &db.cursor2, 0); + if (bdbres != 0) { + fprintf(stderr, "Unexpected error. BDB Error: %s\n", + db_strerror(bdbres)); + return; + } + + bdbkey.data = zone; + bdbkey.size = strlen(zone); + bdbres = db.cursor2->c_get(db.cursor2, &bdbkey, &bdbdata, + DB_SET); + if (bdbres != 0) { + if (bdbres != DB_NOTFOUND) { + fprintf(stderr, + "Unexpected error. BDB Error: %s\n", + db_strerror(bdbres)); + } else { + printf("Zone not found in database"); + } + return; + } + + /* add cursor to cursor list for later use in join */ + curList[curIndex++] = db.cursor2; + } + + /* if host is passed */ + if (host != NULL) { + /* create a cursor and make sure it worked. */ + bdbres = db.host->cursor(db.host, NULL, &db.cursor3, 0); + if (bdbres != 0) { + fprintf(stderr, "Unexpected error. BDB Error: %s\n", + db_strerror(bdbres)); + return; + } + bdbkey.data = host; + bdbkey.size = strlen(host); + bdbres = db.cursor3->c_get(db.cursor3, &bdbkey, &bdbdata, + DB_SET); + if (bdbres != 0) { + if (bdbres != DB_NOTFOUND) { + fprintf(stderr, + "Unexpected error. BDB Error: %s\n", + db_strerror(bdbres)); + } else { + printf("Host not found in database"); + } + return; + } + + /* add cursor to cursor list for later use in join */ + curList[curIndex++] = db.cursor3; + } + + if (zone != NULL || host != NULL) { + /* join any cursors */ + bdbres = db.data->join(db.data, curList, &db.cursor4, 0); + if (bdbres != 0) { + fprintf(stderr, "Unexpected error. BDB Error: %s\n", + db_strerror(bdbres)); + return; + } + + memset(&bdbkey, 0, sizeof(bdbkey)); + bdbkey.flags = DB_DBT_REALLOC; + memset(&bdbdata, 0, sizeof(bdbdata)); + bdbdata.flags = DB_DBT_REALLOC; + + /* print a header to explain the output */ + printf("KEY | DATA\n"); + /* loop and list all results. */ + while (bdbres == 0) { + /* get data */ + bdbres = db.cursor4->c_get(db.cursor4, &bdbkey, + &bdbdata, 0); + /* verify call had no errors */ + if (bdbres != 0) { + break; + } + printf("%lu | %.*s\n", *(u_long *)bdbkey.data, + (int)bdbdata.size, (char *)bdbdata.data); + } /* closes while loop */ + } + + if (c_ip != NULL && c_zone == NULL) { + fprintf(stderr, "i may only be specified when c is also " + "specified\n"); + quit(2); + } + /* if client_zone was passed */ + if (c_zone != NULL) { + /* create a cursor and make sure it worked. */ + if (dlt) { + /* open read-write cursor */ + bdbres = db.client->cursor(db.client, NULL, &db.cursor, + DB_WRITECURSOR); + } else { + /* open read only cursor */ + bdbres = db.client->cursor(db.client, NULL, &db.cursor, + 0); + /* print a header to explain the output */ + printf("CLIENT_ZONE | CLIENT_IP\n"); + } + + bdbkey.data = c_zone; + bdbkey.size = strlen(c_zone); + + if (c_ip != NULL) { + bdbdata.data = c_ip; + bdbdata.size = strlen(c_ip); + bdbres = db.cursor->c_get(db.cursor, &bdbkey, &bdbdata, + DB_GET_BOTH); + if (bdbres == DB_NOTFOUND) { + printf("Client zone & IP not found in " + "database"); + } + } else { + bdbdata.flags = DB_DBT_REALLOC; + bdbres = db.cursor->c_get(db.cursor, &bdbkey, &bdbdata, + DB_SET); + if (bdbres == DB_NOTFOUND) { + printf("Client zone not found in database"); + } + } + + while (bdbres == 0) { + if (!dlt) { + printf("%.*s | %.*s\n", (int)bdbkey.size, + (char *)bdbkey.data, (int)bdbdata.size, + (char *)bdbdata.data); + } else { + /* delete record. */ + bdbres = db.cursor->c_del(db.cursor, 0); + if (bdbres != 0) { + fprintf(stderr, + "Unexpected error. BDB Error: " + "%s\n", + db_strerror(bdbres)); + break; + } + } + if (c_ip != NULL) { + break; + } + bdbres = db.cursor->c_get(db.cursor, &bdbkey, &bdbdata, + DB_NEXT_DUP); + if (bdbres != 0) { + break; + } + } /* end while loop */ + } + + if (bdbres != 0 && bdbres != DB_NOTFOUND) { + fprintf(stderr, + "Unexpected error during list operation " + "BDB error: %s", + db_strerror(bdbres)); + } + + if (bdbkey.flags == DB_DBT_REALLOC && bdbkey.data != NULL) { + free(bdbkey.data); + } + if (bdbdata.flags == DB_DBT_REALLOC && bdbdata.data != NULL) { + free(bdbdata.data); + } +} + +int +main(int argc, char **argv) { + int ch; + char *endp; + + /* there has to be at least 2 args, some operations require more */ + if (argc < 2) { + show_usage(); + } + + /* use the ISC commandline parser to get all the program arguments */ + while ((ch = isc_commandline_parse(argc, argv, + "ldesna:f:k:z:h:c:i:")) != -1) + { + switch (ch) { + case 'n': + create_allowed = true; + break; + case 'l': + checkOp(operation); + operation = list; + break; + case 'd': + checkOp(operation); + operation = dele; + break; + case 'a': + checkOp(operation); + operation = add; + a_data = isc_commandline_argument; + break; + case 'f': + checkOp(operation); + operation = bulk; + bulk_file = isc_commandline_argument; + break; + case 's': + checkOp(operation); + operation = bulk; + break; + case 'k': + checkParam(key, "k"); + key = isc_commandline_argument; + key_val = strtoul(key, &endp, 10); + if (*endp != '\0' || key_val < 1) { + fprintf(stderr, "Error converting key to " + "integer"); + } + break; + case 'z': + checkParam(zone, "z"); + zone = isc_commandline_argument; + break; + case 'h': + checkParam(host, "h"); + host = isc_commandline_argument; + break; + case 'c': + checkParam(c_zone, "c"); + c_zone = isc_commandline_argument; + break; + case 'i': + checkParam(c_ip, "i"); + c_ip = isc_commandline_argument; + break; + case 'e': + checkOp(operation); + operation = list; + list_everything = true; + break; + case '?': + show_usage(); + break; + default: + /* should never reach this point */ + fprintf(stderr, "unexpected error parsing command " + "arguments\n"); + quit(1); + break; + } + } + + argc -= isc_commandline_index; + argv += isc_commandline_index; + + /* argc & argv have been modified, so now only "extra" parameters are */ + /* left in argc & argv. "Extra" parameters are any parameters that were + */ + /* not passed using a command line flag. Exactly 2 args should be left. + */ + /* The first should be the BDB environment path, the second should be + * the */ + /* BDB database. The BDB database path can be either relative to the */ + /* BDB environment path, or absolute. */ + if (argc < 2) { + fprintf(stderr, "Both a Berkeley DB environment and file " + "must be specified"); + quit(2); + } else if (argc > 2) { + fprintf(stderr, "Too many parameters. Check command line for " + "errors."); + quit(2); + } + + /* get db_file to operate on */ + db_envdir = argv[0]; + db_file = argv[1]; + + if (openBDB() != ISC_R_SUCCESS) { + /* openBDB already prints error messages, don't do it here. */ + bdb_cleanup(); + quit(3); + } + + switch (operation) { + case list: + operation_listOrDelete(false); + break; + case dele: + operation_listOrDelete(true); + break; + case add: + operation_add(); + break; + case bulk: + operation_bulk(); + break; + default: + fprintf(stderr, "\nNo operation was selected. " + "Select an operation (l d a f)"); + quit(2); + break; + } + + quit(0); +} +#endif /* ifdef DLZ_BDB */ diff --git a/contrib/dlz/config.dlz.in b/contrib/dlz/config.dlz.in new file mode 100644 index 0000000..696fd96 --- /dev/null +++ b/contrib/dlz/config.dlz.in @@ -0,0 +1,510 @@ +# Copyright (C) 2005 Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND 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. + +# +# Shorthand. Note quoting: DLZ_DRIVER_DIR expanded in Makefile, not here. +# +dlzdir='${DLZ_DRIVER_DIR}' + +# +# Private autoconf macro to simplify configuring drivers: +# +# DLZ_ADD_DRIVER(DEFINE, DRIVER, INCLUDES, LIBS) +# +# where: +# DEFINE is FOO (to define -DDLZ_FOO) +# DRIVER is dlz_foo_driver (sources without the .c) +# INCLUDES is any necessary include definitions +# LIBS is any necessary library definitions +# +AC_DEFUN([DLZ_ADD_DRIVER], [ + CONTRIB_DLZ="$CONTRIB_DLZ -DDLZ_$1" + for i in $2 + do + DLZ_DRIVER_SRCS="$DLZ_DRIVER_SRCS $dlzdir/$i.c" + DLZ_DRIVER_OBJS="$DLZ_DRIVER_OBJS $i.$O" + done + if test -n "$3" + then + DLZ_DRIVER_INCLUDES="$DLZ_DRIVER_INCLUDES $3" + DLZ_DRIVER_$1_INCLUDES="$3" + fi + if test -n "$4" + then + DLZ_DRIVER_LIBS="$DLZ_DRIVER_LIBS $4" + DLZ_DRIVER_$1_LIBS="$4" + fi +]) + +# +# Check for the various DLZ drivers +# + +# +# Was --with-dlz-postgres specified? +# + +AC_MSG_CHECKING(for Postgres DLZ driver) +AC_ARG_WITH(dlz_postgres, + AS_HELP_STRING([--with-dlz-postgres[=PATH]], + [Build with Postgres DLZ driver [yes|no|path]. + (Required to use Postgres with DLZ)]), + use_dlz_postgres="$withval", use_dlz_postgres="no") + +if test "$use_dlz_postgres" != "no" +then + if test "$use_dlz_postgres" != "yes" + then + AC_PATH_PROGS(PG_CONFIG, pg_config, [not found], $use_dlz_postgres/bin) + else + AC_PATH_PROGS(PG_CONFIG, pg_config, [not found]) + fi + + if test "$PG_CONFIG" != "not found" + then + use_dlz_postgres=`$PG_CONFIG --includedir` + use_dlz_postgres_lib=`$PG_CONFIG --libdir` + else + pgprefix="$use_dlz_postgres" + use_dlz_postgres="$pgprefix/include" + use_dlz_postgres_lib="$pgprefix/lib" + fi +fi + +if test "$use_dlz_postgres" = "yes/include" +then + # User did not specify path and Postgres didn't say - guess it + pgdirs="/usr /usr/local /usr/local/pgsql /usr/pkg" + for d in $pgdirs + do + if test -f $d/include/libpq-fe.h + then + use_dlz_postgres=$d/include + use_dlz_postgres_lib=$d/lib + break + fi + done +fi + +if test "$use_dlz_postgres" = "yes/include" +then + # Still no joy, give up + + AC_MSG_RESULT(not found) + AC_MSG_ERROR( +[No pg_config and PostgreSQL was not found in any of $pgdirs; use --with-dlz-postgres=/path or put pg_config in your path]) +fi + +case "$use_dlz_postgres" in + no) + AC_MSG_RESULT(no) + ;; + *) + DLZ_ADD_DRIVER(POSTGRES, dlz_postgres_driver, + [-I$use_dlz_postgres], + [-L$use_dlz_postgres_lib -lpq]) + + AC_MSG_RESULT( +[using PostgreSQL from $use_dlz_postgres_lib and $use_dlz_postgres]) + ;; +esac + + +# +# Was --with-dlz-mysql specified? +# + +AC_MSG_CHECKING(for MySQL DLZ driver) +AC_ARG_WITH(dlz_mysql, + AS_HELP_STRING([--with-dlz-mysql[=PATH]], + [Build with MySQL DLZ driver [yes|no|path]. + (Required to use MySQL with DLZ)]), + use_dlz_mysql="$withval", use_dlz_mysql="no") + +mysql_include="" +mysql_lib="" +if test "$use_dlz_mysql" = "yes" +then + AC_CHECK_PROGS(MYSQL_CONFIG, mysql_config) + if test -n "$MYSQL_CONFIG" + then + mysql_include=`${MYSQL_CONFIG} --include` + mysql_lib=`${MYSQL_CONFIG} --libs` + use_dlz_mysql="config" + + else + # User did not specify a path - guess it + mysqldirs="/usr /usr/local /usr/local/mysql /usr/pkg" + for d in $mysqldirs + do + if test -f $d/include/mysql/mysql.h + then + use_dlz_mysql=$d + mysql_include=$d/include/mysql + break + elif test -f $d/include/mysql.h + then + use_dlz_mysql=$d + mysql_include=$d/include + break + fi + done + fi +elif test "$use_dlz_mysql" != "no" +then + d=$use_dlz_mysql + if test -f $d/include/mysql/mysql.h + then + mysql_include=$d/include/mysql + elif test -f $d/include/mysql.h + then + mysql_include=$d/include + fi +fi + +if test "$use_dlz_mysql" = "yes" +then + AC_MSG_RESULT(not found) + AC_MSG_ERROR( +[MySQL was not found in any of $mysqldirs; use --with-dlz-mysql=/path]) +fi + +case "$use_dlz_mysql" in + no) + AC_MSG_RESULT(no) + ;; + config) + DLZ_ADD_DRIVER(MYSQL, dlz_mysql_driver, + [${mysql_include}], + [${mysql_lib}]) + + AC_MSG_RESULT( +[using mysql with libs ${mysql_lib} and includes ${mysql_include}]) + ;; + *) + if test -d "$use_dlz_mysql/lib/mysql" + then + mysql_lib="$use_dlz_mysql/lib/mysql" + else + mysql_lib="$use_dlz_mysql/lib" + fi + DLZ_ADD_DRIVER(MYSQL, dlz_mysql_driver, + [-I${mysql_include}], + [-L${mysql_lib} -lmysqlclient -lz -lcrypt -lm]) + + AC_MSG_RESULT( +[using mysql from ${mysql_lib} and ${mysql_include}]) + ;; +esac + + +# +# Was --with-dlz-bdb specified? +# + +AC_MSG_CHECKING(for Berkeley DB DLZ driver...) +AC_ARG_WITH(dlz_bdb, + AS_HELP_STRING([--with-dlz-bdb[=PATH]], + [Build with Berkeley DB DLZ driver [yes|no|path]. + (Required to use Berkeley DB with DLZ)]), + use_dlz_bdb="$withval", use_dlz_bdb="no") + +case "$use_dlz_bdb" in + no) + AC_MSG_RESULT(no) + ;; + *) + if test "$use_dlz_bdb" = "yes" + then + # User did not specify a path - guess directories + bdbdirs="/usr/local /usr/pkg /usr" + elif test -d "$use_dlz_bdb" + then + # User specified directory and it exists + bdbdirs="$use_dlz_bdb" + else + AC_MSG_RESULT(not found) + AC_MSG_ERROR([path $use_dlz_bdb does not exist]) + bdbdirs="" + fi + + # Use path we were given or guessed. This is insanely + # complicated because we have to search for a bunch of + # platform-specific variations and have to check + # separately for include and library directories. + + # Set both to yes, so we can check them later + dlz_bdb_inc="yes" + dlz_bdb_libs="yes" + + AC_MSG_RESULT( ) + for dd in $bdbdirs + do + # Skip nonexistent directories + if test ! -d "$dd" + then + continue + fi + + # Check other locations for includes. + # Order is important (sigh). + + bdb_incdirs="/db53 /db51 /db48 /db47 /db46 /db45 /db44 /db43 /db42 /db41 /db4 /db" + # include a blank element first + for d in "" $bdb_incdirs + do + if test -f "$dd/include${d}/db.h" + then + dlz_bdb_inc="-I$dd/include${d}" + break + fi + done + + # Give up on this directory if we couldn't + # find the include subdir + + if test "$dlz_bdb_inc" = "yes" + then + continue + fi + + # Look for libname other than libdb.so. + # Order is important (sigh). + + bdb_libnames="db53 db-5.3 db51 db-5.1 db48 db-4.8 db47 db-4.7 db46 db-4.6 db45 db-4.5 db44 db-4.4 db43 db-4.3 db42 db-4.2 db41 db-4.1 db" + for d in $bdb_libnames + do + if test "$dd" = "/usr" + then + AC_CHECK_LIB($d, db_create, dlz_bdb_libs="-l${d}") + if test $dlz_bdb_libs != "yes" + then + break + fi + elif test -f "$dd/lib/lib${d}.so" + then + dlz_bdb_libs="-L${dd}/lib -l${d}" + break + fi + done + + # If we found both incdir and lib, we're done + if test "$dlz_bdb_libs" != "yes" + then + break + fi + + # Otherwise, we're starting over + + dlz_bdb_inc="yes" + dlz_bdb_libs="yes" + done + + # Done searching, now make sure we got everything. + + if test "$dlz_bdb_inc" = "yes" + then + AC_MSG_ERROR([could not find Berkeley DB include directory]) + fi + + if test "$dlz_bdb_libs" = "yes" + then + AC_MSG_RESULT(not found) + AC_MSG_ERROR([could not find Berkeley DB library]) + fi + + DLZ_ADD_DRIVER(BDB, dlz_bdb_driver dlz_bdbhpt_driver, + [$dlz_bdb_inc], [$dlz_bdb_libs]) + + AC_MSG_RESULT([using Berkeley DB: $dlz_bdb_inc $dlz_bdb_libs]) + + AC_CONFIG_FILES([contrib/dlz/bin/dlzbdb/Makefile]) + ;; +esac + + +# +# Was --with-dlz-filesystem specified? +# + +AC_MSG_CHECKING(for file system DLZ driver) +AC_ARG_WITH(dlz_filesystem, + AS_HELP_STRING([--with-dlz-filesystem[=ARG]], + [Build with filesystem DLZ driver [yes|no]. + (Required to use file system driver with DLZ)]), + use_dlz_filesystem="$withval", use_dlz_filesystem="no") + +case "$use_dlz_filesystem" in + no) + AC_MSG_RESULT(no) + ;; + *) + DLZ_ADD_DRIVER(FILESYSTEM, dlz_filesystem_driver) + DLZ_SYSTEM_TEST=filesystem + AC_MSG_RESULT(yes) + ;; +esac + + +# +# Was --with-dlz-ldap specified? +# + +AC_MSG_CHECKING(for LDAP DLZ driver) +AC_ARG_WITH(dlz_ldap, + AS_HELP_STRING([--with-dlz-ldap[=PATH]], + [Build with LDAP DLZ driver [yes|no|path]. + (Required to use LDAP with DLZ)]), + use_dlz_ldap="$withval", use_dlz_ldap="no") + +if test "$use_dlz_ldap" = "yes" +then + # User did not specify a path - guess it + ldapdirs="/usr /usr/local /usr/pkg" + for d in $ldapdirs + do + if test -f $d/include/ldap.h + then + use_dlz_ldap=$d + break + fi + done +fi + +if test "$use_dlz_ldap" = "yes" +then + AC_MSG_RESULT(not found) + AC_MSG_ERROR( +[LDAP headers were not found in any of $ldapdirs; use --with-dlz-ldap=/path]) +fi + +case "$use_dlz_ldap" in + no) + AC_MSG_RESULT(no) + ;; + *) + DLZ_ADD_DRIVER(LDAP, dlz_ldap_driver, + [-I$use_dlz_ldap/include], + [-L$use_dlz_ldap/lib -lldap -llber]) + + AC_MSG_RESULT( +[using LDAP from $use_dlz_ldap/lib and $use_dlz_ldap/include]) + ;; +esac + + +# +# Was --with-dlz-odbc specified? +# + +AC_MSG_CHECKING(for ODBC DLZ driver) +AC_ARG_WITH(dlz_odbc, + AS_HELP_STRING([--with-dlz-odbc[=PATH]], + [Build with ODBC DLZ driver [yes|no|path]. + (Required to use ODBC with DLZ)]), + use_dlz_odbc="$withval", use_dlz_odbc="no") + +if test "$use_dlz_odbc" = "yes" +then + # User did not specify a path - guess it + libodbc_found=no + sql_h_found=no + AC_CHECK_HEADER(sql.h, sql_h_found=yes) + AC_CHECK_LIB(odbc, SQLConnect, libodbc_found=yes) + + if test $libodbc_found = "yes" -o $sql_h_found = "yes" + then + use_dlz_odbc=system + dlz_odbc_include="" + dlz_odbc_libs="-lodbc" + else + odbcdirs="/usr /usr/local /usr/pkg" + for d in $odbcdirs + do + if test -f $d/include/sql.h -a -f $d/lib/libodbc.a + then + use_dlz_odbc=$d + dlz_odbc_include="-I$use_dlz_odbc/include" + dlz_odbc_libs="-L$use_dlz_odbc/lib -lodbc" + break + fi + done + fi +fi + +case "$use_dlz_odbc" in + no) + AC_MSG_RESULT(no) + ;; + yes) + AC_MSG_RESULT(not found) + AC_MSG_ERROR( +[ODBC headers were not found in any of $odbcdirs; use --with-dlz-odbc=/path]) + ;; + *) + DLZ_ADD_DRIVER(ODBC, dlz_odbc_driver, + [$dlz_odbc_include], + [$dlz_odbc_libs]) + + AC_MSG_RESULT([using ODBC from $use_dlz_odbc]) + ;; +esac + + +# +# Was --with-dlz-stub specified? +# + +AC_MSG_CHECKING(for stub DLZ driver) +AC_ARG_WITH(dlz_stub, + AS_HELP_STRING([--with-dlz-stub[=ARG]], + [Build with stub DLZ driver [yes|no]. + (Required to use stub driver with DLZ)]), + use_dlz_stub="$withval", use_dlz_stub="no") + +case "$use_dlz_stub" in + no) + AC_MSG_RESULT(no) + ;; + *) + + DLZ_ADD_DRIVER(STUB, dlz_stub_driver) + + AC_MSG_RESULT(yes) + ;; +esac + +# Add any additional DLZ drivers here. + +# +# Finally, some generic stuff that applies to all drivers, assuming +# we're compiling contrib DLZ drivers at all. +# +if test -n "$CONTRIB_DLZ" +then + CONTRIB_DLZ="-DCONTRIB_DLZ $CONTRIB_DLZ" + + # + # Where to find DLZ driver header files. + # + DLZ_DRIVER_INCLUDES="-I$dlzdir/include $DLZ_DRIVER_INCLUDES" + + # + # Initialization and shutdown wrappers, helper functions. + # + DLZ_DRIVER_SRCS="$dlzdir/dlz_drivers.c $dlzdir/sdlz_helper.c $DLZ_DRIVER_SRCS" + DLZ_DRIVER_OBJS="dlz_drivers.$O sdlz_helper.$O $DLZ_DRIVER_OBJS" +fi diff --git a/contrib/dlz/drivers/dlz_bdb_driver.c b/contrib/dlz/drivers/dlz_bdb_driver.c new file mode 100644 index 0000000..5cce7ce --- /dev/null +++ b/contrib/dlz/drivers/dlz_bdb_driver.c @@ -0,0 +1,780 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +/* + * Copyright (C) 1999-2001, 2016 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/. + */ + +#ifdef DLZ_BDB +#include <db.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +#include <dns/log.h> +#include <dns/result.h> +#include <dns/sdlz.h> + +#include <dlz/dlz_bdb_driver.h> +#include <named/globals.h> + +static dns_sdlzimplementation_t *dlz_bdb = NULL; + +/* should the bdb driver use threads. */ +#define bdb_threads DB_THREAD + +/* BDB database names */ +#define dlz_data "dns_data" +#define dlz_zone "dns_zone" +#define dlz_host "dns_host" +#define dlz_client "dns_client" + +/*% + * This structure contains all the Berkeley DB handles + * for this instance of the BDB driver. + */ + +typedef struct bdb_instance { + DB_ENV *dbenv; /*%< BDB environment */ + DB *data; /*%< dns_data database handle */ + DB *zone; /*%< zone database handle */ + DB *host; /*%< host database handle */ + DB *client; /*%< client database handle */ + isc_mem_t *mctx; /*%< memory context */ +} bdb_instance_t; + +typedef struct parsed_data { + char *zone; + char *host; + char *type; + int ttl; + char *data; +} parsed_data_t; + +/* forward reference */ + +static isc_result_t +bdb_findzone(void *driverarg, void *dbdata, const char *name, + dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo); + +/*% + * Parses the DBT from the Berkeley DB into a parsed_data record + * The parsed_data record should be allocated before and passed into the + * bdb_parse_data function. The char (type & data) fields should not + * be "free"d as that memory is part of the DBT data field. It will be + * "free"d when the DBT is freed. + */ + +static isc_result_t +bdb_parse_data(char *in, parsed_data_t *pd) { + char *endp, *ttlStr; + char *tmp = in; + char *lastchar = (char *)&tmp[strlen(tmp) + 1]; + + /*% + * String should be formatted as: + * zone(a space)host(a space)ttl(a space)type(a space)remaining data + * examples: + * example.com www 10 A 127.0.0.1 + * example.com mail 10 A 127.0.0.2 + * example.com @ 10 MX 20 mail.example.com + */ + + /* save pointer to zone */ + pd->zone = tmp; + + /* find space after zone and change it to a '\0' */ + tmp = strchr(tmp, ' '); + /* verify we found a space */ + if (tmp == NULL) { + return (ISC_R_FAILURE); + } + /* change the space to a null (string terminator) */ + tmp[0] = '\0'; + /* make sure it is safe to increment pointer */ + if (++tmp > lastchar) { + return (ISC_R_FAILURE); + } + + /* save pointer to host */ + pd->host = tmp; + + /* find space after type and change it to a '\0' */ + tmp = strchr(tmp, ' '); + /* verify we found a space */ + if (tmp == NULL) { + return (ISC_R_FAILURE); + } + /* change the space to a null (string terminator) */ + tmp[0] = '\0'; + /* make sure it is safe to increment pointer */ + if (++tmp > lastchar) { + return (ISC_R_FAILURE); + } + + /* save pointer to dns type */ + pd->type = tmp; + + /* find space after type and change it to a '\0' */ + tmp = strchr(tmp, ' '); + /* verify we found a space */ + if (tmp == NULL) { + return (ISC_R_FAILURE); + } + /* change the space to a null (string terminator) */ + tmp[0] = '\0'; + /* make sure it is safe to increment pointer */ + if (++tmp > lastchar) { + return (ISC_R_FAILURE); + } + + /* save pointer to dns ttl */ + ttlStr = tmp; + + /* find space after ttl and change it to a '\0' */ + tmp = strchr(tmp, ' '); + /* verify we found a space */ + if (tmp == NULL) { + return (ISC_R_FAILURE); + } + /* change the space to a null (string terminator) */ + tmp[0] = '\0'; + /* make sure it is safe to increment pointer */ + if (++tmp > lastchar) { + return (ISC_R_FAILURE); + } + + /* save pointer to remainder of DNS data */ + pd->data = tmp; + + /* convert ttl string to integer */ + pd->ttl = strtol(ttlStr, &endp, 10); + if (*endp != '\0' || pd->ttl < 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "BDB driver ttl must be a positive number"); + return (ISC_R_FAILURE); + } + + /* if we get this far everything should have worked. */ + return (ISC_R_SUCCESS); +} + +/* + * DLZ methods + */ + +static isc_result_t +bdb_allowzonexfr(void *driverarg, void *dbdata, const char *name, + const char *client) { + isc_result_t result; + bdb_instance_t *db = (bdb_instance_t *)dbdata; + DBC *client_cursor = NULL; + DBT key, data; + + /* check to see if we are authoritative for the zone first. */ + result = bdb_findzone(driverarg, dbdata, name, NULL, NULL); + if (result != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + memset(&key, 0, sizeof(DBT)); + key.flags = DB_DBT_MALLOC; + key.data = strdup(name); + if (key.data == NULL) { + result = ISC_R_NOMEMORY; + goto xfr_cleanup; + } + key.size = strlen(key.data); + + memset(&data, 0, sizeof(DBT)); + data.flags = DB_DBT_MALLOC; + data.data = strdup(client); + if (data.data == NULL) { + result = ISC_R_NOMEMORY; + goto xfr_cleanup; + } + data.size = strlen(data.data); + + /* get a cursor to loop through zone data */ + if (db->client->cursor(db->client, NULL, &client_cursor, 0) != 0) { + result = ISC_R_FAILURE; + goto xfr_cleanup; + } + + switch (client_cursor->c_get(client_cursor, &key, &data, DB_GET_BOTH)) { + case DB_NOTFOUND: + case DB_SECONDARY_BAD: + result = ISC_R_NOTFOUND; + break; + case 0: + result = ISC_R_SUCCESS; + break; + default: + result = ISC_R_FAILURE; + } + +xfr_cleanup: + + /* free any memory duplicate string in the key field */ + if (key.data != NULL) { + free(key.data); + } + + /* free any memory allocated to the data field. */ + if (data.data != NULL) { + free(data.data); + } + + /* get rid of zone_cursor */ + if (client_cursor != NULL) { + client_cursor->c_close(client_cursor); + } + + return (result); +} + +static isc_result_t +bdb_allnodes(const char *zone, void *driverarg, void *dbdata, + dns_sdlzallnodes_t *allnodes) { + isc_result_t result = ISC_R_NOTFOUND; + bdb_instance_t *db = (bdb_instance_t *)dbdata; + DBC *zone_cursor = NULL; + DBT key, data; + int flags; + int bdbres; + parsed_data_t pd; + char *tmp = NULL, *tmp_zone; + + UNUSED(driverarg); + + memset(&key, 0, sizeof(DBT)); + memset(&data, 0, sizeof(DBT)); + + key.data = tmp_zone = strdup(zone); + + if (key.data == NULL) { + return (ISC_R_NOMEMORY); + } + + key.size = strlen(key.data); + + /* get a cursor to loop through zone data */ + if (db->zone->cursor(db->zone, NULL, &zone_cursor, 0) != 0) { + result = ISC_R_FAILURE; + goto allnodes_cleanup; + } + + flags = DB_SET; + + while ((bdbres = zone_cursor->c_get(zone_cursor, &key, &data, flags)) == + 0) + { + flags = DB_NEXT_DUP; + + tmp = realloc(tmp, data.size + 1); + if (tmp == NULL) { + goto allnodes_cleanup; + } + + strncpy(tmp, data.data, data.size); + tmp[data.size] = '\0'; + + if (bdb_parse_data(tmp, &pd) != ISC_R_SUCCESS) { + goto allnodes_cleanup; + } + + result = dns_sdlz_putnamedrr(allnodes, pd.host, pd.type, pd.ttl, + pd.data); + if (result != ISC_R_SUCCESS) { + goto allnodes_cleanup; + } + } /* end while loop */ + +allnodes_cleanup: + + if (tmp != NULL) { + free(tmp); + } + + /* free any memory duplicate string in the key field */ + if (tmp_zone != NULL) { + free(tmp_zone); + } + + /* get rid of zone_cursor */ + if (zone_cursor != NULL) { + zone_cursor->c_close(zone_cursor); + } + + return (result); +} + +/*% + * Performs BDB cleanup. + * Used by bdb_create if there is an error starting up. + * Used by bdb_destroy when the driver is shutting down. + */ + +static void +bdb_cleanup(bdb_instance_t *db) { + isc_mem_t *mctx; + + /* close databases */ + if (db->data != NULL) { + db->data->close(db->data, 0); + } + if (db->host != NULL) { + db->host->close(db->host, 0); + } + if (db->zone != NULL) { + db->zone->close(db->zone, 0); + } + if (db->client != NULL) { + db->client->close(db->client, 0); + } + + /* close environment */ + if (db->dbenv != NULL) { + db->dbenv->close(db->dbenv, 0); + } + + /* cleanup memory */ + if (db->mctx != NULL) { + /* save mctx for later */ + mctx = db->mctx; + /* return, and detach the memory */ + isc_mem_putanddetach(&mctx, db, sizeof(bdb_instance_t)); + } +} + +static isc_result_t +bdb_findzone(void *driverarg, void *dbdata, const char *name, + dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo) { + isc_result_t result; + bdb_instance_t *db = (bdb_instance_t *)dbdata; + DBC *zone_cursor = NULL; + DBT key, data; + + UNUSED(driverarg); + UNUSED(methods); + UNUSED(clientinfo); + + memset(&key, 0, sizeof(DBT)); + memset(&data, 0, sizeof(DBT)); + data.flags = DB_DBT_MALLOC; + + key.data = strdup(name); + + if (key.data == NULL) { + return (ISC_R_NOMEMORY); + } + + key.size = strlen(key.data); + + /* get a cursor to loop through zone data */ + if (db->zone->cursor(db->zone, NULL, &zone_cursor, 0) != 0) { + result = ISC_R_NOTFOUND; + goto findzone_cleanup; + } + + switch (zone_cursor->c_get(zone_cursor, &key, &data, DB_SET)) { + case DB_NOTFOUND: + case DB_SECONDARY_BAD: + result = ISC_R_NOTFOUND; + break; + case 0: + result = ISC_R_SUCCESS; + break; + default: + result = ISC_R_FAILURE; + } + +findzone_cleanup: + + /* free any memory duplicate string in the key field */ + if (key.data != NULL) { + free(key.data); + } + + /* free any memory allocated to the data field. */ + if (data.data != NULL) { + free(data.data); + } + + /* get rid of zone_cursor */ + if (zone_cursor != NULL) { + zone_cursor->c_close(zone_cursor); + } + + return (result); +} + +static isc_result_t +bdb_lookup(const char *zone, const char *name, void *driverarg, void *dbdata, + dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + isc_result_t result = ISC_R_NOTFOUND; + bdb_instance_t *db = (bdb_instance_t *)dbdata; + DBC *zone_cursor = NULL; + DBC *host_cursor = NULL; + DBC *join_cursor = NULL; + DBT key, data; + DBC *cur_arr[3]; + int bdbres; + parsed_data_t pd; + char *tmp_zone, *tmp_host = NULL; + char *tmp = NULL; + + UNUSED(driverarg); + UNUSED(methods); + UNUSED(clientinfo); + + memset(&key, 0, sizeof(DBT)); + memset(&data, 0, sizeof(DBT)); + + /* set zone key */ + key.data = tmp_zone = strdup(zone); + if (key.data == NULL) { + result = ISC_R_NOMEMORY; + goto lookup_cleanup; + } + key.size = strlen(key.data); + + /* get a cursor to loop through zone data */ + if (db->zone->cursor(db->zone, NULL, &zone_cursor, 0) != 0) { + result = ISC_R_FAILURE; + goto lookup_cleanup; + } + + /* initialize zone_cursor with zone_key */ + if (zone_cursor->c_get(zone_cursor, &key, &data, DB_SET) != 0) { + result = ISC_R_NOTFOUND; + goto lookup_cleanup; + } + + /* set host key */ + key.data = tmp_host = strdup(name); + if (key.data == NULL) { + result = ISC_R_NOMEMORY; + goto lookup_cleanup; + } + key.size = strlen(key.data); + + /* get a cursor to loop through host data */ + if (db->host->cursor(db->host, NULL, &host_cursor, 0) != 0) { + result = ISC_R_FAILURE; + goto lookup_cleanup; + } + + /* initialize host_cursor with host_key */ + if (host_cursor->c_get(host_cursor, &key, &data, DB_SET) != 0) { + result = ISC_R_NOTFOUND; + goto lookup_cleanup; + } + + cur_arr[0] = zone_cursor; + cur_arr[1] = host_cursor; + cur_arr[2] = NULL; + + db->data->join(db->data, cur_arr, &join_cursor, 0); + + while ((bdbres = join_cursor->c_get(join_cursor, &key, &data, 0)) == 0) + { + tmp = realloc(tmp, data.size + 1); + if (tmp == NULL) { + goto lookup_cleanup; + } + + strncpy(tmp, data.data, data.size); + tmp[data.size] = '\0'; + + if (bdb_parse_data(tmp, &pd) != ISC_R_SUCCESS) { + goto lookup_cleanup; + } + + result = dns_sdlz_putrr(lookup, pd.type, pd.ttl, pd.data); + + if (result != ISC_R_SUCCESS) { + goto lookup_cleanup; + } + } /* end while loop */ + +lookup_cleanup: + + if (tmp != NULL) { + free(tmp); + } + if (tmp_zone != NULL) { + free(tmp_zone); + } + if (tmp_host != NULL) { + free(tmp_host); + } + + /* get rid of the joined cusor */ + if (join_cursor != NULL) { + join_cursor->c_close(join_cursor); + } + + /* get rid of zone_cursor */ + if (zone_cursor != NULL) { + zone_cursor->c_close(zone_cursor); + } + + /* get rid of host_cursor */ + if (host_cursor != NULL) { + host_cursor->c_close(host_cursor); + } + + return (result); +} + +/*% Initializes, sets flags and then opens Berkeley databases. */ + +static isc_result_t +bdb_opendb(DB_ENV *db_env, DBTYPE db_type, DB **db, const char *db_name, + char *db_file, int flags) { + int result; + + /* Initialize the database. */ + if ((result = db_create(db, db_env, 0)) != 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "BDB could not initialize %s database. " + "BDB error: %s", + db_name, db_strerror(result)); + return (ISC_R_FAILURE); + } + + /* set database flags. */ + if ((result = (*db)->set_flags(*db, flags)) != 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "BDB could not set flags for %s database. " + "BDB error: %s", + db_name, db_strerror(result)); + return (ISC_R_FAILURE); + } + + /* open the database. */ + if ((result = (*db)->open(*db, NULL, db_file, db_name, db_type, + DB_RDONLY | bdb_threads, 0)) != 0) + { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "BDB could not open %s database in %s. " + "BDB error: %s", + db_name, db_file, db_strerror(result)); + return (ISC_R_FAILURE); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +bdb_create(const char *dlzname, unsigned int argc, char *argv[], + void *driverarg, void **dbdata) { + isc_result_t result; + int bdbres; + bdb_instance_t *db = NULL; + + UNUSED(dlzname); + UNUSED(driverarg); + + /* verify we have 3 arg's passed to the driver */ + if (argc != 3) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Berkeley DB driver requires at least " + "2 command line args."); + return (ISC_R_FAILURE); + } + + /* allocate and zero memory for driver structure */ + db = isc_mem_get(named_g_mctx, sizeof(bdb_instance_t)); + memset(db, 0, sizeof(bdb_instance_t)); + + /* attach to the memory context */ + isc_mem_attach(named_g_mctx, &db->mctx); + + /* create BDB environment + * Basically BDB allocates and assigns memory to db->dbenv + */ + bdbres = db_env_create(&db->dbenv, 0); + if (bdbres != 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "BDB environment could not be created. " + "BDB error: %s", + db_strerror(bdbres)); + result = ISC_R_FAILURE; + goto init_cleanup; + } + + /* open BDB environment */ + bdbres = db->dbenv->open( + db->dbenv, argv[1], + DB_INIT_CDB | DB_INIT_MPOOL | bdb_threads | DB_CREATE, 0); + if (bdbres != 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "BDB environment at '%s' could not be opened. " + "BDB error: %s", + argv[1], db_strerror(bdbres)); + result = ISC_R_FAILURE; + goto init_cleanup; + } + + /* open dlz_data database. */ + result = bdb_opendb(db->dbenv, DB_UNKNOWN, &db->data, dlz_data, argv[2], + 0); + if (result != ISC_R_SUCCESS) { + goto init_cleanup; + } + + /* open dlz_host database. */ + result = bdb_opendb(db->dbenv, DB_UNKNOWN, &db->host, dlz_host, argv[2], + DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) { + goto init_cleanup; + } + + /* open dlz_zone database. */ + result = bdb_opendb(db->dbenv, DB_UNKNOWN, &db->zone, dlz_zone, argv[2], + DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) { + goto init_cleanup; + } + + /* open dlz_client database. */ + result = bdb_opendb(db->dbenv, DB_UNKNOWN, &db->client, dlz_client, + argv[2], DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) { + goto init_cleanup; + } + + /* associate the host secondary database with the primary database */ + bdbres = db->data->associate(db->data, NULL, db->host, NULL, 0); + if (bdbres != 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "BDB could not associate %s database with %s. " + "BDB error: %s", + dlz_host, dlz_data, db_strerror(bdbres)); + result = ISC_R_FAILURE; + goto init_cleanup; + } + + /* associate the zone secondary database with the primary database */ + bdbres = db->data->associate(db->data, NULL, db->zone, NULL, 0); + if (bdbres != 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "BDB could not associate %s database with %s. " + "BDB error: %s", + dlz_zone, dlz_data, db_strerror(bdbres)); + result = ISC_R_FAILURE; + goto init_cleanup; + } + + *dbdata = db; + + return (ISC_R_SUCCESS); + +init_cleanup: + + bdb_cleanup(db); + return (result); +} + +static void +bdb_destroy(void *driverarg, void *dbdata) { + UNUSED(driverarg); + + bdb_cleanup((bdb_instance_t *)dbdata); +} + +/* bdb_authority not needed as authority data is returned by lookup */ +static dns_sdlzmethods_t dlz_bdb_methods = { + bdb_create, + bdb_destroy, + bdb_findzone, + bdb_lookup, + NULL, + bdb_allnodes, + bdb_allowzonexfr, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +/*% + * Wrapper around dns_sdlzregister(). + */ +isc_result_t +dlz_bdb_init(void) { + isc_result_t result; + + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Registering DLZ bdb driver."); + + result = dns_sdlzregister("bdb", &dlz_bdb_methods, NULL, + DNS_SDLZFLAG_RELATIVEOWNER | + DNS_SDLZFLAG_RELATIVERDATA | + DNS_SDLZFLAG_THREADSAFE, + named_g_mctx, &dlz_bdb); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "dns_sdlzregister() failed: %s", + isc_result_totext(result)); + result = ISC_R_UNEXPECTED; + } + + return (result); +} + +/*% + * Wrapper around dns_sdlzunregister(). + */ +void +dlz_bdb_clear(void) { + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Unregistering DLZ bdb driver."); + + if (dlz_bdb != NULL) { + dns_sdlzunregister(&dlz_bdb); + } +} + +#endif /* ifdef DLZ_BDB */ diff --git a/contrib/dlz/drivers/dlz_bdbhpt_driver.c b/contrib/dlz/drivers/dlz_bdbhpt_driver.c new file mode 100644 index 0000000..fefe99d --- /dev/null +++ b/contrib/dlz/drivers/dlz_bdbhpt_driver.c @@ -0,0 +1,849 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +/* + * Copyright (C) 1999-2001, 2016 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/. + */ + +#ifdef DLZ_BDB +#include <db.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +#include <dns/log.h> +#include <dns/result.h> +#include <dns/sdlz.h> + +#include <dlz/dlz_bdbhpt_driver.h> +#include <named/globals.h> + +static dns_sdlzimplementation_t *dlz_bdbhpt = NULL; + +/* should the bdb driver use threads. */ +#define bdbhpt_threads DB_THREAD + +/* bdbhpt database names */ +#define dlz_data "dns_data" +#define dlz_zone "dns_zone" +#define dlz_xfr "dns_xfr" +#define dlz_client "dns_client" + +/* This structure contains all the Berkeley DB handles + * for this instance of the bdbhpt driver. + */ + +typedef struct bdbhpt_instance { + DB_ENV *dbenv; /*%< bdbhpt environment */ + DB *data; /*%< dns_data database handle */ + DB *zone; /*%< zone database handle */ + DB *xfr; /*%< zone xfr database handle */ + DB *client; /*%< client database handle */ + isc_mem_t *mctx; /*%< memory context */ +} bdbhpt_instance_t; + +typedef struct bdbhpt_parsed_data { + char *host; + char *type; + int ttl; + char *data; +} bdbhpt_parsed_data_t; + +/* forward reference */ + +static isc_result_t +bdbhpt_findzone(void *driverarg, void *dbdata, const char *name, + dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo); + +/*% + * Reverses a string in place. + */ + +static char * +bdbhpt_strrev(char *str) { + char *p1, *p2; + + if (!str || !*str) { + return (str); + } + for (p1 = str, p2 = str + strlen(str) - 1; p2 > p1; ++p1, --p2) { + *p1 ^= *p2; + *p2 ^= *p1; + *p1 ^= *p2; + } + return (str); +} + +/*% + * Parses the DBT from the Berkeley DB into a parsed_data record + * The parsed_data record should be allocated before and passed into the + * bdbhpt_parse_data function. The char (type & data) fields should not + * be "free"d as that memory is part of the DBT data field. It will be + * "free"d when the DBT is freed. + */ + +static isc_result_t +bdbhpt_parse_data(char *in, bdbhpt_parsed_data_t *pd) { + char *endp, *ttlStr; + char *tmp = in; + char *lastchar = (char *)&tmp[strlen(tmp)]; + + /*% + * String should be formatted as: + * replication_id + * (a space) + * host_name + * (a space) + * ttl + * (a space) + * type + * (a space) + * remaining data + * + * examples: + * + * 9191 host 10 A 127.0.0.1 + * server1_212 host 10 A 127.0.0.2 + * {xxxx-xxxx-xxxx-xxxx-xxxx} host 10 MX 20 mail.example.com + */ + + /* + * we don't need the replication id, so don't + * bother saving a pointer to it. + */ + + /* find space after replication id */ + tmp = strchr(tmp, ' '); + /* verify we found a space */ + if (tmp == NULL) { + return (ISC_R_FAILURE); + } + /* make sure it is safe to increment pointer */ + if (++tmp > lastchar) { + return (ISC_R_FAILURE); + } + + /* save pointer to host */ + pd->host = tmp; + + /* find space after host and change it to a '\0' */ + tmp = strchr(tmp, ' '); + /* verify we found a space */ + if (tmp == NULL) { + return (ISC_R_FAILURE); + } + /* change the space to a null (string terminator) */ + tmp[0] = '\0'; + /* make sure it is safe to increment pointer */ + if (++tmp > lastchar) { + return (ISC_R_FAILURE); + } + + /* save pointer to ttl string */ + ttlStr = tmp; + + /* find space after ttl and change it to a '\0' */ + tmp = strchr(tmp, ' '); + /* verify we found a space */ + if (tmp == NULL) { + return (ISC_R_FAILURE); + } + /* change the space to a null (string terminator) */ + tmp[0] = '\0'; + /* make sure it is safe to increment pointer */ + if (++tmp > lastchar) { + return (ISC_R_FAILURE); + } + + /* save pointer to dns type */ + pd->type = tmp; + + /* find space after type and change it to a '\0' */ + tmp = strchr(tmp, ' '); + /* verify we found a space */ + if (tmp == NULL) { + return (ISC_R_FAILURE); + } + /* change the space to a null (string terminator) */ + tmp[0] = '\0'; + /* make sure it is safe to increment pointer */ + if (++tmp > lastchar) { + return (ISC_R_FAILURE); + } + + /* save pointer to remainder of DNS data */ + pd->data = tmp; + + /* convert ttl string to integer */ + pd->ttl = strtol(ttlStr, &endp, 10); + if (*endp != '\0' || pd->ttl < 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "bdbhpt driver ttl must be a positive number"); + return (ISC_R_FAILURE); + } + + /* if we get this far everything should have worked. */ + return (ISC_R_SUCCESS); +} + +/* + * DLZ methods + */ + +static isc_result_t +bdbhpt_allowzonexfr(void *driverarg, void *dbdata, const char *name, + const char *client) { + isc_result_t result; + bdbhpt_instance_t *db = (bdbhpt_instance_t *)dbdata; + DBT key, data; + + /* check to see if we are authoritative for the zone first. */ + result = bdbhpt_findzone(driverarg, dbdata, name, NULL, NULL); + if (result != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + memset(&key, 0, sizeof(DBT)); + key.flags = DB_DBT_MALLOC; + key.data = strdup(name); + if (key.data == NULL) { + result = ISC_R_NOMEMORY; + goto xfr_cleanup; + } + key.size = strlen(key.data); + + memset(&data, 0, sizeof(DBT)); + data.flags = DB_DBT_MALLOC; + data.data = strdup(client); + if (data.data == NULL) { + result = ISC_R_NOMEMORY; + goto xfr_cleanup; + } + data.size = strlen(data.data); + + switch (db->client->get(db->client, NULL, &key, &data, DB_GET_BOTH)) { + case DB_NOTFOUND: + result = ISC_R_NOTFOUND; + break; + case 0: + result = ISC_R_SUCCESS; + break; + default: + result = ISC_R_FAILURE; + } + +xfr_cleanup: + + /* free any memory duplicate string in the key field */ + if (key.data != NULL) { + free(key.data); + } + + /* free any memory allocated to the data field. */ + if (data.data != NULL) { + free(data.data); + } + + return (result); +} + +/*% + * BDB does not allow a secondary index on a database that allows + * duplicates. We have a few options: + * + * 1) kill speed by having lookup method use a secondary db which + * is associated to the primary DB with the DNS data. Then have + * another secondary db for zone transfer which also points to + * the dns_data primary. NO - The point of this driver is + * lookup performance. + * + * 2) Blow up database size by storing DNS data twice. Once for + * the lookup (dns_data) database, and a second time for the zone + * transfer (dns_xfr) database. NO - That would probably require + * a larger cache to provide good performance. Also, that would + * make the DB larger on disk potentially slowing it as well. + * + * 3) Loop through the dns_xfr database with a cursor to get + * all the different hosts in a zone. Then use the zone & host + * together to lookup the data in the dns_data database. YES - + * This may slow down zone xfr's a little, but that's ok they + * don't happen as often and don't need to be as fast. We can + * also use this table when deleting a zone (The BDB driver + * is read only - the delete would be used during replication + * updates by a separate process). + */ + +static isc_result_t +bdbhpt_allnodes(const char *zone, void *driverarg, void *dbdata, + dns_sdlzallnodes_t *allnodes) { + isc_result_t result = ISC_R_NOTFOUND; + bdbhpt_instance_t *db = (bdbhpt_instance_t *)dbdata; + DBC *xfr_cursor = NULL; + DBC *dns_cursor = NULL; + DBT xfr_key, xfr_data, dns_key, dns_data; + int xfr_flags; + int dns_flags; + int bdbhptres; + bdbhpt_parsed_data_t pd; + char *tmp = NULL, *tmp_zone, *tmp_zone_host = NULL; + + UNUSED(driverarg); + + memset(&xfr_key, 0, sizeof(DBT)); + memset(&xfr_data, 0, sizeof(DBT)); + memset(&dns_key, 0, sizeof(DBT)); + memset(&dns_data, 0, sizeof(DBT)); + + xfr_key.data = tmp_zone = strdup(zone); + if (xfr_key.data == NULL) { + return (ISC_R_NOMEMORY); + } + + xfr_key.size = strlen(xfr_key.data); + + /* get a cursor to loop through dns_xfr table */ + if (db->xfr->cursor(db->xfr, NULL, &xfr_cursor, 0) != 0) { + result = ISC_R_FAILURE; + goto allnodes_cleanup; + } + + /* get a cursor to loop through dns_data table */ + if (db->data->cursor(db->data, NULL, &dns_cursor, 0) != 0) { + result = ISC_R_FAILURE; + goto allnodes_cleanup; + } + + xfr_flags = DB_SET; + + /* loop through xfr table for specified zone. */ + while ((bdbhptres = xfr_cursor->c_get(xfr_cursor, &xfr_key, &xfr_data, + xfr_flags)) == 0) + { + xfr_flags = DB_NEXT_DUP; + + /* +1 to allow for space between zone and host names */ + dns_key.size = xfr_data.size + xfr_key.size + 1; + + /* +1 to allow for null term at end of string. */ + dns_key.data = tmp_zone_host = malloc(dns_key.size + 1); + if (dns_key.data == NULL) { + goto allnodes_cleanup; + } + + /* + * construct search key for dns_data. + * zone_name(a space)host_name + */ + strcpy(dns_key.data, zone); + strcat(dns_key.data, " "); + strncat(dns_key.data, xfr_data.data, xfr_data.size); + + dns_flags = DB_SET; + + while ((bdbhptres = dns_cursor->c_get(dns_cursor, &dns_key, + &dns_data, dns_flags)) == + 0) + { + dns_flags = DB_NEXT_DUP; + + /* +1 to allow for null term at end of string. */ + tmp = realloc(tmp, dns_data.size + 1); + if (tmp == NULL) { + goto allnodes_cleanup; + } + + /* copy data to tmp string, and append null term. */ + strncpy(tmp, dns_data.data, dns_data.size); + tmp[dns_data.size] = '\0'; + + /* split string into dns data parts. */ + if (bdbhpt_parse_data(tmp, &pd) != ISC_R_SUCCESS) { + goto allnodes_cleanup; + } + + result = dns_sdlz_putnamedrr(allnodes, pd.host, pd.type, + pd.ttl, pd.data); + if (result != ISC_R_SUCCESS) { + goto allnodes_cleanup; + } + } /* end inner while loop */ + + /* clean up memory */ + if (tmp_zone_host != NULL) { + free(tmp_zone_host); + tmp_zone_host = NULL; + } + } /* end outer while loop */ + +allnodes_cleanup: + + /* free any memory */ + if (tmp != NULL) { + free(tmp); + } + + if (tmp_zone_host != NULL) { + free(tmp_zone_host); + } + + if (tmp_zone != NULL) { + free(tmp_zone); + } + + /* get rid of cursors */ + if (xfr_cursor != NULL) { + xfr_cursor->c_close(xfr_cursor); + } + + if (dns_cursor != NULL) { + dns_cursor->c_close(dns_cursor); + } + + return (result); +} + +/*% + * Performs bdbhpt cleanup. + * Used by bdbhpt_create if there is an error starting up. + * Used by bdbhpt_destroy when the driver is shutting down. + */ + +static void +bdbhpt_cleanup(bdbhpt_instance_t *db) { + isc_mem_t *mctx; + + /* close databases */ + if (db->data != NULL) { + db->data->close(db->data, 0); + } + if (db->xfr != NULL) { + db->xfr->close(db->xfr, 0); + } + if (db->zone != NULL) { + db->zone->close(db->zone, 0); + } + if (db->client != NULL) { + db->client->close(db->client, 0); + } + + /* close environment */ + if (db->dbenv != NULL) { + db->dbenv->close(db->dbenv, 0); + } + + /* cleanup memory */ + if (db->mctx != NULL) { + /* save mctx for later */ + mctx = db->mctx; + /* return, and detach the memory */ + isc_mem_putanddetach(&mctx, db, sizeof(bdbhpt_instance_t)); + } +} + +static isc_result_t +bdbhpt_findzone(void *driverarg, void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + isc_result_t result; + bdbhpt_instance_t *db = (bdbhpt_instance_t *)dbdata; + DBT key, data; + + UNUSED(driverarg); + UNUSED(methods); + UNUSED(clientinfo); + + memset(&key, 0, sizeof(DBT)); + memset(&data, 0, sizeof(DBT)); + data.flags = DB_DBT_MALLOC; + + key.data = strdup(name); + + if (key.data == NULL) { + return (ISC_R_NOMEMORY); + } + + /* + * reverse string to take advantage of BDB locality of reference + * if we need further lookups because the zone doesn't match the + * first time. + */ + key.data = bdbhpt_strrev(key.data); + key.size = strlen(key.data); + + switch (db->zone->get(db->zone, NULL, &key, &data, 0)) { + case DB_NOTFOUND: + result = ISC_R_NOTFOUND; + break; + case 0: + result = ISC_R_SUCCESS; + break; + default: + result = ISC_R_FAILURE; + } + + /* free any memory duplicate string in the key field */ + if (key.data != NULL) { + free(key.data); + } + + /* free any memory allocated to the data field. */ + if (data.data != NULL) { + free(data.data); + } + + return (result); +} + +static isc_result_t +bdbhpt_lookup(const char *zone, const char *name, void *driverarg, void *dbdata, + dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + isc_result_t result = ISC_R_NOTFOUND; + bdbhpt_instance_t *db = (bdbhpt_instance_t *)dbdata; + DBC *data_cursor = NULL; + DBT key, data; + int bdbhptres; + int flags; + + bdbhpt_parsed_data_t pd; + char *tmp = NULL; + char *keyStr = NULL; + + UNUSED(driverarg); + UNUSED(methods); + UNUSED(clientinfo); + + memset(&key, 0, sizeof(DBT)); + memset(&data, 0, sizeof(DBT)); + + key.size = strlen(zone) + strlen(name) + 1; + + /* allocate mem for key */ + key.data = keyStr = malloc((key.size + 1) * sizeof(char)); + + if (keyStr == NULL) { + return (ISC_R_NOMEMORY); + } + + strcpy(keyStr, zone); + strcat(keyStr, " "); + strcat(keyStr, name); + + /* get a cursor to loop through data */ + if (db->data->cursor(db->data, NULL, &data_cursor, 0) != 0) { + result = ISC_R_FAILURE; + goto lookup_cleanup; + } + + result = ISC_R_NOTFOUND; + + flags = DB_SET; + while ((bdbhptres = data_cursor->c_get(data_cursor, &key, &data, + flags)) == 0) + { + flags = DB_NEXT_DUP; + tmp = realloc(tmp, data.size + 1); + if (tmp == NULL) { + goto lookup_cleanup; + } + + strncpy(tmp, data.data, data.size); + tmp[data.size] = '\0'; + + if (bdbhpt_parse_data(tmp, &pd) != ISC_R_SUCCESS) { + goto lookup_cleanup; + } + + result = dns_sdlz_putrr(lookup, pd.type, pd.ttl, pd.data); + + if (result != ISC_R_SUCCESS) { + goto lookup_cleanup; + } + } /* end while loop */ + +lookup_cleanup: + + /* get rid of cursor */ + if (data_cursor != NULL) { + data_cursor->c_close(data_cursor); + } + + free(keyStr); + if (tmp != NULL) { + free(tmp); + } + + return (result); +} + +/*% Initializes, sets flags and then opens Berkeley databases. */ + +static isc_result_t +bdbhpt_opendb(DB_ENV *db_env, DBTYPE db_type, DB **db, const char *db_name, + char *db_file, int flags) { + int result; + + /* Initialize the database. */ + if ((result = db_create(db, db_env, 0)) != 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "bdbhpt could not initialize %s database. " + "bdbhpt error: %s", + db_name, db_strerror(result)); + return (ISC_R_FAILURE); + } + + /* set database flags. */ + if ((result = (*db)->set_flags(*db, flags)) != 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "bdbhpt could not set flags for %s database. " + "bdbhpt error: %s", + db_name, db_strerror(result)); + return (ISC_R_FAILURE); + } + + /* open the database. */ + if ((result = (*db)->open(*db, NULL, db_file, db_name, db_type, + DB_RDONLY | bdbhpt_threads, 0)) != 0) + { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "bdbhpt could not open %s database in %s. " + "bdbhpt error: %s", + db_name, db_file, db_strerror(result)); + return (ISC_R_FAILURE); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +bdbhpt_create(const char *dlzname, unsigned int argc, char *argv[], + void *driverarg, void **dbdata) { + isc_result_t result; + int bdbhptres; + int bdbFlags = 0; + bdbhpt_instance_t *db = NULL; + + UNUSED(dlzname); + UNUSED(driverarg); + + /* verify we have 4 arg's passed to the driver */ + if (argc != 4) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "bdbhpt driver requires at least " + "3 command line args."); + return (ISC_R_FAILURE); + } + + switch ((char)*argv[1]) { + /* + * Transactional mode. Highest safety - lowest speed. + */ + case 'T': + case 't': + bdbFlags = DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_LOG | + DB_INIT_TXN; + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1), + "bdbhpt driver using transactional mode."); + break; + /* + * Concurrent mode. Lower safety (no rollback) - + * higher speed. + */ + case 'C': + case 'c': + bdbFlags = DB_INIT_CDB | DB_INIT_MPOOL; + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1), + "bdbhpt driver using concurrent mode."); + break; + /* + * Private mode. No inter-process communication & no locking. + * Lowest safety - highest speed. + */ + case 'P': + case 'p': + bdbFlags = DB_PRIVATE | DB_INIT_MPOOL; + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1), + "bdbhpt driver using private mode."); + break; + default: + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "bdbhpt driver requires the operating mode " + "be set to P or C or T. You specified '%s'", + argv[1]); + return (ISC_R_FAILURE); + } + + /* allocate and zero memory for driver structure */ + db = isc_mem_get(named_g_mctx, sizeof(bdbhpt_instance_t)); + memset(db, 0, sizeof(bdbhpt_instance_t)); + + /* attach to the memory context */ + isc_mem_attach(named_g_mctx, &db->mctx); + + /* + * create bdbhpt environment + * Basically bdbhpt allocates and assigns memory to db->dbenv + */ + bdbhptres = db_env_create(&db->dbenv, 0); + if (bdbhptres != 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "bdbhpt environment could not be created. " + "bdbhpt error: %s", + db_strerror(bdbhptres)); + result = ISC_R_FAILURE; + goto init_cleanup; + } + + /* open bdbhpt environment */ + bdbhptres = db->dbenv->open(db->dbenv, argv[2], + bdbFlags | bdbhpt_threads | DB_CREATE, 0); + if (bdbhptres != 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "bdbhpt environment at '%s' could not be opened." + " bdbhpt error: %s", + argv[2], db_strerror(bdbhptres)); + result = ISC_R_FAILURE; + goto init_cleanup; + } + + /* open dlz_data database. */ + result = bdbhpt_opendb(db->dbenv, DB_UNKNOWN, &db->data, dlz_data, + argv[3], DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) { + goto init_cleanup; + } + + /* open dlz_xfr database. */ + result = bdbhpt_opendb(db->dbenv, DB_UNKNOWN, &db->xfr, dlz_xfr, + argv[3], DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) { + goto init_cleanup; + } + + /* open dlz_zone database. */ + result = bdbhpt_opendb(db->dbenv, DB_UNKNOWN, &db->zone, dlz_zone, + argv[3], 0); + if (result != ISC_R_SUCCESS) { + goto init_cleanup; + } + + /* open dlz_client database. */ + result = bdbhpt_opendb(db->dbenv, DB_UNKNOWN, &db->client, dlz_client, + argv[3], DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) { + goto init_cleanup; + } + + *dbdata = db; + + return (ISC_R_SUCCESS); + +init_cleanup: + + bdbhpt_cleanup(db); + return (result); +} + +static void +bdbhpt_destroy(void *driverarg, void *dbdata) { + UNUSED(driverarg); + + bdbhpt_cleanup((bdbhpt_instance_t *)dbdata); +} + +/* + * bdbhpt_authority not needed as authority data is returned by lookup + */ +static dns_sdlzmethods_t dlz_bdbhpt_methods = { + bdbhpt_create, + bdbhpt_destroy, + bdbhpt_findzone, + bdbhpt_lookup, + NULL, + bdbhpt_allnodes, + bdbhpt_allowzonexfr, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +/*% + * Wrapper around dns_sdlzregister(). + */ +isc_result_t +dlz_bdbhpt_init(void) { + isc_result_t result; + + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Registering DLZ bdbhpt driver."); + + result = dns_sdlzregister("bdbhpt", &dlz_bdbhpt_methods, NULL, + DNS_SDLZFLAG_RELATIVEOWNER | + DNS_SDLZFLAG_RELATIVERDATA | + DNS_SDLZFLAG_THREADSAFE, + named_g_mctx, &dlz_bdbhpt); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "dns_sdlzregister() failed: %s", + isc_result_totext(result)); + result = ISC_R_UNEXPECTED; + } + + return (result); +} + +/*% + * Wrapper around dns_sdlzunregister(). + */ +void +dlz_bdbhpt_clear(void) { + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Unregistering DLZ bdbhpt driver."); + + if (dlz_bdbhpt != NULL) { + dns_sdlzunregister(&dlz_bdbhpt); + } +} + +#endif /* ifdef DLZ_BDB */ diff --git a/contrib/dlz/drivers/dlz_dlopen_driver.c b/contrib/dlz/drivers/dlz_dlopen_driver.c new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/dlz/drivers/dlz_dlopen_driver.c diff --git a/contrib/dlz/drivers/dlz_drivers.c b/contrib/dlz/drivers/dlz_drivers.c new file mode 100644 index 0000000..8320bf8 --- /dev/null +++ b/contrib/dlz/drivers/dlz_drivers.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2005 Internet Systems Consortium, Inc. ("ISC") + * 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. + */ + +/*! \file */ + +#include <isc/result.h> + +/* + * Pull in declarations for this module's functions. + */ + +#include <dlz/dlz_drivers.h> + +/* + * Pull in driver-specific stuff. + */ + +#ifdef DLZ_STUB +#include <dlz/dlz_stub_driver.h> +#endif /* ifdef DLZ_STUB */ + +#ifdef DLZ_POSTGRES +#include <dlz/dlz_postgres_driver.h> +#endif /* ifdef DLZ_POSTGRES */ + +#ifdef DLZ_MYSQL +#include <dlz/dlz_mysql_driver.h> +#endif /* ifdef DLZ_MYSQL */ + +#ifdef DLZ_FILESYSTEM +#include <dlz/dlz_filesystem_driver.h> +#endif /* ifdef DLZ_FILESYSTEM */ + +#ifdef DLZ_BDB +#include <dlz/dlz_bdb_driver.h> +#include <dlz/dlz_bdbhpt_driver.h> +#endif /* ifdef DLZ_BDB */ + +#ifdef DLZ_LDAP +#include <dlz/dlz_ldap_driver.h> +#endif /* ifdef DLZ_LDAP */ + +#ifdef DLZ_ODBC +#include <dlz/dlz_odbc_driver.h> +#endif /* ifdef DLZ_ODBC */ + +/*% + * Call init functions for all relevant DLZ drivers. + */ + +isc_result_t +dlz_drivers_init(void) { + isc_result_t result = ISC_R_SUCCESS; + +#ifdef DLZ_STUB + result = dlz_stub_init(); + if (result != ISC_R_SUCCESS) { + return (result); + } +#endif /* ifdef DLZ_STUB */ + +#ifdef DLZ_POSTGRES + result = dlz_postgres_init(); + if (result != ISC_R_SUCCESS) { + return (result); + } +#endif /* ifdef DLZ_POSTGRES */ + +#ifdef DLZ_MYSQL + result = dlz_mysql_init(); + if (result != ISC_R_SUCCESS) { + return (result); + } +#endif /* ifdef DLZ_MYSQL */ + +#ifdef DLZ_FILESYSTEM + result = dlz_fs_init(); + if (result != ISC_R_SUCCESS) { + return (result); + } +#endif /* ifdef DLZ_FILESYSTEM */ + +#ifdef DLZ_BDB + result = dlz_bdb_init(); + if (result != ISC_R_SUCCESS) { + return (result); + } + result = dlz_bdbhpt_init(); + if (result != ISC_R_SUCCESS) { + return (result); + } +#endif /* ifdef DLZ_BDB */ + +#ifdef DLZ_LDAP + result = dlz_ldap_init(); + if (result != ISC_R_SUCCESS) { + return (result); + } +#endif /* ifdef DLZ_LDAP */ + +#ifdef DLZ_ODBC + result = dlz_odbc_init(); + if (result != ISC_R_SUCCESS) { + return (result); + } +#endif /* ifdef DLZ_ODBC */ + + return (result); +} + +/*% + * Call shutdown functions for all relevant DLZ drivers. + */ + +void +dlz_drivers_clear(void) { +#ifdef DLZ_STUB + dlz_stub_clear(); +#endif /* ifdef DLZ_STUB */ + +#ifdef DLZ_POSTGRES + dlz_postgres_clear(); +#endif /* ifdef DLZ_POSTGRES */ + +#ifdef DLZ_MYSQL + dlz_mysql_clear(); +#endif /* ifdef DLZ_MYSQL */ + +#ifdef DLZ_FILESYSTEM + dlz_fs_clear(); +#endif /* ifdef DLZ_FILESYSTEM */ + +#ifdef DLZ_BDB + dlz_bdb_clear(); + dlz_bdbhpt_clear(); +#endif /* ifdef DLZ_BDB */ + +#ifdef DLZ_LDAP + dlz_ldap_clear(); +#endif /* ifdef DLZ_LDAP */ + +#ifdef DLZ_ODBC + dlz_odbc_clear(); +#endif /* ifdef DLZ_ODBC */ +} diff --git a/contrib/dlz/drivers/dlz_filesystem_driver.c b/contrib/dlz/drivers/dlz_filesystem_driver.c new file mode 100644 index 0000000..c097411 --- /dev/null +++ b/contrib/dlz/drivers/dlz_filesystem_driver.c @@ -0,0 +1,1000 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +/* + * Copyright (C) 1999-2001, 2016 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/. + */ + +#ifdef DLZ_FILESYSTEM +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include <isc/dir.h> +#include <isc/mem.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/util.h> + +#include <dns/log.h> +#include <dns/result.h> +#include <dns/sdlz.h> + +#include <dlz/dlz_filesystem_driver.h> +#include <named/globals.h> + +static dns_sdlzimplementation_t *dlz_fs = NULL; + +typedef struct config_data { + char *basedir; + int basedirsize; + char *datadir; + int datadirsize; + char *xfrdir; + int xfrdirsize; + int splitcnt; + char separator; + char pathsep; + isc_mem_t *mctx; +} config_data_t; + +typedef struct dir_entry dir_entry_t; + +struct dir_entry { + char dirpath[PATH_MAX]; + ISC_LINK(dir_entry_t) link; +}; + +typedef ISC_LIST(dir_entry_t) dlist_t; + +/* forward reference */ + +static void +fs_destroy(void *driverarg, void *dbdata); + +/* + * Private methods + */ + +static bool +is_safe(const char *input) { + unsigned int i; + unsigned int len = strlen(input); + + /* check that only allowed characters are in the domain name */ + for (i = 0; i < len; i++) { + /* '.' is allowed, but has special requirements */ + if (input[i] == '.') { + /* '.' is not allowed as first char */ + if (i == 0) { + return (false); + } + /* '..', two dots together is not allowed. */ + if (input[i - 1] == '.') { + return (false); + } + /* '.' is not allowed as last char */ + if (i == len - 1) { + return (false); + } + /* only 1 dot in ok location, continue at next char */ + continue; + } + /* '-' is allowed, continue at next char */ + if (input[i] == '-') { + continue; + } + /* 0-9 is allowed, continue at next char */ + if (input[i] >= '0' && input[i] <= '9') { + continue; + } + /* A-Z uppercase is allowed, continue at next char */ + if (input[i] >= 'A' && input[i] <= 'Z') { + continue; + } + /* a-z lowercase is allowed, continue at next char */ + if (input[i] >= 'a' && input[i] <= 'z') { + continue; + } + + /* + * colon needs to be allowed for IPV6 client + * addresses. Not dangerous in domain names, as not a + * special char. + */ + if (input[i] == ':') { + continue; + } + + /* + * '@' needs to be allowed for in zone data. Not + * dangerous in domain names, as not a special char. + */ + if (input[i] == '@') { + continue; + } + + /* + * if we reach this point we have encountered a + * disallowed char! + */ + return (false); + } + /* everything ok. */ + return (true); +} + +static void +create_path_helper(char *out, const char *in, config_data_t *cd) { + char *tmpString; + char *tmpPtr; + int i; + + tmpString = isc_mem_strdup(named_g_mctx, in); + + /* + * don't forget is_safe guarantees '.' will NOT be the + * first/last char + */ + while ((tmpPtr = strrchr(tmpString, '.')) != NULL) { + i = 0; + while (tmpPtr[i + 1] != '\0') { + if (cd->splitcnt < 1) { + strcat(out, (char *)&tmpPtr[i + 1]); + } else { + strncat(out, (char *)&tmpPtr[i + 1], + cd->splitcnt); + } + strncat(out, (char *)&cd->pathsep, 1); + if (cd->splitcnt == 0) { + break; + } + if (strlen((char *)&tmpPtr[i + 1]) <= + (unsigned int)cd->splitcnt) + { + break; + } + i += cd->splitcnt; + } + tmpPtr[0] = '\0'; + } + + /* handle the "first" label properly */ + i = 0; + tmpPtr = tmpString; + while (tmpPtr[i] != '\0') { + if (cd->splitcnt < 1) { + strcat(out, (char *)&tmpPtr[i]); + } else { + strncat(out, (char *)&tmpPtr[i], cd->splitcnt); + } + strncat(out, (char *)&cd->pathsep, 1); + if (cd->splitcnt == 0) { + break; + } + if (strlen((char *)&tmpPtr[i]) <= (unsigned int)cd->splitcnt) { + break; + } + i += cd->splitcnt; + } + + isc_mem_free(named_g_mctx, tmpString); +} + +/*% + * Checks to make sure zone and host are safe. If safe, then + * hashes zone and host strings to build a path. If zone / host + * are not safe an error is returned. + */ + +static isc_result_t +create_path(const char *zone, const char *host, const char *client, + config_data_t *cd, char **path) { + char *tmpPath; + int pathsize; + int len; + bool isroot = false; + + /* we require a zone & cd parameter */ + REQUIRE(zone != NULL); + REQUIRE(cd != NULL); + /* require path to be a pointer to NULL */ + REQUIRE(path != NULL && *path == NULL); + /* + * client and host may both be NULL, but they can't both be + * NON-NULL + */ + REQUIRE((host == NULL && client == NULL) || + (host != NULL && client == NULL) || + (host == NULL && client != NULL)); + + /* special case for root zone */ + if (strcmp(zone, ".") == 0) { + isroot = true; + } + + /* if the requested zone is "unsafe", return error */ + if (!isroot && !is_safe(zone)) { + return (ISC_R_FAILURE); + } + + /* if host was passed, verify that it is safe */ + if (host != NULL && !is_safe(host)) { + return (ISC_R_FAILURE); + } + + /* if client was passed, verify that it is safe */ + if (client != NULL && !is_safe(client)) { + return (ISC_R_FAILURE); + } + + /* Determine how much memory the split up string will require */ + if (host != NULL) { + len = strlen(zone) + strlen(host); + } else if (client != NULL) { + len = strlen(zone) + strlen(client); + } else { + len = strlen(zone); + } + + /* + * even though datadir and xfrdir will never be in the same + * string we only waste a few bytes by allocating for both, + * and then we are safe from buffer overruns. + */ + pathsize = len + cd->basedirsize + cd->datadirsize + cd->xfrdirsize + 4; + + /* if we are splitting names, we will need extra space. */ + if (cd->splitcnt > 0) { + pathsize += len / cd->splitcnt; + } + + tmpPath = isc_mem_allocate(named_g_mctx, pathsize * sizeof(char)); + + /* + * build path string. + * start out with base directory. + */ + strcpy(tmpPath, cd->basedir); + + /* add zone name - parsed properly */ + if (!isroot) { + create_path_helper(tmpPath, zone, cd); + } + + /* + * When neither client or host is passed we are building a + * path to see if a zone is supported. We require that a zone + * path have the "data dir" directory contained within it so + * that we know this zone is really supported. Otherwise, + * this zone may not really be supported because we are + * supporting a delagated sub zone. + * + * Example: + * + * We are supporting long.domain.com and using a splitcnt of + * 0. the base dir is "/base-dir/" and the data dir is + * "/.datadir" We want to see if we are authoritative for + * domain.com. Path /base-dir/com/domain/.datadir since + * /base-dir/com/domain/.datadir does not exist, we are not + * authoritative for the domain "domain.com". However we are + * authoritative for the domain "long.domain.com" because the + * path /base-dir/com/domain/long/.datadir does exist! + */ + + /* if client is passed append xfr dir, otherwise append data dir */ + if (client != NULL) { + strcat(tmpPath, cd->xfrdir); + strncat(tmpPath, (char *)&cd->pathsep, 1); + strcat(tmpPath, client); + } else { + strcat(tmpPath, cd->datadir); + } + + /* if host not null, add it. */ + if (host != NULL) { + strncat(tmpPath, (char *)&cd->pathsep, 1); + create_path_helper(tmpPath, host, cd); + } + + /* return the path we built. */ + *path = tmpPath; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +process_dir(isc_dir_t *dir, void *passback, config_data_t *cd, + dlist_t *dir_list, unsigned int basedirlen) { + char tmp[PATH_MAX + NAME_MAX]; + int astPos; + struct stat sb; + isc_result_t result = ISC_R_FAILURE; + char *endp; + char *type; + char *ttlStr; + char *data; + char host[NAME_MAX]; + char *tmpString; + char *tmpPtr; + int ttl; + int i; + int len; + dir_entry_t *direntry; + bool foundHost; + + tmp[0] = '\0'; /* set 1st byte to '\0' so strcpy works right. */ + host[0] = '\0'; + foundHost = false; + + /* copy base directory name to tmp. */ + strcpy(tmp, dir->dirname); + + /* dir->dirname will always have '*' as the last char. */ + astPos = strlen(dir->dirname) - 1; + + /* if dir_list != NULL, were are performing a zone xfr */ + if (dir_list != NULL) { + /* if splitcnt == 0, determine host from path. */ + if (cd->splitcnt == 0) { + if (strlen(tmp) - 3 > basedirlen) { + tmp[astPos - 1] = '\0'; + tmpString = (char *)&tmp[basedirlen + 1]; + /* handle filesystem's special wildcard "-" */ + if (strcmp(tmpString, "-") == 0) { + strcpy(host, "*"); + } else { + /* + * not special wildcard -- normal name + */ + while ((tmpPtr = strrchr( + tmpString, + cd->pathsep)) != NULL) + { + if ((strlen(host) + + strlen(tmpPtr + 1) + 2) > + NAME_MAX) + { + continue; + } + strcat(host, tmpPtr + 1); + strcat(host, "."); + tmpPtr[0] = '\0'; + } + if ((strlen(host) + strlen(tmpString) + + 1) <= NAME_MAX) + { + strcat(host, tmpString); + } + } + + foundHost = true; + /* set tmp again for use later */ + strcpy(tmp, dir->dirname); + } + } else { + /* + * if splitcnt != 0 determine host from + * ".host" directory entry + */ + while (isc_dir_read(dir) == ISC_R_SUCCESS) { + if (strncasecmp(".host", dir->entry.name, 5) == + 0) + { + /* + * handle filesystem's special + * wildcard "-" + */ + if (strcmp((char *)&dir->entry.name[6], + "-") == 0) + { + strlcpy(host, "*", + sizeof(host)); + } else { + strlcpy(host, + (char *)&dir->entry + .name[6], + sizeof(host)); + } + foundHost = true; + break; + } + } + /* reset dir list for use later */ + isc_dir_reset(dir); + } /* end of else */ + } + + while (isc_dir_read(dir) == ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1), + "Filesystem driver Dir name:" + " '%s' Dir entry: '%s'\n", + dir->dirname, dir->entry.name); + + /* skip any entries starting with "." */ + if (dir->entry.name[0] == '.') { + continue; + } + + /* + * get rid of '*', set to NULL. Effectively trims + * string from previous loop to base directory only + * while still leaving memory for concat to be + * performed next. + */ + + tmp[astPos] = '\0'; + + /* add name to base directory name. */ + strcat(tmp, dir->entry.name); + + /* make sure we can stat entry */ + if (stat(tmp, &sb) == 0) { + /* if entry is a directory */ + if ((sb.st_mode & S_IFDIR) != 0) { + /* + * if dir list is NOT NULL, add dir to + * dir list + */ + if (dir_list != NULL) { + direntry = isc_mem_get( + named_g_mctx, + sizeof(dir_entry_t)); + strcpy(direntry->dirpath, tmp); + ISC_LINK_INIT(direntry, link); + ISC_LIST_APPEND(*dir_list, direntry, + link); + result = ISC_R_SUCCESS; + } + continue; + + /* + * if entry is a file be sure we do + * not add entry to DNS results if we + * are performing a zone xfr and we + * could not find a host entry. + */ + } else if (dir_list != NULL && !foundHost) { + continue; + } + } else { /* if we cannot stat entry, skip it. */ + continue; + } + + type = dir->entry.name; + ttlStr = strchr(type, cd->separator); + if (ttlStr == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Filesystem driver: " + "%s could not be parsed properly", + tmp); + return (ISC_R_FAILURE); + } + + /* replace separator char with NULL to split string */ + ttlStr[0] = '\0'; + /* start string after NULL of previous string */ + ttlStr = (char *)&ttlStr[1]; + + data = strchr(ttlStr, cd->separator); + if (data == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Filesystem driver: " + "%s could not be parsed properly", + tmp); + return (ISC_R_FAILURE); + } + + /* replace separator char with NULL to split string */ + data[0] = '\0'; + + /* start string after NULL of previous string */ + data = (char *)&data[1]; + + /* replace all cd->separator chars with a space. */ + len = strlen(data); + + for (i = 0; i < len; i++) { + if (data[i] == cd->separator) { + data[i] = ' '; + } + } + + /* convert text to int, make sure it worked right */ + ttl = strtol(ttlStr, &endp, 10); + if (*endp != '\0' || ttl < 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Filesystem driver " + "ttl must be a positive number"); + } + + /* pass data back to Bind */ + if (dir_list == NULL) { + result = dns_sdlz_putrr((dns_sdlzlookup_t *)passback, + type, ttl, data); + } else { + result = dns_sdlz_putnamedrr( + (dns_sdlzallnodes_t *)passback, (char *)host, + type, ttl, data); + } + + /* if error, return error right away */ + if (result != ISC_R_SUCCESS) { + return (result); + } + } /* end of while loop */ + + return (result); +} + +/* + * SDLZ interface methods + */ + +static isc_result_t +fs_allowzonexfr(void *driverarg, void *dbdata, const char *name, + const char *client) { + isc_result_t result; + char *path; + struct stat sb; + config_data_t *cd; + path = NULL; + + UNUSED(driverarg); + + cd = (config_data_t *)dbdata; + + if (create_path(name, NULL, client, cd, &path) != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + if (stat(path, &sb) != 0) { + result = ISC_R_NOTFOUND; + goto complete_AXFR; + } + + if ((sb.st_mode & S_IFREG) != 0) { + result = ISC_R_SUCCESS; + goto complete_AXFR; + } + + result = ISC_R_NOTFOUND; + +complete_AXFR: + isc_mem_free(named_g_mctx, path); + return (result); +} + +static isc_result_t +fs_allnodes(const char *zone, void *driverarg, void *dbdata, + dns_sdlzallnodes_t *allnodes) { + isc_result_t result; + dlist_t *dir_list; + config_data_t *cd; + char *basepath; + unsigned int basepathlen; + struct stat sb; + isc_dir_t dir; + dir_entry_t *dir_entry; + dir_entry_t *next_de; + + basepath = NULL; + dir_list = NULL; + + UNUSED(driverarg); + UNUSED(allnodes); + + cd = (config_data_t *)dbdata; + + /* allocate memory for list */ + dir_list = isc_mem_get(named_g_mctx, sizeof(dlist_t)); + + /* initialize list */ + ISC_LIST_INIT(*dir_list); + + if (create_path(zone, NULL, NULL, cd, &basepath) != ISC_R_SUCCESS) { + result = ISC_R_NOTFOUND; + goto complete_allnds; + } + + /* remove path separator at end of path so stat works properly */ + basepathlen = strlen(basepath); + + if (stat(basepath, &sb) != 0) { + result = ISC_R_NOTFOUND; + goto complete_allnds; + } + + if ((sb.st_mode & S_IFDIR) == 0) { + result = ISC_R_NOTFOUND; + goto complete_allnds; + } + + /* initialize and open directory */ + isc_dir_init(&dir); + result = isc_dir_open(&dir, basepath); + + /* if directory open failed, return error. */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Unable to open %s directory to read entries.", + basepath); + result = ISC_R_FAILURE; + goto complete_allnds; + } + + /* process the directory */ + result = process_dir(&dir, allnodes, cd, dir_list, basepathlen); + + /* close the directory */ + isc_dir_close(&dir); + + if (result != ISC_R_SUCCESS) { + goto complete_allnds; + } + + /* get first dir entry from list. */ + dir_entry = ISC_LIST_HEAD(*dir_list); + while (dir_entry != NULL) { + result = isc_dir_open(&dir, dir_entry->dirpath); + /* if directory open failed, return error. */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Unable to open %s " + "directory to read entries.", + basepath); + result = ISC_R_FAILURE; + goto complete_allnds; + } + + /* process the directory */ + result = process_dir(&dir, allnodes, cd, dir_list, basepathlen); + + /* close the directory */ + isc_dir_close(&dir); + + if (result != ISC_R_SUCCESS) { + goto complete_allnds; + } + + dir_entry = ISC_LIST_NEXT(dir_entry, link); + } /* end while */ + +complete_allnds: + /* clean up entries from list. */ + dir_entry = ISC_LIST_HEAD(*dir_list); + while (dir_entry != NULL) { + next_de = ISC_LIST_NEXT(dir_entry, link); + isc_mem_put(named_g_mctx, dir_entry, sizeof(dir_entry_t)); + dir_entry = next_de; + } /* end while */ + isc_mem_put(named_g_mctx, dir_list, sizeof(dlist_t)); + + if (basepath != NULL) { + isc_mem_free(named_g_mctx, basepath); + } + + return (result); +} + +static isc_result_t +fs_findzone(void *driverarg, void *dbdata, const char *name, + dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo) { + isc_result_t result; + char *path; + struct stat sb; + path = NULL; + + UNUSED(driverarg); + UNUSED(methods); + UNUSED(clientinfo); + + if (create_path(name, NULL, NULL, (config_data_t *)dbdata, &path) != + ISC_R_SUCCESS) + { + return (ISC_R_NOTFOUND); + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(1), + "Filesystem driver Findzone() Checking for path: '%s'\n", + path); + + if (stat(path, &sb) != 0) { + result = ISC_R_NOTFOUND; + goto complete_FZ; + } + + if ((sb.st_mode & S_IFDIR) != 0) { + result = ISC_R_SUCCESS; + goto complete_FZ; + } + + result = ISC_R_NOTFOUND; + +complete_FZ: + + isc_mem_free(named_g_mctx, path); + return (result); +} + +static isc_result_t +fs_lookup(const char *zone, const char *name, void *driverarg, void *dbdata, + dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + isc_result_t result; + char *path; + struct stat sb; + isc_dir_t dir; + path = NULL; + + UNUSED(driverarg); + UNUSED(lookup); + UNUSED(methods); + UNUSED(clientinfo); + + if (strcmp(name, "*") == 0) { + /* + * handle filesystem's special wildcard "-" + */ + result = create_path(zone, "-", NULL, (config_data_t *)dbdata, + &path); + } else { + result = create_path(zone, name, NULL, (config_data_t *)dbdata, + &path); + } + + if (result != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + /* remove path separator at end of path so stat works properly */ + path[strlen(path) - 1] = '\0'; + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(1), + "Filesystem driver lookup() Checking for path: '%s'\n", + path); + + if (stat(path, &sb) != 0) { + result = ISC_R_NOTFOUND; + goto complete_lkup; + } + + if ((sb.st_mode & S_IFDIR) == 0) { + result = ISC_R_NOTFOUND; + goto complete_lkup; + } + + /* initialize and open directory */ + isc_dir_init(&dir); + result = isc_dir_open(&dir, path); + + /* if directory open failed, return error. */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Unable to open %s directory to read entries.", + path); + result = ISC_R_FAILURE; + goto complete_lkup; + } + + /* process any records in the directory */ + result = process_dir(&dir, lookup, (config_data_t *)dbdata, NULL, 0); + + /* close the directory */ + isc_dir_close(&dir); + +complete_lkup: + + isc_mem_free(named_g_mctx, path); + return (result); +} + +static isc_result_t +fs_create(const char *dlzname, unsigned int argc, char *argv[], void *driverarg, + void **dbdata) { + config_data_t *cd; + char *endp; + int len; + char pathsep; + + UNUSED(driverarg); + UNUSED(dlzname); + + /* we require 5 command line args. */ + if (argc != 6) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Filesystem driver requires " + "6 command line args."); + return (ISC_R_FAILURE); + } + + if (strlen(argv[5]) > 1) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Filesystem driver can only " + "accept a single character for separator."); + return (ISC_R_FAILURE); + } + + /* verify base dir ends with '/' or '\' */ + len = strlen(argv[1]); + if (argv[1][len - 1] != '\\' && argv[1][len - 1] != '/') { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Base dir parameter for filesystem driver " + "should end with %s", + "either '/' or '\\' "); + return (ISC_R_FAILURE); + } + + /* determine and save path separator for later */ + if (argv[1][len - 1] == '\\') { + pathsep = '\\'; + } else { + pathsep = '/'; + } + + /* allocate memory for our config data */ + cd = isc_mem_get(named_g_mctx, sizeof(config_data_t)); + + /* zero the memory */ + memset(cd, 0, sizeof(config_data_t)); + + cd->pathsep = pathsep; + + /* get and store our base directory */ + cd->basedir = isc_mem_strdup(named_g_mctx, argv[1]); + cd->basedirsize = strlen(cd->basedir); + + /* get and store our data sub-dir */ + cd->datadir = isc_mem_strdup(named_g_mctx, argv[2]); + cd->datadirsize = strlen(cd->datadir); + + /* get and store our zone xfr sub-dir */ + cd->xfrdir = isc_mem_strdup(named_g_mctx, argv[3]); + cd->xfrdirsize = strlen(cd->xfrdir); + + /* get and store our directory split count */ + cd->splitcnt = strtol(argv[4], &endp, 10); + if (*endp != '\0' || cd->splitcnt < 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Directory split count must be zero (0) " + "or a positive number"); + } + + /* get and store our separator character */ + cd->separator = *argv[5]; + + /* attach config data to memory context */ + isc_mem_attach(named_g_mctx, &cd->mctx); + + /* pass back config data */ + *dbdata = cd; + + /* return success */ + return (ISC_R_SUCCESS); +} + +static void +fs_destroy(void *driverarg, void *dbdata) { + isc_mem_t *mctx; + config_data_t *cd; + + UNUSED(driverarg); + + cd = (config_data_t *)dbdata; + + /* + * free memory for each section of config data that was + * allocated + */ + if (cd->basedir != NULL) { + isc_mem_free(named_g_mctx, cd->basedir); + } + + if (cd->datadir != NULL) { + isc_mem_free(named_g_mctx, cd->datadir); + } + + if (cd->xfrdir != NULL) { + isc_mem_free(named_g_mctx, cd->xfrdir); + } + + /* hold memory context to use later */ + mctx = cd->mctx; + + /* free config data memory */ + isc_mem_putanddetach(&mctx, cd, sizeof(config_data_t)); +} + +static dns_sdlzmethods_t dlz_fs_methods = { + fs_create, + fs_destroy, + fs_findzone, + fs_lookup, + NULL, + fs_allnodes, + fs_allowzonexfr, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +/*% + * Wrapper around dns_sdlzregister(). + */ +isc_result_t +dlz_fs_init(void) { + isc_result_t result; + + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Registering DLZ filesystem driver."); + + result = dns_sdlzregister("filesystem", &dlz_fs_methods, NULL, + DNS_SDLZFLAG_RELATIVEOWNER | + DNS_SDLZFLAG_RELATIVERDATA, + named_g_mctx, &dlz_fs); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "dns_sdlzregister() failed: %s", + isc_result_totext(result)); + result = ISC_R_UNEXPECTED; + } + + return (result); +} + +/*% + * Wrapper around dns_sdlzunregister(). + */ +void +dlz_fs_clear(void) { + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Unregistering DLZ filesystem driver."); + + if (dlz_fs != NULL) { + dns_sdlzunregister(&dlz_fs); + } +} + +#endif /* ifdef DLZ_FILESYSTEM */ diff --git a/contrib/dlz/drivers/dlz_ldap_driver.c b/contrib/dlz/drivers/dlz_ldap_driver.c new file mode 100644 index 0000000..072d285 --- /dev/null +++ b/contrib/dlz/drivers/dlz_ldap_driver.c @@ -0,0 +1,1216 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +/* + * Copyright (C) 1999-2001, 2016 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/. + */ + +#ifdef DLZ_LDAP +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <isc/mem.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/util.h> + +#include <dns/log.h> +#include <dns/result.h> +#include <dns/sdlz.h> + +#include <dlz/dlz_ldap_driver.h> +#include <dlz/sdlz_helper.h> +#include <named/globals.h> + +/* + * Need older API functions from ldap.h. + */ +#define LDAP_DEPRECATED 1 + +#include <ldap.h> + +#define SIMPLE "simple" +#define KRB41 "krb41" +#define KRB42 "krb42" +#define V2 "v2" +#define V3 "v3" + +static dns_sdlzimplementation_t *dlz_ldap = NULL; + +#define dbc_search_limit 30 +#define ALLNODES 1 +#define ALLOWXFR 2 +#define AUTHORITY 3 +#define FINDZONE 4 +#define LOOKUP 5 + +/*% + * Structure to hold everything needed by this "instance" of the LDAP + * driver remember, the driver code is only loaded once, but may have + * many separate instances. + */ + +typedef struct { + db_list_t *db; /*%< handle to a list of DB */ + int method; /*%< security authentication method */ + char *user; /*%< who is authenticating */ + char *cred; /*%< password for simple authentication method */ + int protocol; /*%< LDAP communication protocol version */ + char *hosts; /*%< LDAP server hosts */ +} ldap_instance_t; + +/* forward references */ + +static isc_result_t +dlz_ldap_findzone(void *driverarg, void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); + +static void +dlz_ldap_destroy(void *driverarg, void *dbdata); + +/* + * Private methods + */ + +/*% checks that the LDAP URL parameters make sense */ +static isc_result_t +dlz_ldap_checkURL(char *URL, int attrCnt, const char *msg) { + isc_result_t result = ISC_R_SUCCESS; + int ldap_result; + LDAPURLDesc *ldap_url = NULL; + + if (!ldap_is_ldap_url(URL)) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%s query is not a valid LDAP URL", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + ldap_result = ldap_url_parse(URL, &ldap_url); + if (ldap_result != LDAP_SUCCESS || ldap_url == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "parsing %s query failed", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_count_values(ldap_url->lud_attrs) < attrCnt) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%s query must specify at least " + "%d attributes to return", + msg, attrCnt); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_url->lud_host != NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%s query must not specify a host", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_url->lud_port != 389) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%s query must not specify a port", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_url->lud_dn == NULL || strlen(ldap_url->lud_dn) < 1) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%s query must specify a search base", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_url->lud_exts != NULL || ldap_url->lud_crit_exts != 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%s uses extensions. " + "The driver does not support LDAP extensions.", + msg); + result = ISC_R_FAILURE; + goto cleanup; + } + +cleanup: + if (ldap_url != NULL) { + ldap_free_urldesc(ldap_url); + } + + return (result); +} + +/*% Connects / reconnects to LDAP server */ +static isc_result_t +dlz_ldap_connect(ldap_instance_t *dbi, dbinstance_t *dbc) { + isc_result_t result; + int ldap_result; + + /* if we have a connection, get ride of it. */ + if (dbc->dbconn != NULL) { + ldap_unbind_s((LDAP *)dbc->dbconn); + dbc->dbconn = NULL; + } + + /* now connect / reconnect. */ + + /* initialize. */ + dbc->dbconn = ldap_init(dbi->hosts, LDAP_PORT); + if (dbc->dbconn == NULL) { + return (ISC_R_NOMEMORY); + } + + /* set protocol version. */ + ldap_result = ldap_set_option((LDAP *)dbc->dbconn, + LDAP_OPT_PROTOCOL_VERSION, + &(dbi->protocol)); + if (ldap_result != LDAP_SUCCESS) { + result = ISC_R_NOPERM; + goto cleanup; + } + + /* "bind" to server. i.e. send username / pass */ + ldap_result = ldap_bind_s((LDAP *)dbc->dbconn, dbi->user, dbi->cred, + dbi->method); + if (ldap_result != LDAP_SUCCESS) { + result = ISC_R_FAILURE; + goto cleanup; + } + + return (ISC_R_SUCCESS); + +cleanup: + + /* cleanup if failure. */ + if (dbc->dbconn != NULL) { + ldap_unbind_s((LDAP *)dbc->dbconn); + dbc->dbconn = NULL; + } + + return (result); +} + +/*% + * Properly cleans up a list of database instances. + * This function is only used when the driver is compiled for + * multithreaded operation. + */ +static void +ldap_destroy_dblist(db_list_t *dblist) { + dbinstance_t *ndbi = NULL; + dbinstance_t *dbi = NULL; + + /* get the first DBI in the list */ + ndbi = ISC_LIST_HEAD(*dblist); + + /* loop through the list */ + while (ndbi != NULL) { + dbi = ndbi; + /* get the next DBI in the list */ + ndbi = ISC_LIST_NEXT(dbi, link); + /* release DB connection */ + if (dbi->dbconn != NULL) { + ldap_unbind_s((LDAP *)dbi->dbconn); + } + /* release all memory that comprised a DBI */ + destroy_sqldbinstance(dbi); + } + /* release memory for the list structure */ + isc_mem_put(named_g_mctx, dblist, sizeof(db_list_t)); +} + +/*% + * Loops through the list of DB instances, attempting to lock + * on the mutex. If successful, the DBI is reserved for use + * and the thread can perform queries against the database. + * If the lock fails, the next one in the list is tried. + * looping continues until a lock is obtained, or until + * the list has been searched dbc_search_limit times. + * This function is only used when the driver is compiled for + * multithreaded operation. + */ +static dbinstance_t * +ldap_find_avail_conn(db_list_t *dblist) { + dbinstance_t *dbi = NULL; + dbinstance_t *head; + int count = 0; + + /* get top of list */ + head = dbi = ISC_LIST_HEAD(*dblist); + + /* loop through list */ + while (count < dbc_search_limit) { + /* try to lock on the mutex */ + if (isc_mutex_trylock(&dbi->instance_lock) == ISC_R_SUCCESS) { + return (dbi); /* success, return the DBI for use. */ + } + /* not successful, keep trying */ + dbi = ISC_LIST_NEXT(dbi, link); + + /* check to see if we have gone to the top of the list. */ + if (dbi == NULL) { + count++; + dbi = head; + } + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_INFO, + "LDAP driver unable to find available connection " + "after searching %d times", + count); + return (NULL); +} + +static isc_result_t +ldap_process_results(LDAP *dbc, LDAPMessage *msg, char **attrs, void *ptr, + bool allnodes) { + isc_result_t result = ISC_R_SUCCESS; + int i = 0; + int j; + int len; + char *attribute = NULL; + LDAPMessage *entry; + char *endp = NULL; + char *host = NULL; + char *type = NULL; + char *data = NULL; + char **vals = NULL; + int ttl; + + /* make sure there are at least some attributes to process. */ + REQUIRE(attrs != NULL || attrs[0] != NULL); + + /* get the first entry to process */ + entry = ldap_first_entry(dbc, msg); + if (entry == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_INFO, + "LDAP no entries to process."); + return (ISC_R_FAILURE); + } + + /* loop through all entries returned */ + while (entry != NULL) { + /* reset for this loop */ + ttl = 0; + len = 0; + i = 0; + attribute = attrs[i]; + + /* determine how much space we need for data string */ + for (j = 0; attrs[j] != NULL; j++) { + /* get the list of values for this attribute. */ + vals = ldap_get_values(dbc, entry, attrs[j]); + /* skip empty attributes. */ + if (vals == NULL || ldap_count_values(vals) < 1) { + continue; + } + /* + * we only use the first value. this driver + * does not support multi-valued attributes. + */ + len = len + strlen(vals[0]) + 1; + /* free vals for next loop */ + ldap_value_free(vals); + } /* end for (j = 0; attrs[j] != NULL, j++) loop */ + + /* allocate memory for data string */ + data = isc_mem_allocate(named_g_mctx, len + 1); + + /* + * Make sure data is null termed at the beginning so + * we can check if any data was stored to it later. + */ + data[0] = '\0'; + + /* reset j to re-use below */ + j = 0; + + /* loop through the attributes in the order specified. */ + while (attribute != NULL) { + /* get the list of values for this attribute. */ + vals = ldap_get_values(dbc, entry, attribute); + + /* skip empty attributes. */ + if (vals == NULL || vals[0] == NULL) { + /* increment attribute pointer */ + attribute = attrs[++i]; + /* start loop over */ + continue; + } + + /* + * j initially = 0. Increment j each time we + * set a field that way next loop will set + * next field. + */ + switch (j) { + case 0: + j++; + /* + * convert text to int, make sure it + * worked right + */ + ttl = strtol(vals[0], &endp, 10); + if (*endp != '\0' || ttl < 0) { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, + ISC_LOG_ERROR, + "LDAP driver ttl must " + "be a positive number"); + goto cleanup; + } + break; + case 1: + j++; + type = isc_mem_strdup(named_g_mctx, vals[0]); + break; + case 2: + j++; + if (allnodes) { + host = isc_mem_strdup(named_g_mctx, + vals[0]); + } else { + strcpy(data, vals[0]); + } + break; + case 3: + j++; + if (allnodes) { + strcpy(data, vals[0]); + } else { + strcat(data, " "); + strcat(data, vals[0]); + } + break; + default: + strcat(data, " "); + strcat(data, vals[0]); + break; + } /* end switch(j) */ + + /* free values */ + ldap_value_free(vals); + vals = NULL; + + /* increment attribute pointer */ + attribute = attrs[++i]; + } /* end while (attribute != NULL) */ + + if (type == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "LDAP driver unable " + "to retrieve DNS type"); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (strlen(data) < 1) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "LDAP driver unable " + "to retrieve DNS data"); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (allnodes && host != NULL) { + if (strcasecmp(host, "~") == 0) { + result = dns_sdlz_putnamedrr( + (dns_sdlzallnodes_t *)ptr, "*", type, + ttl, data); + } else { + result = dns_sdlz_putnamedrr( + (dns_sdlzallnodes_t *)ptr, host, type, + ttl, data); + } + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "dlz-ldap: putnamedrr failed " + "for \"%s %s %u %s\", %s", + host, type, ttl, data, + isc_result_totext(result)); + } + } else { + result = dns_sdlz_putrr((dns_sdlzlookup_t *)ptr, type, + ttl, data); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "dlz-ldap: putrr failed " + "for \"%s %u %s\", %s", + type, ttl, data, + isc_result_totext(result)); + } + } + + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "LDAP driver failed " + "while sending data to BIND."); + goto cleanup; + } + + /* free memory for type, data and host for next loop */ + isc_mem_free(named_g_mctx, type); + isc_mem_free(named_g_mctx, data); + if (host != NULL) { + isc_mem_free(named_g_mctx, host); + } + + /* get the next entry to process */ + entry = ldap_next_entry(dbc, entry); + } /* end while (entry != NULL) */ + +cleanup: + /* de-allocate memory */ + if (vals != NULL) { + ldap_value_free(vals); + } + if (host != NULL) { + isc_mem_free(named_g_mctx, host); + } + if (type != NULL) { + isc_mem_free(named_g_mctx, type); + } + if (data != NULL) { + isc_mem_free(named_g_mctx, data); + } + + return (result); +} + +/*% + * This function is the real core of the driver. Zone, record + * and client strings are passed in (or NULL is passed if the + * string is not available). The type of query we want to run + * is indicated by the query flag, and the dbdata object is passed + * passed in to. dbdata really holds either: + * 1) a list of database instances (in multithreaded mode) OR + * 2) a single database instance (in single threaded mode) + * The function will construct the query and obtain an available + * database instance (DBI). It will then run the query and hopefully + * obtain a result set. + */ +static isc_result_t +ldap_get_results(const char *zone, const char *record, const char *client, + unsigned int query, void *dbdata, void *ptr) { + isc_result_t result; + dbinstance_t *dbi = NULL; + char *querystring = NULL; + LDAPURLDesc *ldap_url = NULL; + int ldap_result = 0; + LDAPMessage *ldap_msg = NULL; + int i; + int entries; + + /* get db instance / connection */ + /* find an available DBI from the list */ + dbi = ldap_find_avail_conn( + (db_list_t *)((ldap_instance_t *)dbdata)->db); + + /* if DBI is null, can't do anything else */ + if (dbi == NULL) { + return (ISC_R_FAILURE); + } + + /* set fields */ + if (zone != NULL) { + dbi->zone = isc_mem_strdup(named_g_mctx, zone); + } else { + dbi->zone = NULL; + } + if (record != NULL) { + dbi->record = isc_mem_strdup(named_g_mctx, record); + } else { + dbi->record = NULL; + } + if (client != NULL) { + dbi->client = isc_mem_strdup(named_g_mctx, client); + } else { + dbi->client = NULL; + } + + /* what type of query are we going to run? */ + switch (query) { + case ALLNODES: + /* + * if the query was not passed in from the config file + * then we can't run it. return not_implemented, so + * it's like the code for that operation was never + * built into the driver.... AHHH flexibility!!! + */ + if (dbi->allnodes_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } else { + querystring = build_querystring(named_g_mctx, + dbi->allnodes_q); + } + break; + case ALLOWXFR: + /* same as comments as ALLNODES */ + if (dbi->allowxfr_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } else { + querystring = build_querystring(named_g_mctx, + dbi->allowxfr_q); + } + break; + case AUTHORITY: + /* same as comments as ALLNODES */ + if (dbi->authority_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } else { + querystring = build_querystring(named_g_mctx, + dbi->authority_q); + } + break; + case FINDZONE: + /* this is required. It's the whole point of DLZ! */ + if (dbi->findzone_q == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2), + "No query specified for findzone. " + "Findzone requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } else { + querystring = build_querystring(named_g_mctx, + dbi->findzone_q); + } + break; + case LOOKUP: + /* this is required. It's also a major point of DLZ! */ + if (dbi->lookup_q == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2), + "No query specified for lookup. " + "Lookup requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } else { + querystring = build_querystring(named_g_mctx, + dbi->lookup_q); + } + break; + default: + /* + * this should never happen. If it does, the code is + * screwed up! + */ + UNEXPECTED_ERROR(__FILE__, __LINE__, + "Incorrect query flag passed to " + "ldap_get_results"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + /* if the querystring is null, Bummer, outta RAM. UPGRADE TIME!!! */ + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* + * output the full query string during debug so we can see + * what lame error the query has. + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(1), "\nQuery String: %s\n", querystring); + + /* break URL down into it's component parts, if error cleanup */ + ldap_result = ldap_url_parse(querystring, &ldap_url); + if (ldap_result != LDAP_SUCCESS || ldap_url == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + for (i = 0; i < 3; i++) { + /* + * dbi->dbconn may be null if trying to reconnect on a + * previous query failed. + */ + if (dbi->dbconn == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_INFO, + "LDAP driver attempting to re-connect"); + + result = dlz_ldap_connect((ldap_instance_t *)dbdata, + dbi); + if (result != ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + continue; + } + } + + /* perform ldap search synchronously */ + ldap_result = + ldap_search_s((LDAP *)dbi->dbconn, ldap_url->lud_dn, + ldap_url->lud_scope, ldap_url->lud_filter, + ldap_url->lud_attrs, 0, &ldap_msg); + + /* + * check return code. No such object is ok, just + * didn't find what we wanted + */ + switch (ldap_result) { + case LDAP_NO_SUCH_OBJECT: + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1), + "No object found matching " + "query requirements"); + result = ISC_R_NOTFOUND; + goto cleanup; + break; + case LDAP_SUCCESS: /* on success do nothing */ + result = ISC_R_SUCCESS; + i = 3; + break; + case LDAP_SERVER_DOWN: + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_INFO, + "LDAP driver attempting to re-connect"); + result = dlz_ldap_connect((ldap_instance_t *)dbdata, + dbi); + if (result != ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + } + break; + default: + /* + * other errors not ok. Log error message and + * get out + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "LDAP error: %s", + ldap_err2string(ldap_result)); + result = ISC_R_FAILURE; + goto cleanup; + break; + } /* close switch(ldap_result) */ + } /* end for (int i = 0 i < 3; i++) */ + + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + switch (query) { + case ALLNODES: + result = ldap_process_results((LDAP *)dbi->dbconn, ldap_msg, + ldap_url->lud_attrs, ptr, true); + break; + case AUTHORITY: + case LOOKUP: + result = ldap_process_results((LDAP *)dbi->dbconn, ldap_msg, + ldap_url->lud_attrs, ptr, false); + break; + case ALLOWXFR: + entries = ldap_count_entries((LDAP *)dbi->dbconn, ldap_msg); + if (entries == 0) { + result = ISC_R_NOPERM; + } else if (entries > 0) { + result = ISC_R_SUCCESS; + } else { + result = ISC_R_FAILURE; + } + break; + case FINDZONE: + entries = ldap_count_entries((LDAP *)dbi->dbconn, ldap_msg); + if (entries == 0) { + result = ISC_R_NOTFOUND; + } else if (entries > 0) { + result = ISC_R_SUCCESS; + } else { + result = ISC_R_FAILURE; + } + break; + default: + /* + * this should never happen. If it does, the code is + * screwed up! + */ + UNEXPECTED_ERROR(__FILE__, __LINE__, + "Incorrect query flag passed to " + "ldap_get_results"); + result = ISC_R_UNEXPECTED; + } + +cleanup: + /* it's always good to cleanup after yourself */ + + /* if we retrieved results, free them */ + if (ldap_msg != NULL) { + ldap_msgfree(ldap_msg); + } + + if (ldap_url != NULL) { + ldap_free_urldesc(ldap_url); + } + + /* cleanup */ + if (dbi->zone != NULL) { + isc_mem_free(named_g_mctx, dbi->zone); + } + if (dbi->record != NULL) { + isc_mem_free(named_g_mctx, dbi->record); + } + if (dbi->client != NULL) { + isc_mem_free(named_g_mctx, dbi->client); + } + + /* release the lock so another thread can use this dbi */ + isc_mutex_unlock(&dbi->instance_lock); + + /* release query string */ + if (querystring != NULL) { + isc_mem_free(named_g_mctx, querystring); + } + + /* return result */ + return (result); +} + +/* + * DLZ methods + */ +static isc_result_t +dlz_ldap_allowzonexfr(void *driverarg, void *dbdata, const char *name, + const char *client) { + isc_result_t result; + + UNUSED(driverarg); + + /* check to see if we are authoritative for the zone first */ + result = dlz_ldap_findzone(driverarg, dbdata, name, NULL, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* get all the zone data */ + result = ldap_get_results(name, NULL, client, ALLOWXFR, dbdata, NULL); + return (result); +} + +static isc_result_t +dlz_ldap_allnodes(const char *zone, void *driverarg, void *dbdata, + dns_sdlzallnodes_t *allnodes) { + UNUSED(driverarg); + return (ldap_get_results(zone, NULL, NULL, ALLNODES, dbdata, allnodes)); +} + +static isc_result_t +dlz_ldap_authority(const char *zone, void *driverarg, void *dbdata, + dns_sdlzlookup_t *lookup) { + UNUSED(driverarg); + return (ldap_get_results(zone, NULL, NULL, AUTHORITY, dbdata, lookup)); +} + +static isc_result_t +dlz_ldap_findzone(void *driverarg, void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + UNUSED(driverarg); + UNUSED(methods); + UNUSED(clientinfo); + return (ldap_get_results(name, NULL, NULL, FINDZONE, dbdata, NULL)); +} + +static isc_result_t +dlz_ldap_lookup(const char *zone, const char *name, void *driverarg, + void *dbdata, dns_sdlzlookup_t *lookup, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + isc_result_t result; + + UNUSED(driverarg); + UNUSED(methods); + UNUSED(clientinfo); + + if (strcmp(name, "*") == 0) { + result = ldap_get_results(zone, "~", NULL, LOOKUP, dbdata, + lookup); + } else { + result = ldap_get_results(zone, name, NULL, LOOKUP, dbdata, + lookup); + } + return (result); +} + +static isc_result_t +dlz_ldap_create(const char *dlzname, unsigned int argc, char *argv[], + void *driverarg, void **dbdata) { + isc_result_t result; + ldap_instance_t *ldap_inst = NULL; + dbinstance_t *dbi = NULL; + int protocol; + int method; + int dbcount; + char *endp; + /* db_list_t *dblist = NULL; */ + int i; + + UNUSED(dlzname); + UNUSED(driverarg); + + /* if debugging, let user know we are multithreaded. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(1), "LDAP driver running multithreaded"); + + if (argc < 9) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "LDAP driver requires at least " + "8 command line args."); + return (ISC_R_FAILURE); + } + + /* no more than 13 arg's should be passed to the driver */ + if (argc > 12) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "LDAP driver cannot accept more than " + "11 command line args."); + return (ISC_R_FAILURE); + } + + /* determine protocol version. */ + if (strncasecmp(argv[2], V2, strlen(V2)) == 0) { + protocol = 2; + } else if (strncasecmp(argv[2], V3, strlen(V3)) == 0) { + protocol = 3; + } else { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "LDAP driver protocol must be either %s or %s", + V2, V3); + return (ISC_R_FAILURE); + } + + /* determine connection method. */ + if (strncasecmp(argv[3], SIMPLE, strlen(SIMPLE)) == 0) { + method = LDAP_AUTH_SIMPLE; + } else if (strncasecmp(argv[3], KRB41, strlen(KRB41)) == 0) { + method = LDAP_AUTH_KRBV41; + } else if (strncasecmp(argv[3], KRB42, strlen(KRB42)) == 0) { + method = LDAP_AUTH_KRBV42; + } else { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "LDAP driver authentication method must be " + "one of %s, %s or %s", + SIMPLE, KRB41, KRB42); + return (ISC_R_FAILURE); + } + + /* check how many db connections we should create */ + dbcount = strtol(argv[1], &endp, 10); + if (*endp != '\0' || dbcount < 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "LDAP driver database connection count " + "must be positive."); + return (ISC_R_FAILURE); + } + + /* check that LDAP URL parameters make sense */ + switch (argc) { + case 12: + result = dlz_ldap_checkURL(argv[11], 0, "allow zone transfer"); + if (result != ISC_R_SUCCESS) { + return (result); + } + FALLTHROUGH; + case 11: + result = dlz_ldap_checkURL(argv[10], 3, "all nodes"); + if (result != ISC_R_SUCCESS) { + return (result); + } + FALLTHROUGH; + case 10: + if (strlen(argv[9]) > 0) { + result = dlz_ldap_checkURL(argv[9], 3, "authority"); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + FALLTHROUGH; + case 9: + result = dlz_ldap_checkURL(argv[8], 3, "lookup"); + if (result != ISC_R_SUCCESS) { + return (result); + } + result = dlz_ldap_checkURL(argv[7], 0, "find zone"); + if (result != ISC_R_SUCCESS) { + return (result); + } + break; + default: + /* not really needed, should shut up compiler. */ + result = ISC_R_FAILURE; + } + + /* allocate memory for LDAP instance */ + ldap_inst = isc_mem_get(named_g_mctx, sizeof(ldap_instance_t)); + memset(ldap_inst, 0, sizeof(ldap_instance_t)); + + /* store info needed to automatically re-connect. */ + ldap_inst->protocol = protocol; + ldap_inst->method = method; + ldap_inst->hosts = isc_mem_strdup(named_g_mctx, argv[6]); + ldap_inst->user = isc_mem_strdup(named_g_mctx, argv[4]); + ldap_inst->cred = isc_mem_strdup(named_g_mctx, argv[5]); + + /* allocate memory for database connection list */ + ldap_inst->db = isc_mem_get(named_g_mctx, sizeof(db_list_t)); + + /* initialize DB connection list */ + ISC_LIST_INIT(*(ldap_inst->db)); + + /* + * create the appropriate number of database instances (DBI) + * append each new DBI to the end of the list + */ + for (i = 0; i < dbcount; i++) { + /* how many queries were passed in from config file? */ + switch (argc) { + case 9: + result = build_sqldbinstance(named_g_mctx, NULL, NULL, + NULL, argv[7], argv[8], + NULL, &dbi); + break; + case 10: + result = build_sqldbinstance(named_g_mctx, NULL, NULL, + argv[9], argv[7], argv[8], + NULL, &dbi); + break; + case 11: + result = build_sqldbinstance(named_g_mctx, argv[10], + NULL, argv[9], argv[7], + argv[8], NULL, &dbi); + break; + case 12: + result = build_sqldbinstance(named_g_mctx, argv[10], + argv[11], argv[9], argv[7], + argv[8], NULL, &dbi); + break; + default: + /* not really needed, should shut up compiler. */ + result = ISC_R_FAILURE; + } + + if (result == ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2), + "LDAP driver created " + "database instance object."); + } else { /* unsuccessful?, log err msg and cleanup. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "LDAP driver could not create " + "database instance object."); + goto cleanup; + } + + ISC_LINK_INIT(dbi, link); + ISC_LIST_APPEND(*(ldap_inst->db), dbi, link); + + /* attempt to connect */ + result = dlz_ldap_connect(ldap_inst, dbi); + + /* + * if db connection cannot be created, log err msg and + * cleanup. + */ + switch (result) { + /* success, do nothing */ + case ISC_R_SUCCESS: + break; + /* + * no memory means ldap_init could not + * allocate memory + */ + case ISC_R_NOMEMORY: + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "LDAP driver could not allocate memory " + "for connection number %u", + i + 1); + goto cleanup; + break; + /* + * no perm means ldap_set_option could not set + * protocol version + */ + case ISC_R_NOPERM: + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "LDAP driver could not " + "set protocol version."); + result = ISC_R_FAILURE; + goto cleanup; + break; + /* failure means couldn't connect to ldap server */ + case ISC_R_FAILURE: + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "LDAP driver could not " + "bind connection number %u to server.", + i + 1); + goto cleanup; + break; + /* + * default should never happen. If it does, + * major errors. + */ + default: + UNEXPECTED_ERROR(__FILE__, __LINE__, + "dlz_ldap_create() failed: %s", + isc_result_totext(result)); + result = ISC_R_UNEXPECTED; + goto cleanup; + break; + } /* end switch(result) */ + + /* set DBI = null for next loop through. */ + dbi = NULL; + } /* end for loop */ + + /* set dbdata to the ldap_instance we created. */ + *dbdata = ldap_inst; + + /* hey, we got through all of that ok, return success. */ + return (ISC_R_SUCCESS); + +cleanup: + dlz_ldap_destroy(NULL, ldap_inst); + + return (ISC_R_FAILURE); +} + +void +dlz_ldap_destroy(void *driverarg, void *dbdata) { + UNUSED(driverarg); + + if (dbdata != NULL) { + /* cleanup the list of DBI's */ + ldap_destroy_dblist( + (db_list_t *)((ldap_instance_t *)dbdata)->db); + + if (((ldap_instance_t *)dbdata)->hosts != NULL) { + isc_mem_free(named_g_mctx, + ((ldap_instance_t *)dbdata)->hosts); + } + + if (((ldap_instance_t *)dbdata)->user != NULL) { + isc_mem_free(named_g_mctx, + ((ldap_instance_t *)dbdata)->user); + } + + if (((ldap_instance_t *)dbdata)->cred != NULL) { + isc_mem_free(named_g_mctx, + ((ldap_instance_t *)dbdata)->cred); + } + + isc_mem_put(named_g_mctx, dbdata, sizeof(ldap_instance_t)); + } +} + +static dns_sdlzmethods_t dlz_ldap_methods = { + dlz_ldap_create, + dlz_ldap_destroy, + dlz_ldap_findzone, + dlz_ldap_lookup, + dlz_ldap_authority, + dlz_ldap_allnodes, + dlz_ldap_allowzonexfr, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +/*% + * Wrapper around dns_sdlzregister(). + */ +isc_result_t +dlz_ldap_init(void) { + isc_result_t result; + + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Registering DLZ ldap driver."); + + result = dns_sdlzregister("ldap", &dlz_ldap_methods, NULL, + DNS_SDLZFLAG_RELATIVEOWNER | + DNS_SDLZFLAG_RELATIVERDATA, + named_g_mctx, &dlz_ldap); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "dns_sdlzregister() failed: %s", + isc_result_totext(result)); + result = ISC_R_UNEXPECTED; + } + + return (result); +} + +/*% + * Wrapper around dns_sdlzunregister(). + */ +void +dlz_ldap_clear(void) { + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Unregistering DLZ ldap driver."); + + if (dlz_ldap != NULL) { + dns_sdlzunregister(&dlz_ldap); + } +} + +#endif /* ifdef DLZ_LDAP */ diff --git a/contrib/dlz/drivers/dlz_mysql_driver.c b/contrib/dlz/drivers/dlz_mysql_driver.c new file mode 100644 index 0000000..35c3fd9 --- /dev/null +++ b/contrib/dlz/drivers/dlz_mysql_driver.c @@ -0,0 +1,1037 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +/* + * Copyright (C) 1999-2001, 2016 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/. + */ + +#ifdef DLZ_MYSQL +#include <mysql.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <isc/mem.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/util.h> + +#include <dns/log.h> +#include <dns/result.h> +#include <dns/sdlz.h> + +#include <dlz/dlz_mysql_driver.h> +#include <dlz/sdlz_helper.h> +#include <named/globals.h> + +#if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 80000 +typedef bool my_bool; +#endif /* !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 80000 */ + +static dns_sdlzimplementation_t *dlz_mysql = NULL; + +#define dbc_search_limit 30 +#define ALLNODES 1 +#define ALLOWXFR 2 +#define AUTHORITY 3 +#define FINDZONE 4 +#define COUNTZONE 5 +#define LOOKUP 6 + +#define safeGet(in) in == NULL ? "" : in + +/* + * Private methods + */ + +/*% + * Allocates memory for a new string, and then constructs the new + * string by "escaping" the input string. The new string is + * safe to be used in queries. This is necessary because we cannot + * be sure of what types of strings are passed to us, and we don't + * want special characters in the string causing problems. + */ + +static char * +mysqldrv_escape_string(MYSQL *mysql, const char *instr) { + char *outstr; + unsigned int len; + + if (instr == NULL) { + return (NULL); + } + + len = strlen(instr); + + outstr = isc_mem_allocate(named_g_mctx, (2 * len * sizeof(char)) + 1); + + mysql_real_escape_string(mysql, outstr, instr, len); + + return (outstr); +} + +/*% + * This function is the real core of the driver. Zone, record + * and client strings are passed in (or NULL is passed if the + * string is not available). The type of query we want to run + * is indicated by the query flag, and the dbdata object is passed + * passed in to. dbdata really holds a single database instance. + * The function will construct and run the query, hopefully getting + * a result set. + */ + +static isc_result_t +mysql_get_resultset(const char *zone, const char *record, const char *client, + unsigned int query, void *dbdata, MYSQL_RES **rs) { + isc_result_t result; + dbinstance_t *dbi = NULL; + char *querystring = NULL; + unsigned int i = 0; + unsigned int j = 0; + int qres = 0; + + if (query != COUNTZONE) { + REQUIRE(*rs == NULL); + } else { + REQUIRE(rs == NULL); + } + + /* get db instance / connection */ + dbi = (dbinstance_t *)dbdata; + + /* if DBI is null, can't do anything else */ + if (dbi == NULL) { + return (ISC_R_FAILURE); + } + + /* what type of query are we going to run? */ + switch (query) { + case ALLNODES: + /* + * if the query was not passed in from the config file + * then we can't run it. return not_implemented, so + * it's like the code for that operation was never + * built into the driver.... AHHH flexibility!!! + */ + if (dbi->allnodes_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case ALLOWXFR: + /* same as comments as ALLNODES */ + if (dbi->allowxfr_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case AUTHORITY: + /* same as comments as ALLNODES */ + if (dbi->authority_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case FINDZONE: + /* this is required. It's the whole point of DLZ! */ + if (dbi->findzone_q == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2), + "No query specified for findzone. " + "Findzone requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } + break; + case COUNTZONE: + /* same as comments as ALLNODES */ + if (dbi->countzone_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case LOOKUP: + /* this is required. It's also a major point of DLZ! */ + if (dbi->lookup_q == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2), + "No query specified for lookup. " + "Lookup requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } + break; + default: + /* + * this should never happen. If it does, the code is + * screwed up! + */ + UNEXPECTED_ERROR(__FILE__, __LINE__, + "Incorrect query flag passed to " + "mysql_get_resultset"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + /* + * was a zone string passed? If so, make it safe for use in + * queries. + */ + if (zone != NULL) { + dbi->zone = mysqldrv_escape_string((MYSQL *)dbi->dbconn, zone); + if (dbi->zone == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { /* no string passed, set the string pointer to NULL */ + dbi->zone = NULL; + } + + /* + * was a record string passed? If so, make it safe for use in + * queries. + */ + if (record != NULL) { + dbi->record = mysqldrv_escape_string((MYSQL *)dbi->dbconn, + record); + if (dbi->record == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { /* no string passed, set the string pointer to NULL */ + dbi->record = NULL; + } + + /* + * was a client string passed? If so, make it safe for use in + * queries. + */ + if (client != NULL) { + dbi->client = mysqldrv_escape_string((MYSQL *)dbi->dbconn, + client); + if (dbi->client == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { /* no string passed, set the string pointer to NULL */ + dbi->client = NULL; + } + + /* + * what type of query are we going to run? this time we build + * the actual query to run. + */ + switch (query) { + case ALLNODES: + querystring = build_querystring(named_g_mctx, dbi->allnodes_q); + break; + case ALLOWXFR: + querystring = build_querystring(named_g_mctx, dbi->allowxfr_q); + break; + case AUTHORITY: + querystring = build_querystring(named_g_mctx, dbi->authority_q); + break; + case FINDZONE: + querystring = build_querystring(named_g_mctx, dbi->findzone_q); + break; + case COUNTZONE: + querystring = build_querystring(named_g_mctx, dbi->countzone_q); + break; + case LOOKUP: + querystring = build_querystring(named_g_mctx, dbi->lookup_q); + break; + default: + /* + * this should never happen. If it does, the code is + * screwed up! + */ + UNEXPECTED_ERROR(__FILE__, __LINE__, + "Incorrect query flag passed to " + "mysql_get_resultset"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + /* if the querystring is null, Bummer, outta RAM. UPGRADE TIME!!! */ + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* + * output the full query string during debug so we can see + * what lame error the query has. + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(1), "\nQuery String: %s\n", querystring); + + /* attempt query up to 3 times. */ + for (i = 0; i < 3; i++) { + qres = mysql_query((MYSQL *)dbi->dbconn, querystring); + if (qres == 0) { + break; + } + for (j = 0; mysql_ping((MYSQL *)dbi->dbconn) != 0 && j < 4; j++) + { + ; + } + } + + if (qres == 0) { + result = ISC_R_SUCCESS; + if (query != COUNTZONE) { + *rs = mysql_store_result((MYSQL *)dbi->dbconn); + if (*rs == NULL) { + result = ISC_R_FAILURE; + } + } + } else { + result = ISC_R_FAILURE; + } + +cleanup: + /* it's always good to cleanup after yourself */ + + /* free dbi->zone string */ + if (dbi->zone != NULL) { + isc_mem_free(named_g_mctx, dbi->zone); + } + + /* free dbi->record string */ + if (dbi->record != NULL) { + isc_mem_free(named_g_mctx, dbi->record); + } + + /* free dbi->client string */ + if (dbi->client != NULL) { + isc_mem_free(named_g_mctx, dbi->client); + } + + /* release query string */ + if (querystring != NULL) { + isc_mem_free(named_g_mctx, querystring); + } + + /* return result */ + return (result); +} + +/*% + * The processing of result sets for lookup and authority are + * exactly the same. So that functionality has been moved + * into this function to minimize code. + */ + +static isc_result_t +mysql_process_rs(dns_sdlzlookup_t *lookup, MYSQL_RES *rs) { + isc_result_t result = ISC_R_NOTFOUND; + MYSQL_ROW row; + unsigned int fields; + unsigned int j; + unsigned int len; + char *tmpString; + char *endp; + int ttl; + + row = mysql_fetch_row(rs); /* get a row from the result set */ + fields = mysql_num_fields(rs); /* how many columns in result set */ + while (row != NULL) { + switch (fields) { + case 1: + /* + * one column in rs, it's the data field. use + * default type of A record, and default TTL + * of 86400 + */ + result = dns_sdlz_putrr(lookup, "a", 86400, + safeGet(row[0])); + break; + case 2: + /* + * two columns, data field, and data type. + * use default TTL of 86400. + */ + result = dns_sdlz_putrr(lookup, safeGet(row[0]), 86400, + safeGet(row[1])); + break; + case 3: + /* + * three columns, all data no defaults. + * convert text to int, make sure it worked + * right. + */ + ttl = strtol(safeGet(row[0]), &endp, 10); + if (*endp != '\0' || ttl < 0) { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver ttl must be " + "a positive number"); + } + result = dns_sdlz_putrr(lookup, safeGet(row[1]), ttl, + safeGet(row[2])); + break; + default: + /* + * more than 3 fields, concatenate the last + * ones together. figure out how long to make + * string. + */ + for (j = 2, len = 0; j < fields; j++) { + len += strlen(safeGet(row[j])) + 1; + } + /* + * allocate string memory, allow for NULL to + * term string + */ + tmpString = isc_mem_allocate(named_g_mctx, len + 1); + /* copy field to tmpString */ + strcpy(tmpString, safeGet(row[2])); + + /* + * concat the rest of fields together, space + * between each one. + */ + for (j = 3; j < fields; j++) { + strcat(tmpString, " "); + strcat(tmpString, safeGet(row[j])); + } + /* convert text to int, make sure it worked right */ + ttl = strtol(safeGet(row[0]), &endp, 10); + if (*endp != '\0' || ttl < 0) { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver ttl must be " + "a positive number"); + } + /* ok, now tell Bind about it. */ + result = dns_sdlz_putrr(lookup, safeGet(row[1]), ttl, + tmpString); + /* done, get rid of this thing. */ + isc_mem_free(named_g_mctx, tmpString); + } + /* I sure hope we were successful */ + if (result != ISC_R_SUCCESS) { + /* nope, get rid of the Result set, and log a msg */ + mysql_free_result(rs); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "dns_sdlz_putrr returned error. " + "Error code was: %s", + isc_result_totext(result)); + return (ISC_R_FAILURE); + } + row = mysql_fetch_row(rs); /* get next row */ + } + + /* free result set memory */ + mysql_free_result(rs); + + /* return result code */ + return (result); +} + +/* + * SDLZ interface methods + */ + +/*% determine if the zone is supported by (in) the database */ + +static isc_result_t +mysql_findzone(void *driverarg, void *dbdata, const char *name, + dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo) { + isc_result_t result; + MYSQL_RES *rs = NULL; + my_ulonglong rows; + + UNUSED(driverarg); + UNUSED(methods); + UNUSED(clientinfo); + + /* run the query and get the result set from the database. */ + result = mysql_get_resultset(name, NULL, NULL, FINDZONE, dbdata, &rs); + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS || rs == NULL) { + if (rs != NULL) { + mysql_free_result(rs); + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver unable to return " + "result set for findzone query"); + return (ISC_R_FAILURE); + } + /* count how many rows in result set */ + rows = mysql_num_rows(rs); + /* get rid of result set, we are done with it. */ + mysql_free_result(rs); + + /* if we returned any rows, zone is supported. */ + if (rows > 0) { + mysql_get_resultset(name, NULL, NULL, COUNTZONE, dbdata, NULL); + return (ISC_R_SUCCESS); + } + + /* no rows returned, zone is not supported. */ + return (ISC_R_NOTFOUND); +} + +/*% Determine if the client is allowed to perform a zone transfer */ +static isc_result_t +mysql_allowzonexfr(void *driverarg, void *dbdata, const char *name, + const char *client) { + isc_result_t result; + MYSQL_RES *rs = NULL; + my_ulonglong rows; + + UNUSED(driverarg); + + /* first check if the zone is supported by the database. */ + result = mysql_findzone(driverarg, dbdata, name, NULL, NULL); + if (result != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + /* + * if we get to this point we know the zone is supported by + * the database the only questions now are is the zone + * transfer is allowed for this client and did the config file + * have an allow zone xfr query. + * + * Run our query, and get a result set from the database. + */ + result = mysql_get_resultset(name, NULL, client, ALLOWXFR, dbdata, &rs); + /* if we get "not implemented", send it along. */ + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS || rs == NULL) { + if (rs != NULL) { + mysql_free_result(rs); + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver unable to return " + "result set for allow xfr query"); + return (ISC_R_FAILURE); + } + /* count how many rows in result set */ + rows = mysql_num_rows(rs); + /* get rid of result set, we are done with it. */ + mysql_free_result(rs); + + /* if we returned any rows, zone xfr is allowed. */ + if (rows > 0) { + return (ISC_R_SUCCESS); + } + + /* no rows returned, zone xfr not allowed */ + return (ISC_R_NOPERM); +} + +/*% + * If the client is allowed to perform a zone transfer, the next order of + * business is to get all the nodes in the zone, so bind can respond to the + * query. + */ +static isc_result_t +mysql_allnodes(const char *zone, void *driverarg, void *dbdata, + dns_sdlzallnodes_t *allnodes) { + isc_result_t result; + MYSQL_RES *rs = NULL; + MYSQL_ROW row; + unsigned int fields; + unsigned int j; + unsigned int len; + char *tmpString; + char *endp; + int ttl; + + UNUSED(driverarg); + + /* run the query and get the result set from the database. */ + result = mysql_get_resultset(zone, NULL, NULL, ALLNODES, dbdata, &rs); + /* if we get "not implemented", send it along */ + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS) { + if (rs != NULL) { + mysql_free_result(rs); + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver unable to return " + "result set for all nodes query"); + return (ISC_R_FAILURE); + } + + result = ISC_R_NOTFOUND; + + row = mysql_fetch_row(rs); /* get a row from the result set */ + fields = mysql_num_fields(rs); /* how many columns in result set */ + while (row != NULL) { + if (fields < 4) { /* gotta have at least 4 columns */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver too few fields returned " + "by all nodes query"); + } + /* convert text to int, make sure it worked right */ + ttl = strtol(safeGet(row[0]), &endp, 10); + if (*endp != '\0' || ttl < 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver ttl must be " + "a positive number"); + } + if (fields == 4) { + /* tell Bind about it. */ + result = dns_sdlz_putnamedrr(allnodes, safeGet(row[2]), + safeGet(row[1]), ttl, + safeGet(row[3])); + } else { + /* + * more than 4 fields, concatenate the last + * ones together. figure out how long to make + * string. + */ + for (j = 3, len = 0; j < fields; j++) { + len += strlen(safeGet(row[j])) + 1; + } + /* allocate memory, allow for NULL to term string */ + tmpString = isc_mem_allocate(named_g_mctx, len + 1); + /* copy this field to tmpString */ + strcpy(tmpString, safeGet(row[3])); + /* concatenate the rest, with spaces between */ + for (j = 4; j < fields; j++) { + strcat(tmpString, " "); + strcat(tmpString, safeGet(row[j])); + } + /* tell Bind about it. */ + result = dns_sdlz_putnamedrr(allnodes, safeGet(row[2]), + safeGet(row[1]), ttl, + tmpString); + isc_mem_free(named_g_mctx, tmpString); + } + /* if we weren't successful, log err msg */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "dns_sdlz_putnamedrr returned error. " + "Error code was: %s", + isc_result_totext(result)); + result = ISC_R_FAILURE; + break; + } + /* get next row from the result set */ + row = mysql_fetch_row(rs); + } + + /* free result set memory */ + mysql_free_result(rs); + + return (result); +} + +/*% if the lookup function does not return SOA or NS records for the zone, + * use this function to get that information for Bind. + */ + +static isc_result_t +mysql_authority(const char *zone, void *driverarg, void *dbdata, + dns_sdlzlookup_t *lookup) { + isc_result_t result; + MYSQL_RES *rs = NULL; + + UNUSED(driverarg); + + /* run the query and get the result set from the database. */ + result = mysql_get_resultset(zone, NULL, NULL, AUTHORITY, dbdata, &rs); + /* if we get "not implemented", send it along */ + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS) { + if (rs != NULL) { + mysql_free_result(rs); + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver unable to return " + "result set for authority query"); + return (ISC_R_FAILURE); + } + /* + * lookup and authority result sets are processed in the same + * manner mysql_process_rs does the job for both functions. + */ + return (mysql_process_rs(lookup, rs)); +} + +/*% if zone is supported, lookup up a (or multiple) record(s) in it */ +static isc_result_t +mysql_lookup(const char *zone, const char *name, void *driverarg, void *dbdata, + dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + isc_result_t result; + MYSQL_RES *rs = NULL; + + UNUSED(driverarg); + UNUSED(methods); + UNUSED(clientinfo); + + /* run the query and get the result set from the database. */ + result = mysql_get_resultset(zone, name, NULL, LOOKUP, dbdata, &rs); + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS) { + if (rs != NULL) { + mysql_free_result(rs); + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver unable to return " + "result set for lookup query"); + return (ISC_R_FAILURE); + } + /* + * lookup and authority result sets are processed in the same manner + * mysql_process_rs does the job for both functions. + */ + return (mysql_process_rs(lookup, rs)); +} + +/*% + * create an instance of the driver. Remember, only 1 copy of the driver's + * code is ever loaded, the driver has to remember which context it's + * operating in. This is done via use of the dbdata argument which is + * passed into all query functions. + */ +static isc_result_t +mysql_create(const char *dlzname, unsigned int argc, char *argv[], + void *driverarg, void **dbdata) { + isc_result_t result; + dbinstance_t *dbi = NULL; + char *tmp = NULL; + char *dbname = NULL; + char *host = NULL; + char *user = NULL; + char *pass = NULL; + char *socket = NULL; + int port; + MYSQL *dbc; + char *endp; + int j; + unsigned int flags = 0; + + UNUSED(driverarg); + UNUSED(dlzname); + + /* verify we have at least 4 arg's passed to the driver */ + if (argc < 4) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver requires " + "at least 4 command line args."); + return (ISC_R_FAILURE); + } + + /* no more than 8 arg's should be passed to the driver */ + if (argc > 8) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver cannot accept " + "more than 7 command line args."); + return (ISC_R_FAILURE); + } + + /* parse connection string and get parameters. */ + + /* get db name - required */ + dbname = getParameterValue(argv[1], "dbname="); + if (dbname == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver requires a dbname parameter."); + result = ISC_R_FAILURE; + goto full_cleanup; + } + + /* get db port. Not required, but must be > 0 if specified */ + tmp = getParameterValue(argv[1], "port="); + if (tmp == NULL) { + port = 0; + } else { + port = strtol(tmp, &endp, 10); + if (*endp != '\0' || port < 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Mysql driver port " + "must be a positive number."); + isc_mem_free(named_g_mctx, tmp); + result = ISC_R_FAILURE; + goto full_cleanup; + } + isc_mem_free(named_g_mctx, tmp); + } + + /* how many queries were passed in from config file? */ + switch (argc) { + case 4: + result = build_sqldbinstance(named_g_mctx, NULL, NULL, NULL, + argv[2], argv[3], NULL, &dbi); + break; + case 5: + result = build_sqldbinstance(named_g_mctx, NULL, NULL, argv[4], + argv[2], argv[3], NULL, &dbi); + break; + case 6: + result = build_sqldbinstance(named_g_mctx, argv[5], NULL, + argv[4], argv[2], argv[3], NULL, + &dbi); + break; + case 7: + result = build_sqldbinstance(named_g_mctx, argv[5], argv[6], + argv[4], argv[2], argv[3], NULL, + &dbi); + break; + case 8: + result = build_sqldbinstance(named_g_mctx, argv[5], argv[6], + argv[4], argv[2], argv[3], argv[7], + &dbi); + break; + default: + /* not really needed, should shut up compiler. */ + result = ISC_R_FAILURE; + } + + /* unsuccessful?, log err msg and cleanup. */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver could not create " + "database instance object."); + result = ISC_R_FAILURE; + goto cleanup; + } + + /* create and set db connection */ + dbi->dbconn = mysql_init(NULL); + + /* if db connection cannot be created, log err msg and cleanup. */ + if (dbi->dbconn == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver could not allocate " + "memory for database connection"); + result = ISC_R_FAILURE; + goto full_cleanup; + } + + tmp = getParameterValue(argv[1], "compress="); + if (tmp != NULL) { + if (strcasecmp(tmp, "true") == 0) { + flags = CLIENT_COMPRESS; + } + isc_mem_free(named_g_mctx, tmp); + } + + tmp = getParameterValue(argv[1], "ssl="); + if (tmp != NULL) { + if (strcasecmp(tmp, "true") == 0) { + flags = flags | CLIENT_SSL; + } + isc_mem_free(named_g_mctx, tmp); + } + + tmp = getParameterValue(argv[1], "space="); + if (tmp != NULL) { + if (strcasecmp(tmp, "ignore") == 0) { + flags = flags | CLIENT_IGNORE_SPACE; + } + isc_mem_free(named_g_mctx, tmp); + } + + dbc = NULL; + host = getParameterValue(argv[1], "host="); + user = getParameterValue(argv[1], "user="); + pass = getParameterValue(argv[1], "pass="); + socket = getParameterValue(argv[1], "socket="); + + /* enable automatic reconnection. */ + if (mysql_options((MYSQL *)dbi->dbconn, MYSQL_OPT_RECONNECT, + &(my_bool){ 1 }) != 0) + { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_WARNING, + "mysql driver failed to set " + "MYSQL_OPT_RECONNECT option, " + "continuing"); + } + + for (j = 0; dbc == NULL && j < 4; j++) { + dbc = mysql_real_connect((MYSQL *)dbi->dbconn, host, user, pass, + dbname, port, socket, flags); + } + + /* let user know if we couldn't connect. */ + if (dbc == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver failed to create " + "database connection after 4 attempts"); + result = ISC_R_FAILURE; + goto full_cleanup; + } + + /* return db connection via dbdata */ + *dbdata = dbi; + + result = ISC_R_SUCCESS; + goto cleanup; + +full_cleanup: + + if (dbi != NULL) { + destroy_sqldbinstance(dbi); + } + +cleanup: + + if (dbname != NULL) { + isc_mem_free(named_g_mctx, dbname); + } + if (host != NULL) { + isc_mem_free(named_g_mctx, host); + } + if (user != NULL) { + isc_mem_free(named_g_mctx, user); + } + if (pass != NULL) { + isc_mem_free(named_g_mctx, pass); + } + if (socket != NULL) { + isc_mem_free(named_g_mctx, socket); + } + + return (result); +} + +/*% + * destroy the driver. Remember, only 1 copy of the driver's + * code is ever loaded, the driver has to remember which context it's + * operating in. This is done via use of the dbdata argument. + * so we really only need to clean it up since we are not using driverarg. + */ + +static void +mysql_destroy(void *driverarg, void *dbdata) { + dbinstance_t *dbi; + + UNUSED(driverarg); + + dbi = (dbinstance_t *)dbdata; + + /* release DB connection */ + if (dbi->dbconn != NULL) { + mysql_close((MYSQL *)dbi->dbconn); + } + + /* destroy DB instance */ + destroy_sqldbinstance(dbi); +} + +/* pointers to all our runtime methods. */ +/* this is used during driver registration */ +/* i.e. in dlz_mysql_init below. */ +static dns_sdlzmethods_t dlz_mysql_methods = { + mysql_create, + mysql_destroy, + mysql_findzone, + mysql_lookup, + mysql_authority, + mysql_allnodes, + mysql_allowzonexfr, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +/*% + * Wrapper around dns_sdlzregister(). + */ +isc_result_t +dlz_mysql_init(void) { + isc_result_t result; + + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Registering DLZ mysql driver."); + + /* Driver is always threadsafe. Because of the way MySQL handles + * threads the MySQL driver can only be used when bind is run single + * threaded. Using MySQL with Bind running multi-threaded is not + * allowed. When using the MySQL driver "-n1" should always be + * passed to Bind to guarantee single threaded operation. + */ + result = dns_sdlzregister("mysql", &dlz_mysql_methods, NULL, + DNS_SDLZFLAG_RELATIVEOWNER | + DNS_SDLZFLAG_RELATIVERDATA | + DNS_SDLZFLAG_THREADSAFE, + named_g_mctx, &dlz_mysql); + /* if we can't register the driver, there are big problems. */ + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "dns_sdlzregister() failed: %s", + isc_result_totext(result)); + result = ISC_R_UNEXPECTED; + } + + return (result); +} + +/*% + * Wrapper around dns_sdlzunregister(). + */ +void +dlz_mysql_clear(void) { + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Unregistering DLZ mysql driver."); + + /* unregister the driver. */ + if (dlz_mysql != NULL) { + dns_sdlzunregister(&dlz_mysql); + } +} + +#endif /* ifdef DLZ_MYSQL */ diff --git a/contrib/dlz/drivers/dlz_odbc_driver.c b/contrib/dlz/drivers/dlz_odbc_driver.c new file mode 100644 index 0000000..dae5510 --- /dev/null +++ b/contrib/dlz/drivers/dlz_odbc_driver.c @@ -0,0 +1,1406 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +/* + * Copyright (C) 1999-2001, 2016 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/. + */ + +#ifdef DLZ_ODBC +#include <sql.h> +#include <sqlext.h> +#include <sqltypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <isc/mem.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/util.h> + +#include <dns/log.h> +#include <dns/result.h> +#include <dns/sdlz.h> + +#include <dlz/dlz_odbc_driver.h> +#include <dlz/sdlz_helper.h> +#include <named/globals.h> + +static dns_sdlzimplementation_t *dlz_odbc = NULL; + +#define dbc_search_limit 30 +#define ALLNODES 1 +#define ALLOWXFR 2 +#define AUTHORITY 3 +#define FINDZONE 4 +#define LOOKUP 5 + +#define sqlOK(a) ((a == SQL_SUCCESS || a == SQL_SUCCESS_WITH_INFO) ? -1 : 0) + +/* + * Private Structures + */ + +/* + * structure to hold ODBC connection & statement + */ + +typedef struct { + SQLHDBC dbc; + SQLHSTMT stmnt; +} odbc_db_t; + +/* + * Structure to hold everything needed by this "instance" of the odbc driver + * remember, the driver code is only loaded once, but may have many separate + * instances + */ + +typedef struct { + db_list_t *db; /* handle to a list of DB */ + SQLHENV sql_env; /* handle to SQL environment */ + SQLCHAR *dsn; + SQLCHAR *user; + SQLCHAR *pass; +} odbc_instance_t; + +/* forward reference */ + +static size_t +odbc_makesafe(char *to, const char *from, size_t length); + +/* + * Private methods + */ + +static SQLSMALLINT +safeLen(void *a) { + if (a == NULL) { + return (0); + } + return (strlen((char *)a)); +} + +/*% properly cleans up an odbc_instance_t */ + +static void +destroy_odbc_instance(odbc_instance_t *odbc_inst) { + dbinstance_t *ndbi = NULL; + dbinstance_t *dbi = NULL; + + /* get the first DBI in the list */ + ndbi = ISC_LIST_HEAD(*odbc_inst->db); + + /* loop through the list */ + while (ndbi != NULL) { + dbi = ndbi; + /* get the next DBI in the list */ + ndbi = ISC_LIST_NEXT(dbi, link); + + /* if we have a connection / statement object in memory */ + if (dbi->dbconn != NULL) { + /* free statement handle */ + if (((odbc_db_t *)(dbi->dbconn))->stmnt != NULL) { + SQLFreeHandle( + SQL_HANDLE_STMT, + ((odbc_db_t *)(dbi->dbconn))->stmnt); + ((odbc_db_t *)(dbi->dbconn))->stmnt = NULL; + } + + /* disconnect from database & free connection handle */ + if (((odbc_db_t *)(dbi->dbconn))->dbc != NULL) { + SQLDisconnect(((odbc_db_t *)dbi->dbconn)->dbc); + SQLFreeHandle( + SQL_HANDLE_DBC, + ((odbc_db_t *)(dbi->dbconn))->dbc); + ((odbc_db_t *)(dbi->dbconn))->dbc = NULL; + } + + /* free memory that held connection & statement. */ + isc_mem_free(named_g_mctx, dbi->dbconn); + } + /* release all memory that comprised a DBI */ + destroy_sqldbinstance(dbi); + } + /* release memory for the list structure */ + isc_mem_put(named_g_mctx, odbc_inst->db, sizeof(db_list_t)); + + /* free sql environment */ + if (odbc_inst->sql_env != NULL) { + SQLFreeHandle(SQL_HANDLE_ENV, odbc_inst->sql_env); + } + + /* free ODBC instance strings */ + if (odbc_inst->dsn != NULL) { + isc_mem_free(named_g_mctx, odbc_inst->dsn); + } + if (odbc_inst->pass != NULL) { + isc_mem_free(named_g_mctx, odbc_inst->pass); + } + if (odbc_inst->user != NULL) { + isc_mem_free(named_g_mctx, odbc_inst->user); + } + + /* free memory for odbc_inst */ + if (odbc_inst != NULL) { + isc_mem_put(named_g_mctx, odbc_inst, sizeof(odbc_instance_t)); + } +} + +/*% Connects to database, and creates ODBC statements */ + +static isc_result_t +odbc_connect(odbc_instance_t *dbi, odbc_db_t **dbc) { + odbc_db_t *ndb = *dbc; + SQLRETURN sqlRes; + isc_result_t result = ISC_R_SUCCESS; + + if (ndb != NULL) { + /* + * if db != null, we have to do some cleanup + * if statement handle != null free it + */ + if (ndb->stmnt != NULL) { + SQLFreeHandle(SQL_HANDLE_STMT, ndb->stmnt); + ndb->stmnt = NULL; + } + + /* if connection handle != null free it */ + if (ndb->dbc != NULL) { + SQLFreeHandle(SQL_HANDLE_DBC, ndb->dbc); + ndb->dbc = NULL; + } + } else { + ndb = isc_mem_allocate(named_g_mctx, sizeof(odbc_db_t)); + memset(ndb, 0, sizeof(odbc_db_t)); + } + + sqlRes = SQLAllocHandle(SQL_HANDLE_DBC, dbi->sql_env, &(ndb->dbc)); + if (!sqlOK(sqlRes)) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver unable to allocate memory"); + result = ISC_R_NOMEMORY; + goto cleanup; + } + + sqlRes = SQLConnect(ndb->dbc, dbi->dsn, safeLen(dbi->dsn), dbi->user, + safeLen(dbi->user), dbi->pass, safeLen(dbi->pass)); + if (!sqlOK(sqlRes)) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver unable to connect"); + result = ISC_R_FAILURE; + goto cleanup; + } + + sqlRes = SQLAllocHandle(SQL_HANDLE_STMT, ndb->dbc, &(ndb->stmnt)); + if (!sqlOK(sqlRes)) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver unable to allocate memory"); + result = ISC_R_NOMEMORY; + goto cleanup; + } + + *dbc = ndb; + + return (ISC_R_SUCCESS); + +cleanup: + + if (ndb != NULL) { + /* if statement handle != null free it */ + if (ndb->stmnt != NULL) { + SQLFreeHandle(SQL_HANDLE_STMT, ndb->stmnt); + ndb->stmnt = NULL; + } + + /* if connection handle != null free it */ + if (ndb->dbc != NULL) { + SQLDisconnect(ndb->dbc); + SQLFreeHandle(SQL_HANDLE_DBC, ndb->dbc); + ndb->dbc = NULL; + } + /* free memory holding ndb */ + isc_mem_free(named_g_mctx, ndb); + } + + return (result); +} + +/*% + * Loops through the list of DB instances, attempting to lock + * on the mutex. If successful, the DBI is reserved for use + * and the thread can perform queries against the database. + * If the lock fails, the next one in the list is tried. + * looping continues until a lock is obtained, or until + * the list has been searched dbc_search_limit times. + * This function is only used when the driver is compiled for + * multithreaded operation. + */ + +static dbinstance_t * +odbc_find_avail_conn(db_list_t *dblist) { + dbinstance_t *dbi = NULL; + dbinstance_t *head; + int count = 0; + + /* get top of list */ + head = dbi = ISC_LIST_HEAD(*dblist); + + /* loop through list */ + while (count < dbc_search_limit) { + /* try to lock on the mutex */ + if (isc_mutex_trylock(&dbi->instance_lock) == ISC_R_SUCCESS) { + return (dbi); /* success, return the DBI for use. */ + } + /* not successful, keep trying */ + dbi = ISC_LIST_NEXT(dbi, link); + + /* check to see if we have gone to the top of the list. */ + if (dbi == NULL) { + count++; + dbi = head; + } + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_INFO, + "Odbc driver unable to find available " + "connection after searching %d times", + count); + return (NULL); +} + +/*% Allocates memory for a new string, and then constructs the new + * string by "escaping" the input string. The new string is + * safe to be used in queries. This is necessary because we cannot + * be sure of what types of strings are passed to us, and we don't + * want special characters in the string causing problems. + */ + +static char * +odbc_escape_string(const char *instr) { + char *outstr; + unsigned int len; + + if (instr == NULL) { + return (NULL); + } + + len = strlen(instr); + + outstr = isc_mem_allocate(named_g_mctx, (2 * len * sizeof(char)) + 1); + + odbc_makesafe(outstr, instr, len); + + return (outstr); +} + +/* --------------- + * Escaping arbitrary strings to get valid SQL strings/identifiers. + * + * Replaces "\\" with "\\\\" and "'" with "''". + * length is the length of the buffer pointed to by + * from. The buffer at to must be at least 2*length + 1 characters + * long. A terminating NUL character is written. + * + * NOTICE!!! + * This function was borrowed directly from PostgreSQL's libpq. + * + * The copyright statements from the original file containing this + * function are included below: + * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * --------------- + */ + +static size_t +odbc_makesafe(char *to, const char *from, size_t length) { + const char *source = from; + char *target = to; + unsigned int remaining = length; + + while (remaining > 0) { + switch (*source) { + case '\\': + *target = '\\'; + target++; + *target = '\\'; + /* target and remaining are updated below. */ + break; + + case '\'': + *target = '\''; + target++; + *target = '\''; + /* target and remaining are updated below. */ + break; + + default: + *target = *source; + /* target and remaining are updated below. */ + } + source++; + target++; + remaining--; + } + + /* Write the terminating NUL character. */ + *target = '\0'; + + return (target - to); +} + +/*% + * This function is the real core of the driver. Zone, record + * and client strings are passed in (or NULL is passed if the + * string is not available). The type of query we want to run + * is indicated by the query flag, and the dbdata object is passed + * passed in to. dbdata really holds either: + * 1) a list of database instances (in multithreaded mode) OR + * 2) a single database instance (in single threaded mode) + * The function will construct the query and obtain an available + * database instance (DBI). It will then run the query and hopefully + * obtain a result set. The data base instance that is used is returned + * to the caller so they can get the data from the result set from it. + * If successful, it will be the responsibility of the caller to close + * the cursor, and unlock the mutex of the DBI when they are done with it. + * If not successful, this function will perform all the cleanup. + */ + +static isc_result_t +odbc_get_resultset(const char *zone, const char *record, const char *client, + unsigned int query, void *dbdata, dbinstance_t **r_dbi) { + isc_result_t result; + dbinstance_t *dbi = NULL; + char *querystring = NULL; + unsigned int j = 0; + SQLRETURN sqlRes; + + REQUIRE(*r_dbi == NULL); + + /* get db instance / connection */ + + /* find an available DBI from the list */ + dbi = odbc_find_avail_conn(((odbc_instance_t *)dbdata)->db); + + /* if DBI is null, can't do anything else */ + if (dbi == NULL) { + return (ISC_R_FAILURE); + } + + /* what type of query are we going to run? */ + switch (query) { + case ALLNODES: + /* + * if the query was not passed in from the config file + * then we can't run it. return not_implemented, so + * it's like the code for that operation was never + * built into the driver.... AHHH flexibility!!! + */ + if (dbi->allnodes_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case ALLOWXFR: + /* same as comments as ALLNODES */ + if (dbi->allowxfr_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case AUTHORITY: + /* same as comments as ALLNODES */ + if (dbi->authority_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case FINDZONE: + /* this is required. It's the whole point of DLZ! */ + if (dbi->findzone_q == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2), + "No query specified for findzone. " + "Findzone requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } + break; + case LOOKUP: + /* this is required. It's also a major point of DLZ! */ + if (dbi->lookup_q == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2), + "No query specified for lookup. " + "Lookup requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } + break; + default: + /* + * this should never happen. If it does, the code is + * screwed up! + */ + UNEXPECTED_ERROR(__FILE__, __LINE__, + "Incorrect query flag passed to " + "odbc_get_resultset"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + /* + * was a zone string passed? If so, make it safe for use in + * queries. + */ + if (zone != NULL) { + dbi->zone = odbc_escape_string(zone); + if (dbi->zone == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { /* no string passed, set the string pointer to NULL */ + dbi->zone = NULL; + } + + /* + * was a record string passed? If so, make it safe for use in + * queries. + */ + if (record != NULL) { + dbi->record = odbc_escape_string(record); + if (dbi->record == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { /* no string passed, set the string pointer to NULL */ + dbi->record = NULL; + } + + /* + * was a client string passed? If so, make it safe for use in + * queries. + */ + if (client != NULL) { + dbi->client = odbc_escape_string(client); + if (dbi->client == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { /* no string passed, set the string pointer to NULL */ + dbi->client = NULL; + } + + /* + * what type of query are we going to run? + * this time we build the actual query to run. + */ + switch (query) { + case ALLNODES: + querystring = build_querystring(named_g_mctx, dbi->allnodes_q); + break; + case ALLOWXFR: + querystring = build_querystring(named_g_mctx, dbi->allowxfr_q); + break; + case AUTHORITY: + querystring = build_querystring(named_g_mctx, dbi->authority_q); + break; + case FINDZONE: + querystring = build_querystring(named_g_mctx, dbi->findzone_q); + break; + case LOOKUP: + querystring = build_querystring(named_g_mctx, dbi->lookup_q); + break; + default: + /* + * this should never happen. If it does, the code is + * screwed up! + */ + UNEXPECTED_ERROR(__FILE__, __LINE__, + "Incorrect query flag passed to " + "odbc_get_resultset"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + /* if the querystring is null, Bummer, outta RAM. UPGRADE TIME!!! */ + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* output the full query string during debug so we can see */ + /* what lame error the query has. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(1), "\nQuery String: %s\n", querystring); + + /* attempt query up to 3 times. */ + for (j = 0; j < 3; j++) { + /* try to get result set */ + sqlRes = SQLExecDirect(((odbc_db_t *)dbi->dbconn)->stmnt, + (SQLCHAR *)querystring, + (SQLINTEGER)strlen(querystring)); + + /* if error, reset DB connection */ + if (!sqlOK(sqlRes)) { + /* close cursor */ + SQLCloseCursor(((odbc_db_t *)dbi->dbconn)->stmnt); + /* attempt to reconnect */ + result = odbc_connect((odbc_instance_t *)dbdata, + (odbc_db_t **)&(dbi->dbconn)); + /* check if we reconnected */ + if (result != ISC_R_SUCCESS) { + break; + } + /* in case this is the last time through the loop */ + result = ISC_R_FAILURE; + } else { + result = ISC_R_SUCCESS; + /* return dbi */ + *r_dbi = dbi; + /* result set ok, break loop */ + break; + } + } /* end for loop */ + +cleanup: /* it's always good to cleanup after yourself */ + + /* free dbi->zone string */ + if (dbi->zone != NULL) { + isc_mem_free(named_g_mctx, dbi->zone); + } + + /* free dbi->record string */ + if (dbi->record != NULL) { + isc_mem_free(named_g_mctx, dbi->record); + } + + /* free dbi->client string */ + if (dbi->client != NULL) { + isc_mem_free(named_g_mctx, dbi->client); + } + + /* if we are done using this dbi, release the lock */ + if (result != ISC_R_SUCCESS) { + isc_mutex_unlock(&dbi->instance_lock); + } + + /* release query string */ + if (querystring != NULL) { + isc_mem_free(named_g_mctx, querystring); + } + + /* return result */ + return (result); +} + +/*% + * Gets a single field from the ODBC statement. The memory for the + * returned data is dynamically allocated. If this method is successful + * it is the responsibility of the caller to free the memory using + * isc_mem_free(named_g_mctx, *ptr); + */ + +static isc_result_t +odbc_getField(SQLHSTMT *stmnt, SQLSMALLINT field, char **data) { + SQLLEN size; + + REQUIRE(data != NULL && *data == NULL); + + if (sqlOK(SQLColAttribute(stmnt, field, SQL_DESC_DISPLAY_SIZE, NULL, 0, + NULL, &size)) && + size > 0) + { + *data = isc_mem_allocate(named_g_mctx, size + 1); + if (data != NULL) { + if (sqlOK(SQLGetData(stmnt, field, SQL_C_CHAR, *data, + size + 1, &size))) + { + return (ISC_R_SUCCESS); + } + isc_mem_free(named_g_mctx, *data); + } + } + return (ISC_R_FAILURE); +} + +/*% + * Gets multiple fields from the ODBC statement. The memory for the + * returned data is dynamically allocated. If this method is successful + * it is the responsibility of the caller to free the memory using + * isc_mem_free(named_g_mctx, *ptr); + */ + +static isc_result_t +odbc_getManyFields(SQLHSTMT *stmnt, SQLSMALLINT startField, + SQLSMALLINT endField, char **retData) { + isc_result_t result; + SQLLEN size; + int totSize = 0; + SQLSMALLINT i; + int j = 0; + char *data; + + REQUIRE(retData != NULL && *retData == NULL); + REQUIRE(startField > 0 && startField <= endField); + + /* determine how large the data is */ + for (i = startField; i <= endField; i++) { + if (sqlOK(SQLColAttribute(stmnt, i, SQL_DESC_DISPLAY_SIZE, NULL, + 0, NULL, &size)) && + size > 0) + { + { + /* always allow for a " " (space) character */ + totSize += (size + 1); + /* after the data item */ + } + } + } + + if (totSize < 1) { + return (ISC_R_FAILURE); + } + + /* allow for a "\n" at the end of the string/ */ + data = isc_mem_allocate(named_g_mctx, ++totSize); + + result = ISC_R_FAILURE; + + /* get the data and concat all fields into a large string */ + for (i = startField; i <= endField; i++) { + if (sqlOK(SQLGetData(stmnt, i, SQL_C_CHAR, &(data[j]), + totSize - j, &size))) + { + if (size > 0) { + j += size; + data[j++] = ' '; + data[j] = '\0'; + result = ISC_R_SUCCESS; + } + } else { + isc_mem_free(named_g_mctx, data); + return (ISC_R_FAILURE); + } + } + + if (result != ISC_R_SUCCESS) { + isc_mem_free(named_g_mctx, data); + return (result); + } + + *retData = data; + return (ISC_R_SUCCESS); +} + +/*% + * The processing of result sets for lookup and authority are + * exactly the same. So that functionality has been moved + * into this function to minimize code. + */ + +static isc_result_t +odbc_process_rs(dns_sdlzlookup_t *lookup, dbinstance_t *dbi) { + isc_result_t result; + SQLSMALLINT fields; + SQLHSTMT *stmnt; + char *ttl_s; + char *type; + char *data; + char *endp; + int ttl; + + REQUIRE(dbi != NULL); + + stmnt = ((odbc_db_t *)(dbi->dbconn))->stmnt; + + /* get number of columns */ + if (!sqlOK(SQLNumResultCols(stmnt, &fields))) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver unable to process result set"); + result = ISC_R_FAILURE; + goto process_rs_cleanup; + } + + /* get things ready for processing */ + result = ISC_R_FAILURE; + + while (sqlOK(SQLFetch(stmnt))) { + /* set to null for next pass through */ + data = type = ttl_s = NULL; + + switch (fields) { + case 1: + /* + * one column in rs, it's the data field. use + * default type of A record, and default TTL + * of 86400. attempt to get data, & tell bind + * about it. + */ + if ((result = odbc_getField(stmnt, 1, &data)) == + ISC_R_SUCCESS) + { + result = dns_sdlz_putrr(lookup, "a", 86400, + data); + } + break; + case 2: + /* + * two columns, data field, and data type. + * use default TTL of 86400. attempt to get + * DNS type & data, then tell bind about it. + */ + if ((result = odbc_getField(stmnt, 1, &type)) == + ISC_R_SUCCESS && + (result = odbc_getField(stmnt, 2, &data)) == + ISC_R_SUCCESS) + { + result = dns_sdlz_putrr(lookup, type, 86400, + data); + } + break; + default: + /* + * 3 fields or more, concatenate the last ones + * together. attempt to get DNS ttl, type, + * data then tell Bind about them. + */ + if ((result = odbc_getField(stmnt, 1, &ttl_s)) == + ISC_R_SUCCESS && + (result = odbc_getField(stmnt, 2, &type)) == + ISC_R_SUCCESS && + (result = odbc_getManyFields( + stmnt, 3, fields, &data)) == ISC_R_SUCCESS) + { + /* try to convert ttl string to int */ + ttl = strtol(ttl_s, &endp, 10); + /* failure converting ttl. */ + if (*endp != '\0' || ttl < 0) { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, + ISC_LOG_ERROR, + "Odbc driver ttl must " + "be a positive number"); + result = ISC_R_FAILURE; + } else { + /* + * successful converting TTL, + * tell Bind everything + */ + result = dns_sdlz_putrr(lookup, type, + ttl, data); + } + } /* closes bid if () */ + } /* closes switch(fields) */ + + /* clean up mem */ + if (ttl_s != NULL) { + isc_mem_free(named_g_mctx, ttl_s); + } + if (type != NULL) { + isc_mem_free(named_g_mctx, type); + } + if (data != NULL) { + isc_mem_free(named_g_mctx, data); + } + + /* I sure hope we were successful */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "dns_sdlz_putrr returned error. " + "Error code was: %s", + isc_result_totext(result)); + result = ISC_R_FAILURE; + goto process_rs_cleanup; + } + } /* closes while loop */ + +process_rs_cleanup: + + /* close cursor */ + SQLCloseCursor(((odbc_db_t *)(dbi->dbconn))->stmnt); + + /* free lock on dbi so someone else can use it. */ + isc_mutex_unlock(&dbi->instance_lock); + + return (result); +} + +/* + * SDLZ interface methods + */ + +/*% determine if the zone is supported by (in) the database */ + +static isc_result_t +odbc_findzone(void *driverarg, void *dbdata, const char *name, + dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo) { + isc_result_t result; + dbinstance_t *dbi = NULL; + + UNUSED(driverarg); + UNUSED(methods); + UNUSED(clientinfo); + + /* run the query and get the result set from the database. */ + /* if result != ISC_R_SUCCESS cursor and mutex already cleaned up. */ + /* so we don't have to do it here. */ + result = odbc_get_resultset(name, NULL, NULL, FINDZONE, dbdata, &dbi); + + /* Check that we got a result set with data */ + if (result == ISC_R_SUCCESS && + !sqlOK(SQLFetch(((odbc_db_t *)(dbi->dbconn))->stmnt))) + { + result = ISC_R_NOTFOUND; + } + + if (dbi != NULL) { + /* get rid of result set, we are done with it. */ + SQLCloseCursor(((odbc_db_t *)(dbi->dbconn))->stmnt); + + /* free lock on dbi so someone else can use it. */ + isc_mutex_unlock(&dbi->instance_lock); + } + + return (result); +} + +/*% Determine if the client is allowed to perform a zone transfer */ +static isc_result_t +odbc_allowzonexfr(void *driverarg, void *dbdata, const char *name, + const char *client) { + isc_result_t result; + dbinstance_t *dbi = NULL; + + UNUSED(driverarg); + + /* first check if the zone is supported by the database. */ + result = odbc_findzone(driverarg, dbdata, name, NULL, NULL); + if (result != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + /* + * if we get to this point we know the zone is supported by + * the database. the only questions now are is the zone + * transfer is allowed for this client and did the config file + * have an allow zone xfr query + * + * Run our query, and get a result set from the database. if + * result != ISC_R_SUCCESS cursor and mutex already cleaned + * up, so we don't have to do it here. + */ + result = odbc_get_resultset(name, NULL, client, ALLOWXFR, dbdata, &dbi); + + /* if we get "not implemented", send it along. */ + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + + /* Check that we got a result set with data */ + if (result == ISC_R_SUCCESS && + !sqlOK(SQLFetch(((odbc_db_t *)(dbi->dbconn))->stmnt))) + { + result = ISC_R_NOPERM; + } + + if (dbi != NULL) { + /* get rid of result set, we are done with it. */ + SQLCloseCursor(((odbc_db_t *)(dbi->dbconn))->stmnt); + + /* free lock on dbi so someone else can use it. */ + isc_mutex_unlock(&dbi->instance_lock); + } + + return (result); +} + +/*% + * If the client is allowed to perform a zone transfer, the next order of + * business is to get all the nodes in the zone, so bind can respond to the + * query. + */ + +static isc_result_t +odbc_allnodes(const char *zone, void *driverarg, void *dbdata, + dns_sdlzallnodes_t *allnodes) { + isc_result_t result; + dbinstance_t *dbi = NULL; + SQLHSTMT *stmnt; + SQLSMALLINT fields; + char *data; + char *type; + char *ttl_s; + int ttl; + char *host; + char *endp; + + UNUSED(driverarg); + + /* run the query and get the result set from the database. */ + result = odbc_get_resultset(zone, NULL, NULL, ALLNODES, dbdata, &dbi); + + /* if we get "not implemented", send it along */ + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver unable to return " + "result set for all nodes query"); + return (ISC_R_FAILURE); + } + + stmnt = ((odbc_db_t *)(dbi->dbconn))->stmnt; + + /* get number of columns */ + if (!sqlOK(SQLNumResultCols(stmnt, &fields))) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver unable to process result set"); + result = ISC_R_FAILURE; + goto allnodes_cleanup; + } + + if (fields < 4) { /* gotta have at least 4 columns */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver too few fields returned by " + "all nodes query"); + result = ISC_R_FAILURE; + goto allnodes_cleanup; + } + + /* get things ready for processing */ + result = ISC_R_FAILURE; + + while (sqlOK(SQLFetch(stmnt))) { + /* set to null for next pass through */ + data = host = type = ttl_s = NULL; + + /* + * attempt to get DNS ttl, type, host, data then tell + * Bind about them + */ + if ((result = odbc_getField(stmnt, 1, &ttl_s)) == + ISC_R_SUCCESS && + (result = odbc_getField(stmnt, 2, &type)) == + ISC_R_SUCCESS && + (result = odbc_getField(stmnt, 3, &host)) == + ISC_R_SUCCESS && + (result = odbc_getManyFields(stmnt, 4, fields, &data)) == + ISC_R_SUCCESS) + { + /* convert ttl string to int */ + ttl = strtol(ttl_s, &endp, 10); + /* failure converting ttl. */ + if (*endp != '\0' || ttl < 0) { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver ttl must be " + "a positive number"); + result = ISC_R_FAILURE; + } else { + /* successful converting TTL, tell Bind */ + result = dns_sdlz_putnamedrr(allnodes, host, + type, ttl, data); + } + } /* closes big if () */ + + /* clean up mem */ + if (ttl_s != NULL) { + isc_mem_free(named_g_mctx, ttl_s); + } + if (type != NULL) { + isc_mem_free(named_g_mctx, type); + } + if (host != NULL) { + isc_mem_free(named_g_mctx, host); + } + if (data != NULL) { + isc_mem_free(named_g_mctx, data); + } + + /* if we weren't successful, log err msg */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "dns_sdlz_putnamedrr returned error. " + "Error code was: %s", + isc_result_totext(result)); + result = ISC_R_FAILURE; + goto allnodes_cleanup; + } + } /* closes while loop */ + +allnodes_cleanup: + + /* close cursor */ + SQLCloseCursor(((odbc_db_t *)(dbi->dbconn))->stmnt); + + /* free lock on dbi so someone else can use it. */ + isc_mutex_unlock(&dbi->instance_lock); + + return (result); +} + +/*% + * if the lookup function does not return SOA or NS records for the zone, + * use this function to get that information for Bind. + */ + +static isc_result_t +odbc_authority(const char *zone, void *driverarg, void *dbdata, + dns_sdlzlookup_t *lookup) { + isc_result_t result; + dbinstance_t *dbi = NULL; + + UNUSED(driverarg); + + /* run the query and get the result set from the database. */ + result = odbc_get_resultset(zone, NULL, NULL, AUTHORITY, dbdata, &dbi); + /* if we get "not implemented", send it along */ + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver unable to return " + "result set for authority query"); + return (ISC_R_FAILURE); + } + /* lookup and authority result sets are processed in the same manner */ + /* odbc_process_rs does the job for both functions. */ + return (odbc_process_rs(lookup, dbi)); +} + +/*% if zone is supported, lookup up a (or multiple) record(s) in it */ + +static isc_result_t +odbc_lookup(const char *zone, const char *name, void *driverarg, void *dbdata, + dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + isc_result_t result; + dbinstance_t *dbi = NULL; + + UNUSED(driverarg); + UNUSED(methods); + UNUSED(clientinfo); + + /* run the query and get the result set from the database. */ + result = odbc_get_resultset(zone, name, NULL, LOOKUP, dbdata, &dbi); + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver unable to return " + "result set for lookup query"); + return (ISC_R_FAILURE); + } + /* lookup and authority result sets are processed in the same manner */ + /* odbc_process_rs does the job for both functions. */ + return (odbc_process_rs(lookup, dbi)); +} + +/*% + * create an instance of the driver. Remember, only 1 copy of the driver's + * code is ever loaded, the driver has to remember which context it's + * operating in. This is done via use of the dbdata argument which is + * passed into all query functions. + */ +static isc_result_t +odbc_create(const char *dlzname, unsigned int argc, char *argv[], + void *driverarg, void **dbdata) { + isc_result_t result; + odbc_instance_t *odbc_inst = NULL; + dbinstance_t *db = NULL; + SQLRETURN sqlRes; + int dbcount; + int i; + char *endp; + + UNUSED(dlzname); + UNUSED(driverarg); + + /* if debugging, let user know we are multithreaded. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(1), "Odbc driver running multithreaded"); + + /* verify we have at least 5 arg's passed to the driver */ + if (argc < 5) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver requires at least " + "4 command line args."); + return (ISC_R_FAILURE); + } + + /* no more than 8 arg's should be passed to the driver */ + if (argc > 8) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver cannot accept more than " + "7 command line args."); + return (ISC_R_FAILURE); + } + + /* multithreaded build can have multiple DB connections */ + /* check how many db connections we should create */ + dbcount = strtol(argv[1], &endp, 10); + if (*endp != '\0' || dbcount < 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver database connection count " + "must be positive."); + return (ISC_R_FAILURE); + } + + /* allocate memory for odbc instance */ + odbc_inst = isc_mem_get(named_g_mctx, sizeof(odbc_instance_t)); + memset(odbc_inst, 0, sizeof(odbc_instance_t)); + + /* parse connection string and get parameters. */ + + /* get odbc database dsn - required */ + odbc_inst->dsn = (SQLCHAR *)getParameterValue(argv[2], "dsn="); + if (odbc_inst->dsn == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "odbc driver requires a dns parameter."); + result = ISC_R_FAILURE; + goto cleanup; + } + /* get odbc database username */ + /* if no username was passed, set odbc_inst.user = NULL; */ + odbc_inst->user = (SQLCHAR *)getParameterValue(argv[2], "user="); + + /* get odbc database password */ + /* if no password was passed, set odbc_inst.pass = NULL; */ + odbc_inst->pass = (SQLCHAR *)getParameterValue(argv[2], "pass="); + + /* create odbc environment & set environment to ODBC V3 */ + if (odbc_inst->sql_env == NULL) { + /* create environment handle */ + sqlRes = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, + &(odbc_inst->sql_env)); + if (!sqlOK(sqlRes)) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_INFO, + "Odbc driver unable to allocate memory"); + result = ISC_R_NOMEMORY; + goto cleanup; + } + /*set ODBC version = 3 */ + sqlRes = SQLSetEnvAttr(odbc_inst->sql_env, + SQL_ATTR_ODBC_VERSION, + (void *)SQL_OV_ODBC3, 0); + if (!sqlOK(sqlRes)) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_INFO, + "Unable to configure ODBC environment"); + result = ISC_R_NOMEMORY; + goto cleanup; + } + } + + /* allocate memory for database connection list */ + odbc_inst->db = isc_mem_get(named_g_mctx, sizeof(db_list_t)); + + /* initialize DB connection list */ + ISC_LIST_INIT(*odbc_inst->db); + + /* create the appropriate number of database instances (DBI) */ + /* append each new DBI to the end of the list */ + for (i = 0; i < dbcount; i++) { + /* how many queries were passed in from config file? */ + switch (argc) { + case 5: + result = build_sqldbinstance(named_g_mctx, NULL, NULL, + NULL, argv[3], argv[4], + NULL, &db); + break; + case 6: + result = build_sqldbinstance(named_g_mctx, NULL, NULL, + argv[5], argv[3], argv[4], + NULL, &db); + break; + case 7: + result = build_sqldbinstance(named_g_mctx, argv[6], + NULL, argv[5], argv[3], + argv[4], NULL, &db); + break; + case 8: + result = build_sqldbinstance(named_g_mctx, argv[6], + argv[7], argv[5], argv[3], + argv[4], NULL, &db); + break; + default: + /* not really needed, should shut up compiler. */ + result = ISC_R_FAILURE; + } + + /* unsuccessful?, log err msg and cleanup. */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver could not create " + "database instance object."); + goto cleanup; + } + + /* when multithreaded, build a list of DBI's */ + ISC_LINK_INIT(db, link); + ISC_LIST_APPEND(*odbc_inst->db, db, link); + + result = odbc_connect(odbc_inst, (odbc_db_t **)&(db->dbconn)); + + if (result != ISC_R_SUCCESS) { + /* + * if multi threaded, let user know which + * connection failed. user could be + * attempting to create 10 db connections and + * for some reason the db backend only allows + * 9. + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver failed to create database " + "connection number %u after 3 attempts", + i + 1); + goto cleanup; + } + + /* set DB = null for next loop through. */ + db = NULL; + } /* end for loop */ + + /* set dbdata to the odbc_instance we created. */ + *dbdata = odbc_inst; + + /* hey, we got through all of that ok, return success. */ + return (ISC_R_SUCCESS); + +cleanup: + + destroy_odbc_instance(odbc_inst); + + return (result); +} + +/*% + * destroy an instance of the driver. Remember, only 1 copy of the driver's + * code is ever loaded, the driver has to remember which context it's + * operating in. This is done via use of the dbdata argument. + * so we really only need to clean it up since we are not using driverarg. + */ + +static void +odbc_destroy(void *driverarg, void *dbdata) { + UNUSED(driverarg); + + destroy_odbc_instance((odbc_instance_t *)dbdata); +} + +/* pointers to all our runtime methods. */ +/* this is used during driver registration */ +/* i.e. in dlz_odbc_init below. */ +static dns_sdlzmethods_t dlz_odbc_methods = { + odbc_create, + odbc_destroy, + odbc_findzone, + odbc_lookup, + odbc_authority, + odbc_allnodes, + odbc_allowzonexfr, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +/*% + * Wrapper around dns_sdlzregister(). + */ +isc_result_t +dlz_odbc_init(void) { + isc_result_t result; + + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Registering DLZ odbc driver."); + + /* + * Driver is always threadsafe. When multithreaded all + * functions use multithreaded code. When not multithreaded, + * all functions can only be entered once, but only 1 thread + * of operation is available in Bind. So everything is still + * threadsafe. + */ + result = dns_sdlzregister("odbc", &dlz_odbc_methods, NULL, + DNS_SDLZFLAG_RELATIVEOWNER | + DNS_SDLZFLAG_RELATIVERDATA | + DNS_SDLZFLAG_THREADSAFE, + named_g_mctx, &dlz_odbc); + /* if we can't register the driver, there are big problems. */ + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "dns_sdlzregister() failed: %s", + isc_result_totext(result)); + result = ISC_R_UNEXPECTED; + } + + return (result); +} + +/*% + * Wrapper around dns_sdlzunregister(). + */ +void +dlz_odbc_clear(void) { + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Unregistering DLZ odbc driver."); + + /* unregister the driver. */ + if (dlz_odbc != NULL) { + dns_sdlzunregister(&dlz_odbc); + } +} + +#endif /* ifdef DLZ_ODBC */ diff --git a/contrib/dlz/drivers/dlz_postgres_driver.c b/contrib/dlz/drivers/dlz_postgres_driver.c new file mode 100644 index 0000000..d960c3f --- /dev/null +++ b/contrib/dlz/drivers/dlz_postgres_driver.c @@ -0,0 +1,1240 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +/* + * Copyright (C) 1999-2001, 2016 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/. + */ + +#ifdef DLZ_POSTGRES +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <isc/mem.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/util.h> + +#include <dns/log.h> +#include <dns/result.h> +#include <dns/sdlz.h> + +#include <dlz/dlz_postgres_driver.h> +#include <dlz/sdlz_helper.h> +#include <named/globals.h> + +/* temporarily include time. */ +#include <libpq-fe.h> +#include <time.h> + +static dns_sdlzimplementation_t *dlz_postgres = NULL; + +#define dbc_search_limit 30 +#define ALLNODES 1 +#define ALLOWXFR 2 +#define AUTHORITY 3 +#define FINDZONE 4 +#define LOOKUP 5 + +/* + * Private methods + */ + +/* --------------- + * Escaping arbitrary strings to get valid SQL strings/identifiers. + * + * Replaces "\\" with "\\\\" and "'" with "''". + * length is the length of the buffer pointed to by + * from. The buffer at to must be at least 2*length + 1 characters + * long. A terminating NUL character is written. + * + * NOTICE!!! + * This function was borrowed directly from PostgreSQL's libpq. + * The function was originally called PQescapeString and renamed + * to postgres_makesafe to avoid a naming collision. + * PQescapeString is a new function made available in Postgres 7.2. + * For some reason the function is not properly exported on Win32 + * builds making the function unavailable on Windows. Also, since + * this function is new it would require building this driver with + * the libpq 7.2. By borrowing this function the Windows problem + * is solved, and the dependence on libpq 7.2 is removed. Libpq is + * still required of course, but an older version should work now too. + * + * The copyright statements from the original file containing this + * function are included below: + * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * --------------- + */ + +static size_t +postgres_makesafe(char *to, const char *from, size_t length) { + const char *source = from; + char *target = to; + unsigned int remaining = length; + + while (remaining > 0) { + switch (*source) { + case '\\': + *target = '\\'; + target++; + *target = '\\'; + /* target and remaining are updated below. */ + break; + + case '\'': + *target = '\''; + target++; + *target = '\''; + /* target and remaining are updated below. */ + break; + + default: + *target = *source; + /* target and remaining are updated below. */ + } + source++; + target++; + remaining--; + } + + /* Write the terminating NUL character. */ + *target = '\0'; + + return (target - to); +} + +/*% + * Properly cleans up a list of database instances. + * This function is only used when the driver is compiled for + * multithreaded operation. + */ +static void +postgres_destroy_dblist(db_list_t *dblist) { + dbinstance_t *ndbi = NULL; + dbinstance_t *dbi = NULL; + + /* get the first DBI in the list */ + ndbi = ISC_LIST_HEAD(*dblist); + + /* loop through the list */ + while (ndbi != NULL) { + dbi = ndbi; + /* get the next DBI in the list */ + ndbi = ISC_LIST_NEXT(dbi, link); + /* release DB connection */ + if (dbi->dbconn != NULL) { + PQfinish((PGconn *)dbi->dbconn); + } + /* release all memory that comprised a DBI */ + destroy_sqldbinstance(dbi); + } + /* release memory for the list structure */ + isc_mem_put(named_g_mctx, dblist, sizeof(db_list_t)); +} + +/*% + * Loops through the list of DB instances, attempting to lock + * on the mutex. If successful, the DBI is reserved for use + * and the thread can perform queries against the database. + * If the lock fails, the next one in the list is tried. + * looping continues until a lock is obtained, or until + * the list has been searched dbc_search_limit times. + * This function is only used when the driver is compiled for + * multithreaded operation. + */ + +static dbinstance_t * +postgres_find_avail_conn(db_list_t *dblist) { + dbinstance_t *dbi = NULL; + dbinstance_t *head; + int count = 0; + + /* get top of list */ + head = dbi = ISC_LIST_HEAD(*dblist); + + /* loop through list */ + while (count < dbc_search_limit) { + /* try to lock on the mutex */ + if (isc_mutex_trylock(&dbi->instance_lock) == ISC_R_SUCCESS) { + return (dbi); /* success, return the DBI for use. */ + } + /* not successful, keep trying */ + dbi = ISC_LIST_NEXT(dbi, link); + + /* check to see if we have gone to the top of the list. */ + if (dbi == NULL) { + count++; + dbi = head; + } + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_INFO, + "Postgres driver unable to find available connection " + "after searching %d times", + count); + return (NULL); +} + +/*% + * Allocates memory for a new string, and then constructs the new + * string by "escaping" the input string. The new string is + * safe to be used in queries. This is necessary because we cannot + * be sure of what types of strings are passed to us, and we don't + * want special characters in the string causing problems. + */ + +static char * +postgres_escape_string(const char *instr) { + char *outstr; + unsigned int len; + + if (instr == NULL) { + return (NULL); + } + + len = strlen(instr); + + outstr = isc_mem_allocate(named_g_mctx, (2 * len * sizeof(char)) + 1); + + postgres_makesafe(outstr, instr, len); + /* PQescapeString(outstr, instr, len); */ + + return (outstr); +} + +/*% + * This function is the real core of the driver. Zone, record + * and client strings are passed in (or NULL is passed if the + * string is not available). The type of query we want to run + * is indicated by the query flag, and the dbdata object is passed + * passed in to. dbdata really holds either: + * 1) a list of database instances (in multithreaded mode) OR + * 2) a single database instance (in single threaded mode) + * The function will construct the query and obtain an available + * database instance (DBI). It will then run the query and hopefully + * obtain a result set. Postgres is nice, in that once the result + * set is returned, we can make the db connection available for another + * thread to use, while this thread continues on. So, the DBI is made + * available ASAP by unlocking the instance_lock after we have cleaned + * it up properly. + */ +static isc_result_t +postgres_get_resultset(const char *zone, const char *record, const char *client, + unsigned int query, void *dbdata, PGresult **rs) { + isc_result_t result; + dbinstance_t *dbi = NULL; + char *querystring = NULL; + unsigned int i = 0; + unsigned int j = 0; + +#if 0 + /* temporarily get a unique thread # */ + unsigned int dlz_thread_num = 1 + + (int) (1000.0 * rand() / + (RAND_MAX + 1.0)); +#endif /* if 0 */ + + REQUIRE(*rs == NULL); + +#if 0 + /* temporary logging message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%d Getting DBI", dlz_thread_num); +#endif /* if 0 */ + + /* get db instance / connection */ + + /* find an available DBI from the list */ + dbi = postgres_find_avail_conn((db_list_t *)dbdata); + +#if 0 + /* temporary logging message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%d Got DBI - checking query", dlz_thread_num); +#endif /* if 0 */ + + /* if DBI is null, can't do anything else */ + if (dbi == NULL) { + return (ISC_R_FAILURE); + } + + /* what type of query are we going to run? */ + switch (query) { + case ALLNODES: + /* + * if the query was not passed in from the config file + * then we can't run it. return not_implemented, so + * it's like the code for that operation was never + * built into the driver.... AHHH flexibility!!! + */ + if (dbi->allnodes_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case ALLOWXFR: + /* same as comments as ALLNODES */ + if (dbi->allowxfr_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case AUTHORITY: + /* same as comments as ALLNODES */ + if (dbi->authority_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case FINDZONE: + /* this is required. It's the whole point of DLZ! */ + if (dbi->findzone_q == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2), + "No query specified for findzone. " + "Findzone requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } + break; + case LOOKUP: + /* this is required. It's also a major point of DLZ! */ + if (dbi->lookup_q == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2), + "No query specified for lookup. " + "Lookup requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } + break; + default: + /* + * this should never happen. If it does, the code is + * screwed up! + */ + UNEXPECTED_ERROR(__FILE__, __LINE__, + "Incorrect query flag passed to " + "postgres_get_resultset"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + +#if 0 + /* temporary logging message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%d checked query", dlz_thread_num); +#endif /* if 0 */ + + /* + * was a zone string passed? If so, make it safe for use in + * queries. + */ + if (zone != NULL) { + dbi->zone = postgres_escape_string(zone); + if (dbi->zone == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { /* no string passed, set the string pointer to NULL */ + dbi->zone = NULL; + } + +#if 0 + /* temporary logging message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%d did zone", dlz_thread_num); +#endif /* if 0 */ + + /* + * was a record string passed? If so, make it safe for use in + * queries. + */ + if (record != NULL) { + dbi->record = postgres_escape_string(record); + if (dbi->record == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { /* no string passed, set the string pointer to NULL */ + dbi->record = NULL; + } + +#if 0 + /* temporary logging message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%d did record", dlz_thread_num); +#endif /* if 0 */ + + /* + * was a client string passed? If so, make it safe for use in + * queries. + */ + if (client != NULL) { + dbi->client = postgres_escape_string(client); + if (dbi->client == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { /* no string passed, set the string pointer to NULL */ + dbi->client = NULL; + } + +#if 0 + /* temporary logging message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%d did client", dlz_thread_num); +#endif /* if 0 */ + + /* + * what type of query are we going to run? + * this time we build the actual query to run. + */ + switch (query) { + case ALLNODES: + querystring = build_querystring(named_g_mctx, dbi->allnodes_q); + break; + case ALLOWXFR: + querystring = build_querystring(named_g_mctx, dbi->allowxfr_q); + break; + case AUTHORITY: + querystring = build_querystring(named_g_mctx, dbi->authority_q); + break; + case FINDZONE: + querystring = build_querystring(named_g_mctx, dbi->findzone_q); + break; + case LOOKUP: + querystring = build_querystring(named_g_mctx, dbi->lookup_q); + break; + default: + /* + * this should never happen. If it does, the code is + * screwed up! + */ + UNEXPECTED_ERROR(__FILE__, __LINE__, + "Incorrect query flag passed to " + "postgres_get_resultset"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + +#if 0 + /* temporary logging message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%d built query", dlz_thread_num); +#endif /* if 0 */ + + /* if the querystring is null, Bummer, outta RAM. UPGRADE TIME!!! */ + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + +#if 0 + /* temporary logging message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%d query is '%s'", dlz_thread_num, querystring); +#endif /* if 0 */ + + /* + * output the full query string during debug so we can see + * what lame error the query has. + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(1), "\nQuery String: %s\n", querystring); + + /* attempt query up to 3 times. */ + for (j = 0; j < 3; j++) { +#if 0 + /* temporary logging message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%d executing query for %d time", + dlz_thread_num, j); +#endif /* if 0 */ + /* try to get result set */ + *rs = PQexec((PGconn *)dbi->dbconn, querystring); + result = ISC_R_SUCCESS; + /* + * if result set is null, reset DB connection, max 3 + * attempts. + */ + for (i = 0; *rs == NULL && i < 3; i++) { +#if 0 + /* temporary logging message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%d resetting connection", + dlz_thread_num); +#endif /* if 0 */ + result = ISC_R_FAILURE; + PQreset((PGconn *)dbi->dbconn); + /* connection ok, break inner loop */ + if (PQstatus((PGconn *)dbi->dbconn) == CONNECTION_OK) { + break; + } + } + /* result set ok, break outer loop */ + if (PQresultStatus(*rs) == PGRES_TUPLES_OK) { +#if 0 + /* temporary logging message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%d rs ok", dlz_thread_num); +#endif /* if 0 */ + break; + } else { + /* we got a result set object, but it's not right. */ +#if 0 + /* temporary logging message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%d clearing rs", dlz_thread_num); +#endif /* if 0 */ + PQclear(*rs); /* get rid of it */ + /* in case this was the last attempt */ + *rs = NULL; + result = ISC_R_FAILURE; + } + } + +cleanup: + /* it's always good to cleanup after yourself */ + +#if 0 + /* temporary logging message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%d cleaning up", dlz_thread_num); +#endif /* if 0 */ + + /* free dbi->zone string */ + if (dbi->zone != NULL) { + isc_mem_free(named_g_mctx, dbi->zone); + } + + /* free dbi->record string */ + if (dbi->record != NULL) { + isc_mem_free(named_g_mctx, dbi->record); + } + + /* free dbi->client string */ + if (dbi->client != NULL) { + isc_mem_free(named_g_mctx, dbi->client); + } + +#if 0 + /* temporary logging message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%d unlocking mutex", dlz_thread_num); +#endif /* if 0 */ + + /* release the lock so another thread can use this dbi */ + isc_mutex_unlock(&dbi->instance_lock); + + /* release query string */ + if (querystring != NULL) { + isc_mem_free(named_g_mctx, querystring); + } + +#if 0 + /* temporary logging message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "%d returning", dlz_thread_num); +#endif /* if 0 */ + + /* return result */ + return (result); +} + +/*% + * The processing of result sets for lookup and authority are + * exactly the same. So that functionality has been moved + * into this function to minimize code. + */ + +static isc_result_t +postgres_process_rs(dns_sdlzlookup_t *lookup, PGresult *rs) { + isc_result_t result; + unsigned int i; + unsigned int rows; + unsigned int fields; + unsigned int j; + unsigned int len; + char *tmpString; + char *endp; + int ttl; + + rows = PQntuples(rs); /* how many rows in result set */ + fields = PQnfields(rs); /* how many columns in result set */ + for (i = 0; i < rows; i++) { + switch (fields) { + case 1: + /* + * one column in rs, it's the data field. use + * default type of A record, and default TTL + * of 86400 + */ + result = dns_sdlz_putrr(lookup, "a", 86400, + PQgetvalue(rs, i, 0)); + break; + case 2: + /* two columns, data field, and data type. + * use default TTL of 86400. + */ + result = dns_sdlz_putrr(lookup, PQgetvalue(rs, i, 0), + 86400, PQgetvalue(rs, i, 1)); + break; + case 3: + /* three columns, all data no defaults. + * convert text to int, make sure it worked + * right. + */ + ttl = strtol(PQgetvalue(rs, i, 0), &endp, 10); + if (*endp != '\0' || ttl < 0) { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver ttl must be " + "a positive number"); + } + result = dns_sdlz_putrr(lookup, PQgetvalue(rs, i, 1), + ttl, PQgetvalue(rs, i, 2)); + break; + default: + /* + * more than 3 fields, concatenate the last + * ones together. figure out how long to make + * string + */ + for (j = 2, len = 0; j < fields; j++) { + len += strlen(PQgetvalue(rs, i, j)) + 1; + } + /* + * allocate string memory, allow for NULL to + * term string + */ + tmpString = isc_mem_allocate(named_g_mctx, len + 1); + /* copy field to tmpString */ + strcpy(tmpString, PQgetvalue(rs, i, 2)); + /* + * concat the rest of fields together, space + * between each one. + */ + for (j = 3; j < fields; j++) { + strcat(tmpString, " "); + strcat(tmpString, PQgetvalue(rs, i, j)); + } + /* convert text to int, make sure it worked right */ + ttl = strtol(PQgetvalue(rs, i, 0), &endp, 10); + if (*endp != '\0' || ttl < 0) { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver ttl must be " + "a positive number"); + } + /* ok, now tell Bind about it. */ + result = dns_sdlz_putrr(lookup, PQgetvalue(rs, i, 1), + ttl, tmpString); + /* done, get rid of this thing. */ + isc_mem_free(named_g_mctx, tmpString); + } + /* I sure hope we were successful */ + if (result != ISC_R_SUCCESS) { + /* nope, get rid of the Result set, and log a msg */ + PQclear(rs); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "dns_sdlz_putrr returned error. " + "Error code was: %s", + isc_result_totext(result)); + return (ISC_R_FAILURE); + } + } + + /* free result set memory */ + PQclear(rs); + + /* if we did return results, we are successful */ + if (rows > 0) { + return (ISC_R_SUCCESS); + } + + /* empty result set, no data found */ + return (ISC_R_NOTFOUND); +} + +/* + * SDLZ interface methods + */ + +/*% determine if the zone is supported by (in) the database */ + +static isc_result_t +postgres_findzone(void *driverarg, void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + isc_result_t result; + PGresult *rs = NULL; + unsigned int rows; + + UNUSED(driverarg); + UNUSED(methods); + UNUSED(clientinfo); + + /* run the query and get the result set from the database. */ + result = postgres_get_resultset(name, NULL, NULL, FINDZONE, dbdata, + &rs); + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS) { + if (rs != NULL) { + PQclear(rs); + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver unable to return " + "result set for findzone query"); + return (ISC_R_FAILURE); + } + /* count how many rows in result set */ + rows = PQntuples(rs); + /* get rid of result set, we are done with it. */ + PQclear(rs); + + /* if we returned any rows, zone is supported. */ + if (rows > 0) { + return (ISC_R_SUCCESS); + } + + /* no rows returned, zone is not supported. */ + return (ISC_R_NOTFOUND); +} + +/*% Determine if the client is allowed to perform a zone transfer */ +static isc_result_t +postgres_allowzonexfr(void *driverarg, void *dbdata, const char *name, + const char *client) { + isc_result_t result; + PGresult *rs = NULL; + unsigned int rows; + UNUSED(driverarg); + + /* first check if the zone is supported by the database. */ + result = postgres_findzone(driverarg, dbdata, name, NULL, NULL); + if (result != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + /* + * if we get to this point we know the zone is supported by + * the database the only questions now are is the zone + * transfer is allowed for this client and did the config file + * have an allow zone xfr query. + * + * Run our query, and get a result set from the database. + */ + result = postgres_get_resultset(name, NULL, client, ALLOWXFR, dbdata, + &rs); + /* if we get "not implemented", send it along. */ + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS) { + if (rs != NULL) { + PQclear(rs); + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver unable to return " + "result set for allow xfr query"); + return (ISC_R_FAILURE); + } + /* count how many rows in result set */ + rows = PQntuples(rs); + /* get rid of result set, we are done with it. */ + PQclear(rs); + + /* if we returned any rows, zone xfr is allowed. */ + if (rows > 0) { + return (ISC_R_SUCCESS); + } + + /* no rows returned, zone xfr not allowed */ + return (ISC_R_NOPERM); +} + +/*% + * If the client is allowed to perform a zone transfer, the next order of + * business is to get all the nodes in the zone, so bind can respond to the + * query. + */ +static isc_result_t +postgres_allnodes(const char *zone, void *driverarg, void *dbdata, + dns_sdlzallnodes_t *allnodes) { + isc_result_t result; + PGresult *rs = NULL; + unsigned int i; + unsigned int rows; + unsigned int fields; + unsigned int j; + unsigned int len; + char *tmpString; + char *endp; + int ttl; + + UNUSED(driverarg); + + /* run the query and get the result set from the database. */ + result = postgres_get_resultset(zone, NULL, NULL, ALLNODES, dbdata, + &rs); + /* if we get "not implemented", send it along */ + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS) { + if (rs != NULL) { + PQclear(rs); + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver unable to return " + "result set for all nodes query"); + return (ISC_R_FAILURE); + } + + rows = PQntuples(rs); /* how many rows in result set */ + fields = PQnfields(rs); /* how many columns in result set */ + for (i = 0; i < rows; i++) { + if (fields < 4) { /* gotta have at least 4 columns */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver too few fields " + "returned by all nodes query"); + } + /* convert text to int, make sure it worked right */ + ttl = strtol(PQgetvalue(rs, i, 0), &endp, 10); + if (*endp != '\0' || ttl < 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver ttl must be " + "a positive number"); + } + if (fields == 4) { + /* tell Bind about it. */ + result = dns_sdlz_putnamedrr(allnodes, + PQgetvalue(rs, i, 2), + PQgetvalue(rs, i, 1), ttl, + PQgetvalue(rs, i, 3)); + } else { + /* + * more than 4 fields, concatonat the last + * ones together. figure out how long to make + * string + */ + for (j = 3, len = 0; j < fields; j++) { + len += strlen(PQgetvalue(rs, i, j)) + 1; + } + /* allocate memory, allow for NULL to term string */ + tmpString = isc_mem_allocate(named_g_mctx, len + 1); + /* copy this field to tmpString */ + strcpy(tmpString, PQgetvalue(rs, i, 3)); + /* concatenate the rest, with spaces between */ + for (j = 4; j < fields; j++) { + strcat(tmpString, " "); + strcat(tmpString, PQgetvalue(rs, i, j)); + } + /* tell Bind about it. */ + result = dns_sdlz_putnamedrr( + allnodes, PQgetvalue(rs, i, 2), + PQgetvalue(rs, i, 1), ttl, tmpString); + isc_mem_free(named_g_mctx, tmpString); + } + /* if we weren't successful, log err msg */ + if (result != ISC_R_SUCCESS) { + PQclear(rs); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "dns_sdlz_putnamedrr returned error. " + "Error code was: %s", + isc_result_totext(result)); + return (ISC_R_FAILURE); + } + } + + /* free result set memory */ + PQclear(rs); + + /* if we did return results, we are successful */ + if (rows > 0) { + return (ISC_R_SUCCESS); + } + + /* empty result set, no data found */ + return (ISC_R_NOTFOUND); +} + +/*% + * if the lookup function does not return SOA or NS records for the zone, + * use this function to get that information for Bind. + */ + +static isc_result_t +postgres_authority(const char *zone, void *driverarg, void *dbdata, + dns_sdlzlookup_t *lookup) { + isc_result_t result; + PGresult *rs = NULL; + + UNUSED(driverarg); + + /* run the query and get the result set from the database. */ + result = postgres_get_resultset(zone, NULL, NULL, AUTHORITY, dbdata, + &rs); + /* if we get "not implemented", send it along */ + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS) { + if (rs != NULL) { + PQclear(rs); + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver unable to return " + "result set for authority query"); + return (ISC_R_FAILURE); + } + /* + * lookup and authority result sets are processed in the same + * manner postgres_process_rs does the job for both + * functions. + */ + return (postgres_process_rs(lookup, rs)); +} + +/*% if zone is supported, lookup up a (or multiple) record(s) in it */ +static isc_result_t +postgres_lookup(const char *zone, const char *name, void *driverarg, + void *dbdata, dns_sdlzlookup_t *lookup, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + isc_result_t result; + PGresult *rs = NULL; + + UNUSED(driverarg); + UNUSED(methods); + UNUSED(clientinfo); + + /* run the query and get the result set from the database. */ + result = postgres_get_resultset(zone, name, NULL, LOOKUP, dbdata, &rs); + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS) { + if (rs != NULL) { + PQclear(rs); + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver unable to " + "return result set for lookup query"); + return (ISC_R_FAILURE); + } + /* + * lookup and authority result sets are processed in the same + * manner postgres_process_rs does the job for both functions. + */ + return (postgres_process_rs(lookup, rs)); +} + +/*% + * create an instance of the driver. Remember, only 1 copy of the driver's + * code is ever loaded, the driver has to remember which context it's + * operating in. This is done via use of the dbdata argument which is + * passed into all query functions. + */ +static isc_result_t +postgres_create(const char *dlzname, unsigned int argc, char *argv[], + void *driverarg, void **dbdata) { + isc_result_t result; + dbinstance_t *dbi = NULL; + unsigned int j; + + /* if multi-threaded, we need a few extra variables. */ + int dbcount; + db_list_t *dblist = NULL; + int i; + char *endp; + + UNUSED(driverarg); + UNUSED(dlzname); + + /* seed random # generator */ + srand((unsigned)time(NULL)); + + /* if debugging, let user know we are multithreaded. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(1), + "Postgres driver running multithreaded"); + + /* verify we have at least 5 arg's passed to the driver */ + if (argc < 5) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver requires at least " + "4 command line args."); + return (ISC_R_FAILURE); + } + + /* no more than 8 arg's should be passed to the driver */ + if (argc > 8) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver cannot accept more than " + "7 command line args."); + return (ISC_R_FAILURE); + } + + /* multithreaded build can have multiple DB connections */ + + /* check how many db connections we should create */ + dbcount = strtol(argv[1], &endp, 10); + if (*endp != '\0' || dbcount < 0) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver database connection count " + "must be positive."); + return (ISC_R_FAILURE); + } + + /* allocate memory for database connection list */ + dblist = isc_mem_get(named_g_mctx, sizeof(db_list_t)); + + /* initialize DB connection list */ + ISC_LIST_INIT(*dblist); + + /* + * create the appropriate number of database instances (DBI) + * append each new DBI to the end of the list + */ + for (i = 0; i < dbcount; i++) { + /* how many queries were passed in from config file? */ + switch (argc) { + case 5: + result = build_sqldbinstance(named_g_mctx, NULL, NULL, + NULL, argv[3], argv[4], + NULL, &dbi); + break; + case 6: + result = build_sqldbinstance(named_g_mctx, NULL, NULL, + argv[5], argv[3], argv[4], + NULL, &dbi); + break; + case 7: + result = build_sqldbinstance(named_g_mctx, argv[6], + NULL, argv[5], argv[3], + argv[4], NULL, &dbi); + break; + case 8: + result = build_sqldbinstance(named_g_mctx, argv[6], + argv[7], argv[5], argv[3], + argv[4], NULL, &dbi); + break; + default: + /* not really needed, should shut up compiler. */ + result = ISC_R_FAILURE; + } + + if (result == ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2), + "Postgres driver created database " + "instance object."); + } else { /* unsuccessful?, log err msg and cleanup. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver could not create " + "database instance object."); + goto cleanup; + } + + /* when multithreaded, build a list of DBI's */ + ISC_LINK_INIT(dbi, link); + ISC_LIST_APPEND(*dblist, dbi, link); + + /* create and set db connection */ + dbi->dbconn = PQconnectdb(argv[2]); + /* + * if db connection cannot be created, log err msg and + * cleanup. + */ + if (dbi->dbconn == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver could not allocate " + "memory for database connection"); + goto cleanup; + } + + /* if we cannot connect the first time, try 3 more times. */ + for (j = 0; + PQstatus((PGconn *)dbi->dbconn) != CONNECTION_OK && j < 3; + j++) + { + PQreset((PGconn *)dbi->dbconn); + } + + /* + * if multi threaded, let user know which connection + * failed. user could be attempting to create 10 db + * connections and for some reason the db backend only + * allows 9 + */ + if (PQstatus((PGconn *)dbi->dbconn) != CONNECTION_OK) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver failed to create " + "database connection number %u " + "after 4 attempts", + i + 1); + goto cleanup; + } + + /* set DBI = null for next loop through. */ + dbi = NULL; + } /* end for loop */ + + /* set dbdata to the list we created. */ + *dbdata = dblist; + + /* hey, we got through all of that ok, return success. */ + return (ISC_R_SUCCESS); + +cleanup: + + /* + * if multithreaded, we could fail because only 1 connection + * couldn't be made. We should cleanup the other successful + * connections properly. + */ + postgres_destroy_dblist(dblist); + + return (ISC_R_FAILURE); +} + +/*% + * destroy an instance of the driver. Remember, only 1 copy of the driver's + * code is ever loaded, the driver has to remember which context it's + * operating in. This is done via use of the dbdata argument. + * so we really only need to clean it up since we are not using driverarg. + */ +static void +postgres_destroy(void *driverarg, void *dbdata) { + UNUSED(driverarg); + /* cleanup the list of DBI's */ + postgres_destroy_dblist((db_list_t *)dbdata); +} + +/* pointers to all our runtime methods. */ +/* this is used during driver registration */ +/* i.e. in dlz_postgres_init below. */ +static dns_sdlzmethods_t dlz_postgres_methods = { + postgres_create, + postgres_destroy, + postgres_findzone, + postgres_lookup, + postgres_authority, + postgres_allnodes, + postgres_allowzonexfr, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +/*% + * Wrapper around dns_sdlzregister(). + */ +isc_result_t +dlz_postgres_init(void) { + isc_result_t result; + + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Registering DLZ postgres driver."); + + /* + * Driver is always threadsafe. When multithreaded all + * functions use multithreaded code. When not multithreaded, + * all functions can only be entered once, but only 1 thread + * of operation is available in Bind. So everything is still + * threadsafe. + */ + result = dns_sdlzregister("postgres", &dlz_postgres_methods, NULL, + DNS_SDLZFLAG_RELATIVEOWNER | + DNS_SDLZFLAG_RELATIVERDATA | + DNS_SDLZFLAG_THREADSAFE, + named_g_mctx, &dlz_postgres); + /* if we can't register the driver, there are big problems. */ + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "dns_sdlzregister() failed: %s", + isc_result_totext(result)); + result = ISC_R_UNEXPECTED; + } + + return (result); +} + +/*% + * Wrapper around dns_sdlzunregister(). + */ +void +dlz_postgres_clear(void) { + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Unregistering DLZ postgres driver."); + + /* unregister the driver. */ + if (dlz_postgres != NULL) { + dns_sdlzunregister(&dlz_postgres); + } +} + +#endif /* ifdef DLZ_POSTGRES */ diff --git a/contrib/dlz/drivers/dlz_stub_driver.c b/contrib/dlz/drivers/dlz_stub_driver.c new file mode 100644 index 0000000..f2e6f5f --- /dev/null +++ b/contrib/dlz/drivers/dlz_stub_driver.c @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +/* + * Copyright (C) 1999-2001, 2016 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/. + */ + +#ifdef DLZ_STUB +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +#include <dns/log.h> +#include <dns/result.h> +#include <dns/sdlz.h> + +#include <dlz/dlz_stub_driver.h> +#include <named/globals.h> + +static dns_sdlzimplementation_t *dlz_stub = NULL; + +typedef struct config_data { + char *myzone; + char *myname; + char *myip; + isc_mem_t *mctx; +} config_data_t; + +/* + * SDLZ methods + */ + +static isc_result_t +stub_dlz_allnodes(const char *zone, void *driverarg, void *dbdata, + dns_sdlzallnodes_t *allnodes) { + config_data_t *cd; + isc_result_t result; + + UNUSED(zone); + UNUSED(driverarg); + + cd = (config_data_t *)dbdata; + + result = dns_sdlz_putnamedrr(allnodes, cd->myname, "soa", 86400, + "web root.localhost. " + "0 28800 7200 604800 86400"); + if (result != ISC_R_SUCCESS) { + return (ISC_R_FAILURE); + } + result = dns_sdlz_putnamedrr(allnodes, "ns", "ns", 86400, cd->myname); + if (result != ISC_R_SUCCESS) { + return (ISC_R_FAILURE); + } + result = dns_sdlz_putnamedrr(allnodes, cd->myname, "a", 1, cd->myip); + if (result != ISC_R_SUCCESS) { + return (ISC_R_FAILURE); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +stub_dlz_allowzonexfr(void *driverarg, void *dbdata, const char *name, + const char *client) { + config_data_t *cd; + + UNUSED(driverarg); + UNUSED(client); + + cd = (config_data_t *)dbdata; + + if (strcmp(name, cd->myname) == 0) { + return (ISC_R_SUCCESS); + } + return (ISC_R_NOTFOUND); +} + +static isc_result_t +stub_dlz_authority(const char *zone, void *driverarg, void *dbdata, + dns_sdlzlookup_t *lookup) { + isc_result_t result; + config_data_t *cd; + + UNUSED(driverarg); + + cd = (config_data_t *)dbdata; + + if (strcmp(zone, cd->myzone) == 0) { + result = dns_sdlz_putsoa(lookup, cd->myname, "root.localhost.", + 0); + if (result != ISC_R_SUCCESS) { + return (ISC_R_FAILURE); + } + + result = dns_sdlz_putrr(lookup, "ns", 86400, cd->myname); + if (result != ISC_R_SUCCESS) { + return (ISC_R_FAILURE); + } + + return (ISC_R_SUCCESS); + } + return (ISC_R_NOTFOUND); +} + +static isc_result_t +stub_dlz_findzonedb(void *driverarg, void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + config_data_t *cd; + + UNUSED(driverarg); + UNUSED(methods); + UNUSED(clientinfo); + + cd = (config_data_t *)dbdata; + + /* Write info message to log */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "dlz_stub findzone looking for '%s'", + name); + + if (strcmp(cd->myzone, name) == 0) { + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +static isc_result_t +stub_dlz_lookup(const char *zone, const char *name, void *driverarg, + void *dbdata, dns_sdlzlookup_t *lookup, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + isc_result_t result; + config_data_t *cd; + + UNUSED(zone); + UNUSED(driverarg); + UNUSED(methods); + UNUSED(clientinfo); + + cd = (config_data_t *)dbdata; + + if (strcmp(name, cd->myname) == 0) { + result = dns_sdlz_putrr(lookup, "a", 1, cd->myip); + if (result != ISC_R_SUCCESS) { + return (ISC_R_FAILURE); + } + + return (ISC_R_SUCCESS); + } + return (ISC_R_FAILURE); +} + +static isc_result_t +stub_dlz_create(const char *dlzname, unsigned int argc, char *argv[], + void *driverarg, void **dbdata) { + config_data_t *cd; + + UNUSED(driverarg); + + if (argc < 4) { + return (ISC_R_FAILURE); + } + /* + * Write info message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_INFO, + "Loading '%s' using DLZ_stub driver. " + "Zone: %s, Name: %s IP: %s", + dlzname, argv[1], argv[2], argv[3]); + + cd = isc_mem_get(named_g_mctx, sizeof(config_data_t)); + if ((cd) == NULL) { + return (ISC_R_NOMEMORY); + } + + memset(cd, 0, sizeof(config_data_t)); + + cd->myzone = isc_mem_strdup(named_g_mctx, argv[1]); + + cd->myname = isc_mem_strdup(named_g_mctx, argv[2]); + + cd->myip = isc_mem_strdup(named_g_mctx, argv[3]); + + isc_mem_attach(named_g_mctx, &cd->mctx); + + *dbdata = cd; + + return (ISC_R_SUCCESS); +} + +static void +stub_dlz_destroy(void *driverarg, void *dbdata) { + config_data_t *cd; + isc_mem_t *mctx; + + UNUSED(driverarg); + + cd = (config_data_t *)dbdata; + + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Unloading DLZ_stub driver."); + + isc_mem_free(named_g_mctx, cd->myzone); + isc_mem_free(named_g_mctx, cd->myname); + isc_mem_free(named_g_mctx, cd->myip); + mctx = cd->mctx; + isc_mem_putanddetach(&mctx, cd, sizeof(config_data_t)); +} + +static dns_sdlzmethods_t dlz_stub_methods = { + stub_dlz_create, + stub_dlz_destroy, + stub_dlz_findzonedb, + stub_dlz_lookup, + stub_dlz_authority, + stub_dlz_allnodes, + stub_dlz_allowzonexfr, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +/*% + * Wrapper around dns_sdlzregister(). + */ +isc_result_t +dlz_stub_init(void) { + isc_result_t result; + + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Registering DLZ_stub driver."); + + result = dns_sdlzregister("dlz_stub", &dlz_stub_methods, NULL, + DNS_SDLZFLAG_RELATIVEOWNER | + DNS_SDLZFLAG_RELATIVERDATA, + named_g_mctx, &dlz_stub); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "dns_sdlzregister() failed: %s", + isc_result_totext(result)); + result = ISC_R_UNEXPECTED; + } + + return (result); +} + +/* + * Wrapper around dns_sdlzunregister(). + */ +void +dlz_stub_clear(void) { + /* + * Write debugging message to log + */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ, + ISC_LOG_DEBUG(2), "Unregistering DLZ_stub driver."); + + if (dlz_stub != NULL) { + dns_sdlzunregister(&dlz_stub); + } +} + +#endif /* ifdef DLZ_STUB */ diff --git a/contrib/dlz/drivers/include/.clang-format b/contrib/dlz/drivers/include/.clang-format new file mode 120000 index 0000000..e919bba --- /dev/null +++ b/contrib/dlz/drivers/include/.clang-format @@ -0,0 +1 @@ +../../../../.clang-format.headers
\ No newline at end of file diff --git a/contrib/dlz/drivers/include/dlz/dlz_bdb_driver.h b/contrib/dlz/drivers/include/dlz/dlz_bdb_driver.h new file mode 100644 index 0000000..afe903d --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_bdb_driver.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +#ifndef DLZ_BDB_DRIVER_H +#define DLZ_BDB_DRIVER_H + +isc_result_t +dlz_bdb_init(void); + +void +dlz_bdb_clear(void); + +#endif /* ifndef DLZ_BDB_DRIVER_H */ diff --git a/contrib/dlz/drivers/include/dlz/dlz_bdbhpt_driver.h b/contrib/dlz/drivers/include/dlz/dlz_bdbhpt_driver.h new file mode 100644 index 0000000..c4d151f --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_bdbhpt_driver.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +#ifndef DLZ_BDBHPT_DRIVER_H +#define DLZ_BDBHPT_DRIVER_H + +isc_result_t +dlz_bdbhpt_init(void); + +void +dlz_bdbhpt_clear(void); + +#endif /* ifndef DLZ_BDBHPT_DRIVER_H */ diff --git a/contrib/dlz/drivers/include/dlz/dlz_dlopen_driver.h b/contrib/dlz/drivers/include/dlz/dlz_dlopen_driver.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_dlopen_driver.h diff --git a/contrib/dlz/drivers/include/dlz/dlz_drivers.h b/contrib/dlz/drivers/include/dlz/dlz_drivers.h new file mode 100644 index 0000000..f3f66c5 --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_drivers.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2005 Internet Systems Consortium, Inc. ("ISC") + * 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. + */ + +#ifndef DLZ_DRIVERS_H +#define DLZ_DRIVERS_H 1 + +/*! \file */ + +isc_result_t +dlz_drivers_init(void); + +void +dlz_drivers_clear(void); + +#endif /* DLZ_DRIVERS_H */ diff --git a/contrib/dlz/drivers/include/dlz/dlz_filesystem_driver.h b/contrib/dlz/drivers/include/dlz/dlz_filesystem_driver.h new file mode 100644 index 0000000..dc83582 --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_filesystem_driver.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +#ifndef DLZ_FILESYSTEM_DRIVER_H +#define DLZ_FILESYSTEM_DRIVER_H + +isc_result_t +dlz_fs_init(void); + +void +dlz_fs_clear(void); + +#endif /* ifndef DLZ_FILESYSTEM_DRIVER_H */ diff --git a/contrib/dlz/drivers/include/dlz/dlz_ldap_driver.h b/contrib/dlz/drivers/include/dlz/dlz_ldap_driver.h new file mode 100644 index 0000000..ec01d97 --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_ldap_driver.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +#ifndef DLZ_LDAP_DRIVER_H +#define DLZ_LDAP_DRIVER_H + +isc_result_t +dlz_ldap_init(void); + +void +dlz_ldap_clear(void); + +#endif /* ifndef DLZ_LDAP_DRIVER_H */ diff --git a/contrib/dlz/drivers/include/dlz/dlz_mysql_driver.h b/contrib/dlz/drivers/include/dlz/dlz_mysql_driver.h new file mode 100644 index 0000000..b419482 --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_mysql_driver.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +#ifndef DLZ_MYSQL_DRIVER_H +#define DLZ_MYSQL_DRIVER_H + +isc_result_t +dlz_mysql_init(void); + +void +dlz_mysql_clear(void); + +#endif /* ifndef DLZ_MYSQL_DRIVER_H */ diff --git a/contrib/dlz/drivers/include/dlz/dlz_odbc_driver.h b/contrib/dlz/drivers/include/dlz/dlz_odbc_driver.h new file mode 100644 index 0000000..023c746 --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_odbc_driver.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +#ifndef DLZ_ODBC_DRIVER_H +#define DLZ_ODBC_DRIVER_H + +isc_result_t +dlz_odbc_init(void); + +void +dlz_odbc_clear(void); + +#endif /* ifndef DLZ_ODBC_DRIVER_H */ diff --git a/contrib/dlz/drivers/include/dlz/dlz_postgres_driver.h b/contrib/dlz/drivers/include/dlz/dlz_postgres_driver.h new file mode 100644 index 0000000..f0692f1 --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_postgres_driver.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +#ifndef DLZ_POSTGRES_DRIVER_H +#define DLZ_POSTGRES_DRIVER_H + +isc_result_t +dlz_postgres_init(void); + +void +dlz_postgres_clear(void); + +#endif /* ifndef DLZ_POSTGRES_DRIVER_H */ diff --git a/contrib/dlz/drivers/include/dlz/dlz_stub_driver.h b/contrib/dlz/drivers/include/dlz/dlz_stub_driver.h new file mode 100644 index 0000000..bf9b90a --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_stub_driver.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +#ifndef DLZ_STUB_DRIVER_H +#define DLZ_STUB_DRIVER_H + +isc_result_t +dlz_stub_init(void); + +void +dlz_stub_clear(void); + +#endif /* ifndef DLZ_STUB_DRIVER_H */ diff --git a/contrib/dlz/drivers/include/dlz/sdlz_helper.h b/contrib/dlz/drivers/include/dlz/sdlz_helper.h new file mode 100644 index 0000000..1ddf3b9 --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/sdlz_helper.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +#ifndef SDLZHELPER_H +#define SDLZHELPER_H + +#include <stdbool.h> + +/* + * Types + */ +#define SDLZH_REQUIRE_CLIENT 0x01 +#define SDLZH_REQUIRE_QUERY 0x02 +#define SDLZH_REQUIRE_RECORD 0x04 +#define SDLZH_REQUIRE_ZONE 0x08 + +typedef struct query_segment query_segment_t; +typedef ISC_LIST(query_segment_t) query_list_t; +typedef struct dbinstance dbinstance_t; +typedef ISC_LIST(dbinstance_t) db_list_t; +typedef struct driverinstance driverinstance_t; + +/*% + * a query segment is all the text between our special tokens + * special tokens are %zone%, %record%, %client% + */ +struct query_segment { + void *sql; + unsigned int strlen; + bool direct; + ISC_LINK(query_segment_t) link; +}; + +/*% + * a database instance contains everything we need for running + * a query against the database. Using it each separate thread + * can dynamically construct a query and execute it against the + * database. The "instance_lock" and locking code in the driver's + * make sure no two threads try to use the same DBI at a time. + */ +struct dbinstance { + void *dbconn; + query_list_t *allnodes_q; + query_list_t *allowxfr_q; + query_list_t *authority_q; + query_list_t *findzone_q; + query_list_t *lookup_q; + query_list_t *countzone_q; + char *query_buf; + char *zone; + char *record; + char *client; + isc_mem_t *mctx; + isc_mutex_t instance_lock; + ISC_LINK(dbinstance_t) link; +}; + +/* + * Method declarations + */ + +/* see the code in sdlz_helper.c for more information on these methods */ + +char * +sdlzh_build_querystring(isc_mem_t *mctx, query_list_t *querylist); + +isc_result_t +sdlzh_build_sqldbinstance(isc_mem_t *mctx, const char *allnodes_str, + const char *allowxfr_str, const char *authority_str, + const char *findzone_str, const char *lookup_str, + const char *countzone_str, dbinstance_t **dbi); + +void +sdlzh_destroy_sqldbinstance(dbinstance_t *dbi); + +char * +sdlzh_get_parameter_value(isc_mem_t *mctx, const char *input, const char *key); + +/* Compatibility with existing DLZ drivers */ + +#define build_querystring sdlzh_build_querystring +#define build_sqldbinstance sdlzh_build_sqldbinstance +#define destroy_sqldbinstance sdlzh_destroy_sqldbinstance + +#define getParameterValue(x, y) \ + sdlzh_get_parameter_value(named_g_mctx, (x), (y)) + +#endif /* SDLZHELPER_H */ diff --git a/contrib/dlz/drivers/rules.in b/contrib/dlz/drivers/rules.in new file mode 100644 index 0000000..560c200 --- /dev/null +++ b/contrib/dlz/drivers/rules.in @@ -0,0 +1,50 @@ +# Copyright (C) 2005 Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND 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. + +dlz_drivers.@O@: ${DLZ_DRIVER_DIR}/dlz_drivers.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_drivers.h + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_drivers.c + +sdlz_helper.@O@: ${DLZ_DRIVER_DIR}/sdlz_helper.c ${DLZ_DRIVER_DIR}/include/dlz/sdlz_helper.h + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/sdlz_helper.c + + +dlz_bdb_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_bdb_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_bdb_driver.h + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_bdb_driver.c + +dlz_bdbhpt_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_bdbhpt_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_bdbhpt_driver.h + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_bdbhpt_driver.c + +dlz_filesystem_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_filesystem_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_filesystem_driver.h + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_filesystem_driver.c + +dlz_ldap_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_ldap_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_ldap_driver.h + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_ldap_driver.c + +dlz_mysql_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_mysql_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_mysql_driver.h + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_mysql_driver.c + +dlz_odbc_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_odbc_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_odbc_driver.h + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_odbc_driver.c + +dlz_postgres_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_postgres_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_postgres_driver.h + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_postgres_driver.c + +dlz_dlopen_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_dlopen_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_dlopen_driver.h + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_dlopen_driver.c + +dlz_stub_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_stub_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_stub_driver.h + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_stub_driver.c + diff --git a/contrib/dlz/drivers/sdlz_helper.c b/contrib/dlz/drivers/sdlz_helper.c new file mode 100644 index 0000000..96c2be7 --- /dev/null +++ b/contrib/dlz/drivers/sdlz_helper.c @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * 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. + */ + +/* + * Copyright (C) 1999-2001, 2016 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/. + */ + +#include <stdbool.h> + +#include <isc/mem.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/util.h> + +#include <dns/log.h> +#include <dns/result.h> + +#include <dlz/sdlz_helper.h> + +/* + * sdlz helper methods + */ + +/*% + * properly destroys a querylist by de-allocating the + * memory for each query segment, and then the list itself + */ + +static void +destroy_querylist(isc_mem_t *mctx, query_list_t **querylist) { + query_segment_t *tseg = NULL; + query_segment_t *nseg = NULL; + + REQUIRE(mctx != NULL); + + /* if query list is null, nothing to do */ + if (*querylist == NULL) { + return; + } + + /* start at the top of the list */ + nseg = ISC_LIST_HEAD(**querylist); + while (nseg != NULL) { /* loop, until end of list */ + tseg = nseg; + /* + * free the query segment's text string but only if it + * was really a query segment, and not a pointer to + * %zone%, or %record%, or %client% + */ + if (tseg->sql != NULL && tseg->direct) { + isc_mem_free(mctx, tseg->sql); + } + /* get the next query segment, before we destroy this one. */ + nseg = ISC_LIST_NEXT(nseg, link); + /* deallocate this query segment. */ + isc_mem_put(mctx, tseg, sizeof(query_segment_t)); + } + /* deallocate the query segment list */ + isc_mem_put(mctx, *querylist, sizeof(query_list_t)); +} + +/*% constructs a query list by parsing a string into query segments */ +static isc_result_t +build_querylist(isc_mem_t *mctx, const char *query_str, char **zone, + char **record, char **client, query_list_t **querylist, + unsigned int flags) { + isc_result_t result; + bool foundzone = false; + bool foundrecord = false; + bool foundclient = false; + char *free_me = NULL; + char *temp_str = NULL; + query_list_t *tql; + query_segment_t *tseg = NULL; + char *last = NULL; + + REQUIRE(querylist != NULL && *querylist == NULL); + REQUIRE(mctx != NULL); + + /* if query string is null, or zero length */ + if (query_str == NULL || strlen(query_str) < 1) { + if ((flags & SDLZH_REQUIRE_QUERY) == 0) { + /* we don't need it were ok. */ + return (ISC_R_SUCCESS); + } else { + /* we did need it, PROBLEM!!! */ + return (ISC_R_FAILURE); + } + } + + /* allocate memory for query list */ + tql = isc_mem_get(mctx, sizeof(query_list_t)); + + /* initialize the query segment list */ + ISC_LIST_INIT(*tql); + + /* make a copy of query_str so we can chop it up */ + free_me = temp_str = isc_mem_strdup(mctx, query_str); + /* couldn't make a copy, problem!! */ + if (temp_str == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* loop through the string and chop it up */ + for (;;) { + /* + * Split string into tokens at '$'. + */ + const char *sql = strtok_r(temp_str, "$", &last); + if (sql == NULL) { + break; + } + temp_str = NULL; + + /* allocate memory for tseg */ + tseg = isc_mem_get(mctx, sizeof(query_segment_t)); + tseg->sql = NULL; + tseg->direct = false; + /* initialize the query segment link */ + ISC_LINK_INIT(tseg, link); + /* append the query segment to the list */ + ISC_LIST_APPEND(*tql, tseg, link); + + tseg->sql = isc_mem_strdup(mctx, sql); + /* tseg->sql points directly to a string. */ + tseg->direct = true; + tseg->strlen = strlen(tseg->sql); + + /* check if we encountered "$zone$" token */ + if (strcasecmp(tseg->sql, "zone") == 0) { + /* + * we don't really need, or want the "zone" + * text, so get rid of it. + */ + isc_mem_free(mctx, tseg->sql); + /* set tseg->sql to in-direct zone string */ + tseg->sql = zone; + tseg->strlen = 0; + /* tseg->sql points in-directly to a string */ + tseg->direct = false; + foundzone = true; + /* check if we encountered "$record$" token */ + } else if (strcasecmp(tseg->sql, "record") == 0) { + /* + * we don't really need, or want the "record" + * text, so get rid of it. + */ + isc_mem_free(mctx, tseg->sql); + /* set tseg->sql to in-direct record string */ + tseg->sql = record; + tseg->strlen = 0; + /* tseg->sql points in-directly points to a string */ + tseg->direct = false; + foundrecord = true; + /* check if we encountered "$client$" token */ + } else if (strcasecmp(tseg->sql, "client") == 0) { + /* + * we don't really need, or want the "client" + * text, so get rid of it. + */ + isc_mem_free(mctx, tseg->sql); + /* set tseg->sql to in-direct record string */ + tseg->sql = client; + tseg->strlen = 0; + /* tseg->sql points in-directly points to a string */ + tseg->direct = false; + foundclient = true; + } + } + + /* we don't need temp_str any more */ + isc_mem_free(mctx, free_me); + /* + * add checks later to verify zone and record are found if + * necessary. + */ + + /* if this query requires %client%, make sure we found it */ + if (((flags & SDLZH_REQUIRE_CLIENT) != 0) && (!foundclient)) { + /* Write error message to log */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Required token $client$ not found."); + result = ISC_R_FAILURE; + goto flag_fail; + } + + /* if this query requires %record%, make sure we found it */ + if (((flags & SDLZH_REQUIRE_RECORD) != 0) && (!foundrecord)) { + /* Write error message to log */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Required token $record$ not found."); + result = ISC_R_FAILURE; + goto flag_fail; + } + + /* if this query requires %zone%, make sure we found it */ + if (((flags & SDLZH_REQUIRE_ZONE) != 0) && (!foundzone)) { + /* Write error message to log */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Required token $zone$ not found."); + result = ISC_R_FAILURE; + goto flag_fail; + } + + /* pass back the query list */ + *querylist = (query_list_t *)tql; + + /* return success */ + return (ISC_R_SUCCESS); + +cleanup: + /* get rid of free_me */ + if (free_me != NULL) { + isc_mem_free(mctx, free_me); + } + +flag_fail: + /* get rid of what was build of the query list */ + destroy_querylist(mctx, &tql); + return (result); +} + +/*% + * build a query string from query segments, and dynamic segments + * dynamic segments replace where the tokens %zone%, %record%, %client% + * used to be in our queries from named.conf + */ +char * +sdlzh_build_querystring(isc_mem_t *mctx, query_list_t *querylist) { + query_segment_t *tseg = NULL; + unsigned int length = 0; + char *qs = NULL; + + REQUIRE(mctx != NULL); + REQUIRE(querylist != NULL); + + /* start at the top of the list */ + tseg = ISC_LIST_HEAD(*querylist); + while (tseg != NULL) { + /* + * if this is a query segment, use the + * precalculated string length + */ + if (tseg->direct) { + length += tseg->strlen; + } else { /* calculate string length for dynamic segments. */ + length += strlen(*(char **)tseg->sql); + } + /* get the next segment */ + tseg = ISC_LIST_NEXT(tseg, link); + } + + /* allocate memory for the string */ + qs = isc_mem_allocate(mctx, length + 1); + + *qs = 0; + /* start at the top of the list again */ + tseg = ISC_LIST_HEAD(*querylist); + while (tseg != NULL) { + if (tseg->direct) { + /* query segments */ + strcat(qs, tseg->sql); + } else { + /* dynamic segments */ + strcat(qs, *(char **)tseg->sql); + } + /* get the next segment */ + tseg = ISC_LIST_NEXT(tseg, link); + } + + return (qs); +} + +/*% constructs a sql dbinstance (DBI) */ +isc_result_t +sdlzh_build_sqldbinstance(isc_mem_t *mctx, const char *allnodes_str, + const char *allowxfr_str, const char *authority_str, + const char *findzone_str, const char *lookup_str, + const char *countzone_str, dbinstance_t **dbi) { + isc_result_t result; + dbinstance_t *db = NULL; + + REQUIRE(dbi != NULL && *dbi == NULL); + REQUIRE(mctx != NULL); + + /* allocate and zero memory for driver structure */ + db = isc_mem_get(mctx, sizeof(dbinstance_t)); + memset(db, 0, sizeof(dbinstance_t)); + db->dbconn = NULL; + db->client = NULL; + db->record = NULL; + db->zone = NULL; + db->mctx = NULL; + db->query_buf = NULL; + db->allnodes_q = NULL; + db->allowxfr_q = NULL; + db->authority_q = NULL; + db->findzone_q = NULL; + db->countzone_q = NULL; + db->lookup_q = NULL; + + /* attach to the memory context */ + isc_mem_attach(mctx, &db->mctx); + + /* initialize the reference count mutex */ + isc_mutex_init(&db->instance_lock); + + /* build the all nodes query list */ + result = build_querylist(mctx, allnodes_str, &db->zone, &db->record, + &db->client, &db->allnodes_q, + SDLZH_REQUIRE_ZONE); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Could not build all nodes query list"); + goto cleanup; + } + + /* build the allow zone transfer query list */ + result = build_querylist(mctx, allowxfr_str, &db->zone, &db->record, + &db->client, &db->allowxfr_q, + SDLZH_REQUIRE_ZONE | SDLZH_REQUIRE_CLIENT); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Could not build allow xfr query list"); + goto cleanup; + } + + /* build the authority query, query list */ + result = build_querylist(mctx, authority_str, &db->zone, &db->record, + &db->client, &db->authority_q, + SDLZH_REQUIRE_ZONE); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Could not build authority query list"); + goto cleanup; + } + + /* build findzone query, query list */ + result = build_querylist(mctx, findzone_str, &db->zone, &db->record, + &db->client, &db->findzone_q, + SDLZH_REQUIRE_ZONE); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Could not build find zone query list"); + goto cleanup; + } + + /* build countzone query, query list */ + result = build_querylist(mctx, countzone_str, &db->zone, &db->record, + &db->client, &db->countzone_q, + SDLZH_REQUIRE_ZONE); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Could not build count zone query list"); + goto cleanup; + } + + /* build lookup query, query list */ + result = build_querylist(mctx, lookup_str, &db->zone, &db->record, + &db->client, &db->lookup_q, + SDLZH_REQUIRE_RECORD); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Could not build lookup query list"); + goto cleanup; + } + + /* pass back the db instance */ + *dbi = (dbinstance_t *)db; + + /* return success */ + return (ISC_R_SUCCESS); + +cleanup: + /* destroy whatever was build of the db instance */ + destroy_sqldbinstance(db); + /* return failure */ + return (ISC_R_FAILURE); +} + +void +sdlzh_destroy_sqldbinstance(dbinstance_t *dbi) { + isc_mem_t *mctx; + + /* save mctx for later */ + mctx = dbi->mctx; + + /* destroy any query lists we created */ + destroy_querylist(mctx, &dbi->allnodes_q); + destroy_querylist(mctx, &dbi->allowxfr_q); + destroy_querylist(mctx, &dbi->authority_q); + destroy_querylist(mctx, &dbi->findzone_q); + destroy_querylist(mctx, &dbi->countzone_q); + destroy_querylist(mctx, &dbi->lookup_q); + + /* get rid of the mutex */ + (void)isc_mutex_destroy(&dbi->instance_lock); + + /* return, and detach the memory */ + isc_mem_putanddetach(&mctx, dbi, sizeof(dbinstance_t)); +} + +char * +sdlzh_get_parameter_value(isc_mem_t *mctx, const char *input, const char *key) { + int keylen; + char *keystart; + char value[255]; + int i; + + if (key == NULL || input == NULL || strlen(input) < 1) { + return (NULL); + } + + keylen = strlen(key); + + if (keylen < 1) { + return (NULL); + } + + keystart = strstr(input, key); + + if (keystart == NULL) { + return (NULL); + } + + REQUIRE(mctx != NULL); + + for (i = 0; i < 255; i++) { + value[i] = keystart[keylen + i]; + if (value[i] == ' ' || value[i] == '\0') { + value[i] = '\0'; + break; + } + } + + return (isc_mem_strdup(mctx, value)); +} diff --git a/contrib/dlz/example/Makefile b/contrib/dlz/example/Makefile new file mode 100644 index 0000000..c447940 --- /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..d36fba4 --- /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 +master configuration, with slaves retrieving zone updates via AXFR. +(Note, however, that DLZ has no built-in support for DNS notify; slaves +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..1a23be9 --- /dev/null +++ b/contrib/dlz/example/dlz_example.c @@ -0,0 +1,822 @@ +/* + * 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; +#if defined(WIN32) || defined(_REENTRANT) + char *saveptr = NULL; +#endif /* if defined(WIN32) || defined(_REENTRANT) */ + + 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"; +}; diff --git a/contrib/dlz/example/win32/DLLMain.c b/contrib/dlz/example/win32/DLLMain.c new file mode 100644 index 0000000..ca76934 --- /dev/null +++ b/contrib/dlz/example/win32/DLLMain.c @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2001, 2004, 2007, 2016 Internet Systems Consortium, Inc. + * 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. + */ + +#include <signal.h> +#include <windows.h> + +/* + * Called when we enter the DLL + */ +__declspec(dllexport) BOOL WINAPI + DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { + switch (fdwReason) { + /* + * The DLL is loading due to process + * initialization or a call to LoadLibrary. + */ + case DLL_PROCESS_ATTACH: + break; + + /* The attached process creates a new thread. */ + case DLL_THREAD_ATTACH: + break; + + /* The thread of the attached process terminates. */ + case DLL_THREAD_DETACH: + break; + + /* + * The DLL is unloading from a process due to + * process termination or a call to FreeLibrary. + */ + case DLL_PROCESS_DETACH: + break; + + default: + break; + } + return (TRUE); +} diff --git a/contrib/dlz/example/win32/dxdriver.def b/contrib/dlz/example/win32/dxdriver.def new file mode 100644 index 0000000..6d97466 --- /dev/null +++ b/contrib/dlz/example/win32/dxdriver.def @@ -0,0 +1,20 @@ +LIBRARY dxdriver + +; Exported Functions +EXPORTS +dlz_addrdataset +dlz_allnodes +dlz_allowzonexfr +dlz_closeversion +dlz_configure +dlz_create +dlz_delrdataset +dlz_destroy +dlz_findzonedb +dlz_lookup +dlz_newversion +dlz_ssumatch +dlz_subrdataset +dlz_version + + diff --git a/contrib/dlz/example/win32/dxdriver.dsp b/contrib/dlz/example/win32/dxdriver.dsp new file mode 100644 index 0000000..c096b08 --- /dev/null +++ b/contrib/dlz/example/win32/dxdriver.dsp @@ -0,0 +1,121 @@ +# Microsoft Developer Studio Project File - Name="dxdriver" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=dxdriver - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "dxdriver.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "dxdriver.mak" CFG="dxdriver - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "dxdriver - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "dxdriver - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "dxdriver - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "../" /I "../../../../../" /I "../../../../../lib/isc/win32" /I "../../../../../lib/isc/win32/include" /I "../../../../../lib/isc/include" /I "../../../../../lib/dns/include" /D "NDEBUG" /D "WIN32" /D "__STDC__" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /YX /FD /c +# SUBTRACT CPP /X +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 user32.lib advapi32.lib ws2_32.lib /nologo /dll /machine:I386 /out:"../../../../../Build/Release/dxdriver.dll" + +!ELSEIF "$(CFG)" == "dxdriver - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "../" /I "../../../../../" /I "../../../../../lib/isc/win32" /I "../../../../../lib/isc/win32/include" /I "../../../../../lib/isc/include" /I "../../../../../lib/dns/include" /D "_DEBUG" /D "WIN32" /D "__STDC__" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /FR /YX /FD /GZ /c +# SUBTRACT CPP /X +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 user32.lib advapi32.lib ws2_32.lib /nologo /dll /debug /machine:I386 /out:"../../../../../Build/Debug/dxdriver.dll" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "dxdriver - Win32 Release" +# Name "dxdriver - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\driver.c +# End Source File +# Begin Source File + +SOURCE=.\DLLMain.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\driver.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# Begin Source File + +SOURCE=.\dxdriver.def +# End Source File +# End Target +# End Project diff --git a/contrib/dlz/example/win32/dxdriver.dsw b/contrib/dlz/example/win32/dxdriver.dsw new file mode 100644 index 0000000..b0212fe --- /dev/null +++ b/contrib/dlz/example/win32/dxdriver.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "dxdriver"=.\dxdriver.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/contrib/dlz/example/win32/dxdriver.mak b/contrib/dlz/example/win32/dxdriver.mak new file mode 100644 index 0000000..1d8506b --- /dev/null +++ b/contrib/dlz/example/win32/dxdriver.mak @@ -0,0 +1,298 @@ +# Microsoft Developer Studio Generated NMAKE File, Based on dxdriver.dsp +!IF "$(CFG)" == "" +CFG=dxdriver - Win32 Release +!MESSAGE No configuration specified. Defaulting to dxdriver - Win32 Release. +!ENDIF + +!IF "$(CFG)" != "dxdriver - Win32 Release" && "$(CFG)" != "dxdriver - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "dxdriver.mak" CFG="dxdriver - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "dxdriver - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "dxdriver - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "dxdriver - Win32 Release" +_VC_MANIFEST_INC=0 +_VC_MANIFEST_BASENAME=__VC80 +!ELSE +_VC_MANIFEST_INC=1 +_VC_MANIFEST_BASENAME=__VC80.Debug +!ENDIF + +#################################################### +# Specifying name of temporary resource file used only in incremental builds: + +!if "$(_VC_MANIFEST_INC)" == "1" +_VC_MANIFEST_AUTO_RES=$(_VC_MANIFEST_BASENAME).auto.res +!else +_VC_MANIFEST_AUTO_RES= +!endif + +#################################################### +# _VC_MANIFEST_EMBED_EXE - command to embed manifest in EXE: + +!if "$(_VC_MANIFEST_INC)" == "1" + +#MT_SPECIAL_RETURN=1090650113 +#MT_SPECIAL_SWITCH=-notify_resource_update +MT_SPECIAL_RETURN=0 +MT_SPECIAL_SWITCH= +_VC_MANIFEST_EMBED_EXE= \ +if exist $@.manifest mt.exe -manifest $@.manifest -out:$(_VC_MANIFEST_BASENAME).auto.manifest $(MT_SPECIAL_SWITCH) & \ +if "%ERRORLEVEL%" == "$(MT_SPECIAL_RETURN)" \ +rc /r $(_VC_MANIFEST_BASENAME).auto.rc & \ +link $** /out:$@ $(LFLAGS) + +!else + +_VC_MANIFEST_EMBED_EXE= \ +if exist $@.manifest mt.exe -manifest $@.manifest -outputresource:$@;1 + +!endif + +#################################################### +# _VC_MANIFEST_EMBED_DLL - command to embed manifest in DLL: + +!if "$(_VC_MANIFEST_INC)" == "1" + +#MT_SPECIAL_RETURN=1090650113 +#MT_SPECIAL_SWITCH=-notify_resource_update +MT_SPECIAL_RETURN=0 +MT_SPECIAL_SWITCH= +_VC_MANIFEST_EMBED_EXE= \ +if exist $@.manifest mt.exe -manifest $@.manifest -out:$(_VC_MANIFEST_BASENAME).auto.manifest $(MT_SPECIAL_SWITCH) & \ +if "%ERRORLEVEL%" == "$(MT_SPECIAL_RETURN)" \ +rc /r $(_VC_MANIFEST_BASENAME).auto.rc & \ +link $** /out:$@ $(LFLAGS) + +!else + +_VC_MANIFEST_EMBED_EXE= \ +if exist $@.manifest mt.exe -manifest $@.manifest -outputresource:$@;2 + +!endif +#################################################### +# _VC_MANIFEST_CLEAN - command to clean resources files generated temporarily: + +!if "$(_VC_MANIFEST_INC)" == "1" + +_VC_MANIFEST_CLEAN=-del $(_VC_MANIFEST_BASENAME).auto.res \ + $(_VC_MANIFEST_BASENAME).auto.rc \ + $(_VC_MANIFEST_BASENAME).auto.manifest + +!else + +_VC_MANIFEST_CLEAN= + +!endif + +!IF "$(CFG)" == "dxdriver - Win32 Release" + +OUTDIR=.\Release +INTDIR=.\Release + +ALL : "..\..\..\..\..\Build\Release\dxdriver.dll" + +CLEAN : + -@erase "$(INTDIR)\DLLMain.obj" + -@erase "$(INTDIR)\driver.obj" + -@erase "$(INTDIR)\vc60.idb" + -@erase "$(OUTDIR)\dxdriver.exp" + -@erase "$(OUTDIR)\dxdriver.lib" + -@erase "..\..\..\..\..\Build\Release\dxdriver.dll" + -@$(_VC_MANIFEST_CLEAN) + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP_PROJ=/nologo /MD /W3 /GX /O2 /I "../" /I "../../../../../" /I "../../../../../lib/isc/win32" /I "../../../../../lib/isc/win32/include" /I "../../../../../lib/isc/include" /I "../../../../../lib/dns/include" /D "NDEBUG" /D "WIN32" /D "__STDC__" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /Fp"$(INTDIR)\dxdriver.pch" /YX /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" /FD /c +MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32 +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\dxdriver.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=user32.lib advapi32.lib ws2_32.lib /nologo /dll /incremental:no /pdb:"$(OUTDIR)\dxdriver.pdb" /machine:I386 /def:".\dxdriver.def" /out:"../../../../../Build/Release/dxdriver.dll" /implib:"$(OUTDIR)\dxdriver.lib" +DEF_FILE= \ + ".\dxdriver.def" +LINK32_OBJS= \ + "$(INTDIR)\DLLMain.obj" \ + "$(INTDIR)\driver.obj" + +"..\..\..\..\..\Build\Release\dxdriver.dll" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + $(_VC_MANIFEST_EMBED_DLL) + +!ELSEIF "$(CFG)" == "dxdriver - Win32 Debug" + +OUTDIR=.\Debug +INTDIR=.\Debug +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +ALL : "..\..\..\..\..\Build\Debug\dxdriver.dll" "$(OUTDIR)\dxdriver.bsc" + +CLEAN : + -@erase "$(INTDIR)\DLLMain.obj" + -@erase "$(INTDIR)\DLLMain.sbr" + -@erase "$(INTDIR)\driver.obj" + -@erase "$(INTDIR)\driver.sbr" + -@erase "$(INTDIR)\vc60.idb" + -@erase "$(INTDIR)\vc60.pdb" + -@erase "$(OUTDIR)\dxdriver.bsc" + -@erase "$(OUTDIR)\dxdriver.exp" + -@erase "$(OUTDIR)\dxdriver.lib" + -@erase "$(OUTDIR)\dxdriver.pdb" + -@erase "..\..\..\..\..\Build\Debug\dxdriver.dll" + -@erase "..\..\..\..\..\Build\Debug\dxdriver.ilk" + -@$(_VC_MANIFEST_CLEAN) + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP_PROJ=/nologo /MDd /W3 /Gm /GX /ZI /Od /I "../" /I "../../../../../" /I "../../../../../lib/isc/win32" /I "../../../../../lib/isc/win32/include" /I "../../../../../lib/isc/include" /I "../../../../../lib/dns/include" /D "_DEBUG" /D "WIN32" /D "__STDC__" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /FR"$(INTDIR)\\" /Fp"$(INTDIR)\dxdriver.pch" /YX /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" /FD /GZ /c +MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32 +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\dxdriver.bsc" +BSC32_SBRS= \ + "$(INTDIR)\DLLMain.sbr" \ + "$(INTDIR)\driver.sbr" + +"$(OUTDIR)\dxdriver.bsc" : "$(OUTDIR)" $(BSC32_SBRS) + $(BSC32) @<< + $(BSC32_FLAGS) $(BSC32_SBRS) +<< + +LINK32=link.exe +LINK32_FLAGS=user32.lib advapi32.lib ws2_32.lib /nologo /dll /incremental:yes /pdb:"$(OUTDIR)\dxdriver.pdb" /debug /machine:I386 /def:".\dxdriver.def" /out:"../../../../../Build/Debug/dxdriver.dll" /implib:"$(OUTDIR)\dxdriver.lib" /pdbtype:sept +DEF_FILE= \ + ".\dxdriver.def" +LINK32_OBJS= \ + "$(INTDIR)\DLLMain.obj" \ + "$(INTDIR)\driver.obj" + +"..\..\..\..\..\Build\Debug\dxdriver.dll" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + $(_VC_MANIFEST_EMBED_DLL) + +!ENDIF + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + + +!IF "$(NO_EXTERNAL_DEPS)" != "1" +!IF EXISTS("dxdriver.dep") +!INCLUDE "dxdriver.dep" +!ELSE +!MESSAGE Warning: cannot find "dxdriver.dep" +!ENDIF +!ENDIF + + +!IF "$(CFG)" == "dxdriver - Win32 Release" || "$(CFG)" == "dxdriver - Win32 Debug" +SOURCE=.\DLLMain.c + +!IF "$(CFG)" == "dxdriver - Win32 Release" + + +"$(INTDIR)\DLLMain.obj" : $(SOURCE) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "dxdriver - Win32 Debug" + + +"$(INTDIR)\DLLMain.obj" "$(INTDIR)\DLLMain.sbr" : $(SOURCE) "$(INTDIR)" + + +!ENDIF + +SOURCE=..\driver.c + +!IF "$(CFG)" == "dxdriver - Win32 Release" + + +"$(INTDIR)\driver.obj" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ELSEIF "$(CFG)" == "dxdriver - Win32 Debug" + + +"$(INTDIR)\driver.obj" "$(INTDIR)\driver.sbr" : $(SOURCE) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +!ENDIF + + +!ENDIF + +#################################################### +# Commands to generate initial empty manifest file and the RC file +# that references it, and for generating the .res file: + +$(_VC_MANIFEST_BASENAME).auto.res : $(_VC_MANIFEST_BASENAME).auto.rc + +$(_VC_MANIFEST_BASENAME).auto.rc : $(_VC_MANIFEST_BASENAME).auto.manifest + type <<$@ +#include <winuser.h> +1RT_MANIFEST"$(_VC_MANIFEST_BASENAME).auto.manifest" +<< KEEP + +$(_VC_MANIFEST_BASENAME).auto.manifest : + type <<$@ +<?xml version='1.0' encoding='UTF-8' standalone='yes'?> +<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'> +</assembly> +<< KEEP diff --git a/contrib/dlz/modules/bdbhpt/Makefile b/contrib/dlz/modules/bdbhpt/Makefile new file mode 100644 index 0000000..45080af --- /dev/null +++ b/contrib/dlz/modules/bdbhpt/Makefile @@ -0,0 +1,43 @@ +# Copyright 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 https://mozilla.org/MPL/2.0/. + +# Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. +# +# The development of Dynamically Loadable Zones (DLZ) for Bind 9 was +# conceived and contributed by Rob Butler. +# +# SPDX-License-Identifier: ISC and MPL-2.0 +# +# Permission to use, copy, modify, and distribute this software for any purpose +# with or without fee is hereby granted, provided that the above copyright +# notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +prefix = /usr +libdir = $(prefix)/lib/bind9 + +CFLAGS=-fPIC -g -I../include +BDB_LIBS=-ldb + +all: dlz_bdbhpt_dynamic.so + +dlz_bdbhpt_dynamic.so: dlz_bdbhpt_dynamic.c + $(CC) $(CFLAGS) -shared -o dlz_bdbhpt_dynamic.so \ + dlz_bdbhpt_dynamic.c $(BDB_LIBS) + +clean: + rm -f dlz_bdbhpt_dynamic.so + +install: dlz_bdbhpt_dynamic.so + mkdir -p $(DESTDIR)$(libdir) + install dlz_bdbhpt_dynamic.so $(DESTDIR)$(libdir) diff --git a/contrib/dlz/modules/bdbhpt/README.md b/contrib/dlz/modules/bdbhpt/README.md new file mode 100644 index 0000000..3a9e7a7 --- /dev/null +++ b/contrib/dlz/modules/bdbhpt/README.md @@ -0,0 +1,113 @@ +<!-- +Copyright 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 https://mozilla.org/MPL/2.0/. + +Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. + +The development of Dynamically Loadable Zones (DLZ) for Bind 9 was +conceived and contributed by Rob Butler. + +SPDX-License-Identifier: ISC and MPL-2.0 + +Permission to use, copy, modify, and distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright +notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +--> + +dlz-bdbhpt-dynamic +================== + +A Bind 9 Dynamically Loadable BerkeleyDB High Performance Text Driver + +Summary +------- + +This is an attempt to port the original Bind 9 DLZ bdbhpt_driver.c as +found in the Bind 9 source tree into the new DLZ dlopen driver API. +The goals of this project are as follows: + +* Provide DLZ facilities to OEM-supported Bind distributions +* Support both v1 (Bind 9.8) and v2 (Bind 9.9) of the dlopen() DLZ API + +Requirements +------------ + +You will need the following: + * Bind 9.8 or higher with the DLZ dlopen driver enabled + * BerkeleyDB libraries and header files + * A C compiler + +This distribution have been successfully installed and tested on +Ubuntu 12.04. + +Installation +------------ + +With the above requirements satisfied perform the following steps: + +1. Ensure the symlink for dlz_minimal.h points at the correct header + file matching your Bind version +2. Run: make +3. Run: sudo make install # this will install dlz_bdbhpt_dynamic.so + into /usr/lib/bind9/ +4. Add a DLZ statement similar to the example below into your + Bind configuration +5. Ensure your BerkeleyDB home-directory exists and can be written to + by the bind user +6. Use the included testing/bdbhpt-populate.pl script to provide some + data for initial testing + +Usage +----- + +Example usage is as follows: + +``` +dlz "bdbhpt_dynamic" { + database "dlopen /usr/lib/bind9/dlz_bdbhpt_dynamic.so T /var/cache/bind/dlz dnsdata.db"; +}; +``` + +The arguments for the "database" line above are as follows: + +1. dlopen - Use the dlopen DLZ driver to dynamically load our compiled + driver +2. The full path to your built dlz_bdbhpt_dynamic.so +3. Single character specifying the mode to open your BerkeleyDB + environment: + * T - Transactional Mode - Highest safety, lowest speed. + * C - Concurrent Mode - Lower safety (no rollback), higher speed. + * P - Private Mode - No interprocess communication & no locking. + Lowest safety, highest speed. +4. Directory containing your BerkeleyDB - this is where the BerkeleyDB + environment will be created. +5. Filename within this directory containing your BerkeleyDB tables. + +A copy of the above Bind configuration is included within +example/dlz.conf. + +Author +------ + +The person responsible for this is: + + Mark Goldfinch <g@g.org.nz> + +The code is maintained at: + + https://github.com/goldie80/dlz-bdbhpt-dynamic + +There is very little in the way of original code in this work, +however, original license conditions from both bdbhpt_driver.c and +dlz_example.c are maintained in the dlz_bdbhpt_dynamic.c. diff --git a/contrib/dlz/modules/bdbhpt/dlz_bdbhpt_dynamic.c b/contrib/dlz/modules/bdbhpt/dlz_bdbhpt_dynamic.c new file mode 100644 index 0000000..e84f866 --- /dev/null +++ b/contrib/dlz/modules/bdbhpt/dlz_bdbhpt_dynamic.c @@ -0,0 +1,837 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 and 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 https://mozilla.org/MPL/2.0/. + */ + +/* + * Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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 is simply a merge of Andrew Tridgell's dlz_example.c and the + * original bdb_bdbhpt_driver.c + * + * This provides the externally loadable bdbhpt DLZ driver, without + * update support + * + */ + +#include <db.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "dlz_minimal.h" + +/* should the bdb driver use threads. */ +#define bdbhpt_threads DB_THREAD + +/* bdbhpt database names */ +#define dlz_data "dns_data" +#define dlz_zone "dns_zone" +#define dlz_xfr "dns_xfr" +#define dlz_client "dns_client" + +#define dlz_bdbhpt_dynamic_version "0.1" + +/* + * This structure contains all our DB handles and helper functions we + * inherit from the dlz_dlopen driver + * + */ +typedef struct bdbhpt_instance { + DB_ENV *dbenv; /* bdbhpt environment */ + DB *data; /* dns_data database handle */ + DB *zone; /* zone database handle */ + DB *xfr; /* zone xfr database handle */ + DB *client; /* client database handle */ + + /* Helper functions from the dlz_dlopen driver */ + log_t *log; + dns_sdlz_putrr_t *putrr; + dns_sdlz_putnamedrr_t *putnamedrr; + dns_dlz_writeablezone_t *writeable_zone; +} bdbhpt_instance_t; + +typedef struct bdbhpt_parsed_data { + char *host; + char *type; + int ttl; + char *data; +} bdbhpt_parsed_data_t; + +static void +b9_add_helper(struct bdbhpt_instance *db, const char *helper_name, void *ptr); + +/*% + * Reverses a string in place. + */ +static char * +bdbhpt_strrev(char *str) { + char *p1, *p2; + + if (!str || !*str) { + return (str); + } + for (p1 = str, p2 = str + strlen(str) - 1; p2 > p1; ++p1, --p2) { + *p1 ^= *p2; + *p2 ^= *p1; + *p1 ^= *p2; + } + return (str); +} + +/*% + * Parses the DBT from the Berkeley DB into a parsed_data record + * The parsed_data record should be allocated before and passed into the + * bdbhpt_parse_data function. The char (type & data) fields should not + * be "free"d as that memory is part of the DBT data field. It will be + * "free"d when the DBT is freed. + */ + +static isc_result_t +bdbhpt_parse_data(log_t *log, char *in, bdbhpt_parsed_data_t *pd) { + char *endp, *ttlStr; + char *tmp = in; + char *lastchar = (char *)&tmp[strlen(tmp)]; + + /*% + * String should be formatted as: + * replication_id + * (a space) + * host_name + * (a space) + * ttl + * (a space) + * type + * (a space) + * remaining data + * + * examples: + * + * 9191 host 10 A 127.0.0.1 + * server1_212 host 10 A 127.0.0.2 + * {xxxx-xxxx-xxxx-xxxx-xxxx} host 10 MX 20 mail.example.com + */ + + /* + * we don't need the replication id, so don't + * bother saving a pointer to it. + */ + + /* find space after replication id */ + tmp = strchr(tmp, ' '); + /* verify we found a space */ + if (tmp == NULL) { + return (ISC_R_FAILURE); + } + /* make sure it is safe to increment pointer */ + if (++tmp > lastchar) { + return (ISC_R_FAILURE); + } + + /* save pointer to host */ + pd->host = tmp; + + /* find space after host and change it to a '\0' */ + tmp = strchr(tmp, ' '); + /* verify we found a space */ + if (tmp == NULL) { + return (ISC_R_FAILURE); + } + /* change the space to a null (string terminator) */ + tmp[0] = '\0'; + /* make sure it is safe to increment pointer */ + if (++tmp > lastchar) { + return (ISC_R_FAILURE); + } + + /* save pointer to ttl string */ + ttlStr = tmp; + + /* find space after ttl and change it to a '\0' */ + tmp = strchr(tmp, ' '); + /* verify we found a space */ + if (tmp == NULL) { + return (ISC_R_FAILURE); + } + /* change the space to a null (string terminator) */ + tmp[0] = '\0'; + /* make sure it is safe to increment pointer */ + if (++tmp > lastchar) { + return (ISC_R_FAILURE); + } + + /* save pointer to dns type */ + pd->type = tmp; + + /* find space after type and change it to a '\0' */ + tmp = strchr(tmp, ' '); + /* verify we found a space */ + if (tmp == NULL) { + return (ISC_R_FAILURE); + } + /* change the space to a null (string terminator) */ + tmp[0] = '\0'; + /* make sure it is safe to increment pointer */ + if (++tmp > lastchar) { + return (ISC_R_FAILURE); + } + + /* save pointer to remainder of DNS data */ + pd->data = tmp; + + /* convert ttl string to integer */ + pd->ttl = strtol(ttlStr, &endp, 10); + if (*endp != '\0' || pd->ttl < 0) { + log(ISC_LOG_ERROR, "bdbhpt_dynamic: " + "ttl must be a positive number"); + return (ISC_R_FAILURE); + } + + /* if we get this far everything should have worked. */ + return (ISC_R_SUCCESS); +} + +/* + * See if a zone transfer is allowed + */ +isc_result_t +dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { + isc_result_t result; + bdbhpt_instance_t *db = (bdbhpt_instance_t *)dbdata; + DBT key, data; + + /* check to see if we are authoritative for the zone first. */ +#if DLZ_DLOPEN_VERSION >= 3 + result = dlz_findzonedb(dbdata, name, NULL, NULL); +#else /* if DLZ_DLOPEN_VERSION >= 3 */ + result = dlz_findzonedb(dbdata, name); +#endif /* if DLZ_DLOPEN_VERSION >= 3 */ + if (result != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + memset(&key, 0, sizeof(DBT)); + key.flags = DB_DBT_MALLOC; + key.data = strdup(name); + if (key.data == NULL) { + result = ISC_R_NOMEMORY; + goto xfr_cleanup; + } + key.size = strlen(key.data); + + memset(&data, 0, sizeof(DBT)); + data.flags = DB_DBT_MALLOC; + data.data = strdup(client); + if (data.data == NULL) { + result = ISC_R_NOMEMORY; + goto xfr_cleanup; + } + data.size = strlen(data.data); + + switch (db->client->get(db->client, NULL, &key, &data, DB_GET_BOTH)) { + case DB_NOTFOUND: + result = ISC_R_NOTFOUND; + break; + case 0: + result = ISC_R_SUCCESS; + break; + default: + result = ISC_R_FAILURE; + } + +xfr_cleanup: + /* free any memory duplicate string in the key field */ + if (key.data != NULL) { + free(key.data); + } + + /* free any memory allocated to the data field. */ + if (data.data != NULL) { + free(data.data); + } + + return (result); +} + +/*% + * Perform a zone transfer + * + * BDB does not allow a secondary index on a database that allows + * duplicates. We have a few options: + * + * 1) kill speed by having lookup method use a secondary db which + * is associated to the primary DB with the DNS data. Then have + * another secondary db for zone transfer which also points to + * the dns_data primary. NO - The point of this driver is + * lookup performance. + * + * 2) Blow up database size by storing DNS data twice. Once for + * the lookup (dns_data) database, and a second time for the zone + * transfer (dns_xfr) database. NO - That would probably require + * a larger cache to provide good performance. Also, that would + * make the DB larger on disk potentially slowing it as well. + * + * 3) Loop through the dns_xfr database with a cursor to get + * all the different hosts in a zone. Then use the zone & host + * together to lookup the data in the dns_data database. YES - + * This may slow down zone xfr's a little, but that's ok they + * don't happen as often and don't need to be as fast. We can + * also use this table when deleting a zone (The BDB driver + * is read only - the delete would be used during replication + * updates by a separate process). + */ +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) { + isc_result_t result = ISC_R_NOTFOUND; + bdbhpt_instance_t *db = (bdbhpt_instance_t *)dbdata; + DBC *xfr_cursor = NULL; + DBC *dns_cursor = NULL; + DBT xfr_key, xfr_data, dns_key, dns_data; + int xfr_flags; + int dns_flags; + int bdbhptres; + bdbhpt_parsed_data_t pd; + char *tmp = NULL, *tmp_zone, *tmp_zone_host = NULL; + + memset(&xfr_key, 0, sizeof(DBT)); + memset(&xfr_data, 0, sizeof(DBT)); + memset(&dns_key, 0, sizeof(DBT)); + memset(&dns_data, 0, sizeof(DBT)); + + xfr_key.data = tmp_zone = strdup(zone); + if (xfr_key.data == NULL) { + return (ISC_R_NOMEMORY); + } + + xfr_key.size = strlen(xfr_key.data); + + /* get a cursor to loop through dns_xfr table */ + if (db->xfr->cursor(db->xfr, NULL, &xfr_cursor, 0) != 0) { + result = ISC_R_FAILURE; + goto allnodes_cleanup; + } + + /* get a cursor to loop through dns_data table */ + if (db->data->cursor(db->data, NULL, &dns_cursor, 0) != 0) { + result = ISC_R_FAILURE; + goto allnodes_cleanup; + } + + xfr_flags = DB_SET; + + /* loop through xfr table for specified zone. */ + while ((bdbhptres = xfr_cursor->c_get(xfr_cursor, &xfr_key, &xfr_data, + xfr_flags)) == 0) + { + xfr_flags = DB_NEXT_DUP; + + /* +1 to allow for space between zone and host names */ + dns_key.size = xfr_data.size + xfr_key.size + 1; + + /* +1 to allow for null term at end of string. */ + dns_key.data = tmp_zone_host = malloc(dns_key.size + 1); + if (dns_key.data == NULL) { + goto allnodes_cleanup; + } + + /* + * construct search key for dns_data. + * zone_name(a space)host_name + */ + strcpy(dns_key.data, zone); + strcat(dns_key.data, " "); + strncat(dns_key.data, xfr_data.data, xfr_data.size); + + dns_flags = DB_SET; + + while ((bdbhptres = dns_cursor->c_get(dns_cursor, &dns_key, + &dns_data, dns_flags)) == + 0) + { + dns_flags = DB_NEXT_DUP; + + /* +1 to allow for null term at end of string. */ + tmp = realloc(tmp, dns_data.size + 1); + if (tmp == NULL) { + goto allnodes_cleanup; + } + + /* copy data to tmp string, and append null term. */ + strncpy(tmp, dns_data.data, dns_data.size); + tmp[dns_data.size] = '\0'; + + /* split string into dns data parts. */ + if (bdbhpt_parse_data(db->log, tmp, &pd) != + ISC_R_SUCCESS) + { + goto allnodes_cleanup; + } + result = db->putnamedrr(allnodes, pd.host, pd.type, + pd.ttl, pd.data); + if (result != ISC_R_SUCCESS) { + goto allnodes_cleanup; + } + } /* end inner while loop */ + + /* clean up memory */ + if (tmp_zone_host != NULL) { + free(tmp_zone_host); + tmp_zone_host = NULL; + } + } /* end outer while loop */ + +allnodes_cleanup: + /* free any memory */ + if (tmp != NULL) { + free(tmp); + } + + if (tmp_zone_host != NULL) { + free(tmp_zone_host); + } + + if (tmp_zone != NULL) { + free(tmp_zone); + } + + /* get rid of cursors */ + if (xfr_cursor != NULL) { + xfr_cursor->c_close(xfr_cursor); + } + + if (dns_cursor != NULL) { + dns_cursor->c_close(dns_cursor); + } + + return (result); +} + +/*% + * Performs bdbhpt cleanup. + * Used by bdbhpt_create if there is an error starting up. + * Used by bdbhpt_destroy when the driver is shutting down. + */ +static void +bdbhpt_cleanup(bdbhpt_instance_t *db) { + /* close databases */ + if (db->data != NULL) { + db->data->close(db->data, 0); + } + if (db->xfr != NULL) { + db->xfr->close(db->xfr, 0); + } + if (db->zone != NULL) { + db->zone->close(db->zone, 0); + } + if (db->client != NULL) { + db->client->close(db->client, 0); + } + + /* close environment */ + if (db->dbenv != NULL) { + db->dbenv->close(db->dbenv, 0); + } +} + +/* + * See if we handle a given zone + */ +#if DLZ_DLOPEN_VERSION < 3 +isc_result_t +dlz_findzonedb(void *dbdata, const char *name) +#else /* if DLZ_DLOPEN_VERSION < 3 */ +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif /* if DLZ_DLOPEN_VERSION < 3 */ +{ + isc_result_t result; + bdbhpt_instance_t *db = (bdbhpt_instance_t *)dbdata; + DBT key, data; + + memset(&key, 0, sizeof(DBT)); + memset(&data, 0, sizeof(DBT)); + data.flags = DB_DBT_MALLOC; + +#if DLZ_DLOPEN_VERSION >= 3 + UNUSED(methods); + UNUSED(clientinfo); +#endif /* if DLZ_DLOPEN_VERSION >= 3 */ + + key.data = strdup(name); + + if (key.data == NULL) { + return (ISC_R_NOMEMORY); + } + + /* + * reverse string to take advantage of BDB locality of reference + * if we need further lookups because the zone doesn't match the + * first time. + */ + key.data = bdbhpt_strrev(key.data); + key.size = strlen(key.data); + + switch (db->zone->get(db->zone, NULL, &key, &data, 0)) { + case DB_NOTFOUND: + result = ISC_R_NOTFOUND; + break; + case 0: + result = ISC_R_SUCCESS; + break; + default: + result = ISC_R_FAILURE; + } + + /* free any memory duplicate string in the key field */ + if (key.data != NULL) { + free(key.data); + } + + /* free any memory allocated to the data field. */ + if (data.data != NULL) { + free(data.data); + } + + return (result); +} + +/* + * Look up one record in the database. + * + */ +#if DLZ_DLOPEN_VERSION == 1 +isc_result_t +dlz_lookup(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup) +#else /* if DLZ_DLOPEN_VERSION == 1 */ +isc_result_t +dlz_lookup(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif /* if DLZ_DLOPEN_VERSION == 1 */ +{ + isc_result_t result = ISC_R_NOTFOUND; + bdbhpt_instance_t *db = (bdbhpt_instance_t *)dbdata; + DBC *data_cursor = NULL; + DBT key, data; + int bdbhptres; + int flags; + + bdbhpt_parsed_data_t pd; + char *tmp = NULL; + char *keyStr = NULL; + +#if DLZ_DLOPEN_VERSION >= 2 + UNUSED(methods); + UNUSED(clientinfo); +#endif /* if DLZ_DLOPEN_VERSION >= 2 */ + + memset(&key, 0, sizeof(DBT)); + memset(&data, 0, sizeof(DBT)); + + key.size = strlen(zone) + strlen(name) + 1; + + /* allocate mem for key */ + key.data = keyStr = malloc((key.size + 1) * sizeof(char)); + + if (keyStr == NULL) { + return (ISC_R_NOMEMORY); + } + + strcpy(keyStr, zone); + strcat(keyStr, " "); + strcat(keyStr, name); + + /* get a cursor to loop through data */ + if (db->data->cursor(db->data, NULL, &data_cursor, 0) != 0) { + result = ISC_R_FAILURE; + goto lookup_cleanup; + } + + result = ISC_R_NOTFOUND; + + flags = DB_SET; + while ((bdbhptres = data_cursor->c_get(data_cursor, &key, &data, + flags)) == 0) + { + flags = DB_NEXT_DUP; + tmp = realloc(tmp, data.size + 1); + if (tmp == NULL) { + goto lookup_cleanup; + } + + strncpy(tmp, data.data, data.size); + tmp[data.size] = '\0'; + + if (bdbhpt_parse_data(db->log, tmp, &pd) != ISC_R_SUCCESS) { + goto lookup_cleanup; + } + + result = db->putrr(lookup, pd.type, pd.ttl, pd.data); + if (result != ISC_R_SUCCESS) { + goto lookup_cleanup; + } + } /* end while loop */ + +lookup_cleanup: + /* get rid of cursor */ + if (data_cursor != NULL) { + data_cursor->c_close(data_cursor); + } + + free(keyStr); + if (tmp != NULL) { + free(tmp); + } + + return (result); +} + +/*% + * Initialises, sets flags and then opens Berkeley databases. + */ +static isc_result_t +bdbhpt_opendb(log_t *log, DB_ENV *db_env, DBTYPE db_type, DB **db, + const char *db_name, char *db_file, int flags) { + int result; + + /* Initialise the database. */ + if ((result = db_create(db, db_env, 0)) != 0) { + log(ISC_LOG_ERROR, + "bdbhpt_dynamic: could not initialize %s database. " + "BerkeleyDB error: %s", + db_name, db_strerror(result)); + return (ISC_R_FAILURE); + } + + /* set database flags. */ + if ((result = (*db)->set_flags(*db, flags)) != 0) { + log(ISC_LOG_ERROR, + "bdbhpt_dynamic: could not set flags for %s database. " + "BerkeleyDB error: %s", + db_name, db_strerror(result)); + return (ISC_R_FAILURE); + } + + /* open the database. */ + if ((result = (*db)->open(*db, NULL, db_file, db_name, db_type, + DB_RDONLY | bdbhpt_threads, 0)) != 0) + { + log(ISC_LOG_ERROR, + "bdbhpt_dynamic: could not open %s database in %s. " + "BerkeleyDB error: %s", + db_name, db_file, db_strerror(result)); + return (ISC_R_FAILURE); + } + + return (ISC_R_SUCCESS); +} + +/* + * Called to initialize the driver + */ +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], void **dbdata, + ...) { + isc_result_t result; + int bdbhptres; + int bdbFlags = 0; + bdbhpt_instance_t *db = NULL; + + const char *helper_name; + va_list ap; + + UNUSED(dlzname); + + /* Allocate memory for our db structures and helper functions */ + db = calloc(1, sizeof(struct bdbhpt_instance)); + if (db == NULL) { + return (ISC_R_NOMEMORY); + } + + /* Fill in the helper functions */ + va_start(ap, dbdata); + while ((helper_name = va_arg(ap, const char *)) != NULL) { + b9_add_helper(db, helper_name, va_arg(ap, void *)); + } + va_end(ap); + + /* verify we have 4 arg's passed to the driver */ + if (argc != 4) { + db->log(ISC_LOG_ERROR, + "bdbhpt_dynamic: please supply 3 command line args. " + "You supplied: %s", + argc); + return (ISC_R_FAILURE); + } + + switch ((char)*argv[1]) { + /* + * Transactional mode. Highest safety - lowest speed. + */ + case 'T': + case 't': + bdbFlags = DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_LOG | + DB_INIT_TXN; + db->log(ISC_LOG_INFO, "bdbhpt_dynamic: using transactional " + "mode."); + break; + + /* + * Concurrent mode. Lower safety (no rollback) - + * higher speed. + */ + case 'C': + case 'c': + bdbFlags = DB_INIT_CDB | DB_INIT_MPOOL; + db->log(ISC_LOG_INFO, "bdbhpt_dynamic: using concurrent mode."); + break; + + /* + * Private mode. No inter-process communication & no locking. + * Lowest safety - highest speed. + */ + case 'P': + case 'p': + bdbFlags = DB_PRIVATE | DB_INIT_MPOOL; + db->log(ISC_LOG_INFO, "bdbhpt_dynamic: using private mode."); + break; + default: + db->log(ISC_LOG_ERROR, + "bdbhpt_dynamic: " + "operating mode must be set to P or C or T. " + "You specified '%s'", + argv[1]); + return (ISC_R_FAILURE); + } + + /* + * create bdbhpt environment + * Basically bdbhpt allocates and assigns memory to db->dbenv + */ + bdbhptres = db_env_create(&db->dbenv, 0); + if (bdbhptres != 0) { + db->log(ISC_LOG_ERROR, + "bdbhpt_dynamic: db environment could not be created. " + "BerkeleyDB error: %s", + db_strerror(bdbhptres)); + result = ISC_R_FAILURE; + goto init_cleanup; + } + + /* open bdbhpt environment */ + bdbhptres = db->dbenv->open(db->dbenv, argv[2], + bdbFlags | bdbhpt_threads | DB_CREATE, 0); + if (bdbhptres != 0) { + db->log(ISC_LOG_ERROR, + "bdbhpt_dynamic: " + "db environment at '%s' could not be opened. " + "BerkeleyDB error: %s", + argv[2], db_strerror(bdbhptres)); + result = ISC_R_FAILURE; + goto init_cleanup; + } + + /* open dlz_data database. */ + result = bdbhpt_opendb(db->log, db->dbenv, DB_UNKNOWN, &db->data, + dlz_data, argv[3], DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) { + goto init_cleanup; + } + + /* open dlz_xfr database. */ + result = bdbhpt_opendb(db->log, db->dbenv, DB_UNKNOWN, &db->xfr, + dlz_xfr, argv[3], DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) { + goto init_cleanup; + } + + /* open dlz_zone database. */ + result = bdbhpt_opendb(db->log, db->dbenv, DB_UNKNOWN, &db->zone, + dlz_zone, argv[3], 0); + if (result != ISC_R_SUCCESS) { + goto init_cleanup; + } + + /* open dlz_client database. */ + result = bdbhpt_opendb(db->log, db->dbenv, DB_UNKNOWN, &db->client, + dlz_client, argv[3], DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) { + goto init_cleanup; + } + + *dbdata = db; + + db->log(ISC_LOG_INFO, "bdbhpt_dynamic: version %s, started", + dlz_bdbhpt_dynamic_version); + return (ISC_R_SUCCESS); + +init_cleanup: + bdbhpt_cleanup(db); + return (result); +} + +/* + * Shut down the backend + */ +void +dlz_destroy(void *dbdata) { + struct bdbhpt_instance *db = (struct bdbhpt_instance *)dbdata; + + db->log(ISC_LOG_INFO, "dlz_bdbhpt_dynamic (%s): shutting down", + dlz_bdbhpt_dynamic_version); + bdbhpt_cleanup((bdbhpt_instance_t *)dbdata); + free(db); +} + +/* + * Return the version of the API + */ +int +dlz_version(unsigned int *flags) { + UNUSED(flags); + return (DLZ_DLOPEN_VERSION); +} + +/* + * Register a helper function from the bind9 dlz_dlopen driver + */ +static void +b9_add_helper(struct bdbhpt_instance *db, const char *helper_name, void *ptr) { + if (strcmp(helper_name, "log") == 0) { + db->log = (log_t *)ptr; + } + if (strcmp(helper_name, "putrr") == 0) { + db->putrr = (dns_sdlz_putrr_t *)ptr; + } + if (strcmp(helper_name, "putnamedrr") == 0) { + db->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr; + } + if (strcmp(helper_name, "writeable_zone") == 0) { + db->writeable_zone = (dns_dlz_writeablezone_t *)ptr; + } +} diff --git a/contrib/dlz/modules/bdbhpt/testing/README b/contrib/dlz/modules/bdbhpt/testing/README new file mode 100644 index 0000000..aec0935 --- /dev/null +++ b/contrib/dlz/modules/bdbhpt/testing/README @@ -0,0 +1,11 @@ +These files were used for testing on Ubuntu Linux using BDB 5.1 and +BerkeleyDB 0.54 for perl. + +- Populate the database from dns-data.txt for zone example.com: + + perl bdbhpt-populate.pl \ + --bdb=test.db --input=dns-data.txt --zones=example.com + +- Run "named -g -c named.conf" + +BDB server is now loaded with example.com data from the file test.db diff --git a/contrib/dlz/modules/bdbhpt/testing/bdbhpt-populate.pl b/contrib/dlz/modules/bdbhpt/testing/bdbhpt-populate.pl new file mode 100755 index 0000000..b2bceba --- /dev/null +++ b/contrib/dlz/modules/bdbhpt/testing/bdbhpt-populate.pl @@ -0,0 +1,244 @@ +#!/usr/bin/perl -w + +# 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. + +use strict; +use BerkeleyDB; +use Getopt::Long; + +my $opt = {}; +if (!GetOptions($opt, qw/bdb|b:s input|i:s zones|z:s help|h/)) { + usage('GetOptions processing failed.'); + exit 1; +} + +if ($opt->{help}) { + usage(); + exit 0; +} + +my $db_file = $opt->{bdb}; +if (!defined $db_file || $db_file eq '') { + usage('Please specify an output BerkeleyDB filename.'); + exit 1; +} + +my $input_file = $opt->{input}; +if (!defined $input_file || $input_file eq '') { + usage('Please specify an input records file.'); + exit 1; +} + +my $zone_list = $opt->{zones}; +if (!defined $zone_list || $zone_list eq '') { + usage('Please specify a space separated list of zones'); + exit 1; +} + +my $records = []; +my $unique_names = []; +populate_records(records=>$records, input_file=>$input_file, unique_names=>$unique_names); + +my $flags = DB_CREATE; + +my $dns_data = new BerkeleyDB::Hash + -Filename => $db_file, + -Flags => $flags, + -Property => DB_DUP | DB_DUPSORT, + -Subname => "dns_data" + || die "Cannot create dns_data: $BerkeleyDB::Error"; + +my $replId = 0; +my @zones = split(/\s+/, $zone_list); +foreach my $zone (@zones) { + foreach my $r (@$records) { + my $name = $r->{name}; + my $ttl = $r->{ttl}; + my $type = $r->{type}; + my $data = $r->{data}; + + $data =~ s/\%zone\%/$zone/g; + $data =~ s/\%driver\%/bdbhpt-dynamic/g; + + my $row_name = "$zone $name"; + my $row_value = "$replId $name $ttl $type $data"; + if ($dns_data->db_put($row_name, $row_value) != 0) { + die "Cannot add record '$row_name' -> '$row_value' to dns_data: $BerkeleyDB::Error"; + } + $replId++; + } +} + +$dns_data->db_close(); + +my $dns_xfr = new BerkeleyDB::Hash + -Filename => $db_file, + -Flags => $flags, + -Property => DB_DUP | DB_DUPSORT, + -Subname => "dns_xfr" + or die "Cannot create dns_xfr: $BerkeleyDB::Error"; + +foreach my $zone (@zones) { + foreach my $name (@$unique_names) { + if ($dns_xfr->db_put($zone, $name) != 0) { + die "Cannot add record '$zone' -> '$name' to dns_xfr: $BerkeleyDB::Error"; + } + } +} + +$dns_xfr->db_close(); + +my $dns_client = new BerkeleyDB::Hash + -Filename => $db_file, + -Flags => $flags, + -Property => DB_DUP | DB_DUPSORT, + -Subname => "dns_client" + or die "Cannot create dns_client: $BerkeleyDB::Error"; + +foreach my $zone (@zones) { + my $ip = '127.0.0.1'; + if ($dns_client->db_put($zone, $ip) != 0) { + die "Cannot add record '$zone' -> '$ip' to dns_client: $BerkeleyDB::Error"; + } +} + +$dns_client->db_close(); + +my $dns_zone = new BerkeleyDB::Btree + -Filename => $db_file, + -Flags => $flags, + -Property => 0, + -Subname => "dns_zone" + or die "Cannot create dns_zone: $BerkeleyDB::Error"; + +foreach my $zone (@zones) { + my $reversed_zone = reverse($zone); + if ($dns_zone->db_put($reversed_zone, "1") != 0) { + die "Cannot add record '$reversed_zone' -> '1' to dns_zone: $BerkeleyDB::Error"; + } +}; + +$dns_zone->db_close(); + +exit 0; + +sub usage { + my ($message) = @_; + if (defined $message && $message ne '') { + print STDERR $message . "\n\n"; + } + + print STDERR "usage: $0 --bdb=<bdb-file> --input=<input-file> --zones=<zone-list>\n\n"; + print STDERR "\tbdb-file: The output BerkeleyDB file you wish to create and use with bdbhpt-dynamic\n\n"; + print STDERR "\tinput-file: The input text-file containing records to populate within your zones\n\n"; + print STDERR "\tzone-list: The space-separated list of zones you wish to create\n\n"; +} + +sub populate_records { + my (%args) = @_; + my $records = $args{records}; + my $input_file = $args{input_file}; + my $unique_names = $args{unique_names}; + + my %unique; + + open(RECORDS, $input_file) || die "unable to open $input_file: $!"; + while (<RECORDS>) { + chomp; + s/\#.*$//; + s/^\s+//; + if ($_ eq '') { + next; + } + my ($name, $ttl, $type, $data) = split(/\s+/, $_, 4); + my $record = { name=>$name, ttl=>$ttl, type=>$type, data=>$data }; + if (validate_record($record)) { + push @$records, $record; + $unique{$name} = 1; + } + } + close(RECORDS); + + foreach my $name (sort keys %unique) { + push @$unique_names, $name; + } +} + +# This could probably do more in-depth tests, but these tests are better than nothing! +sub validate_record { + my ($r) = @_; + + # http://en.wikipedia.org/wiki/List_of_DNS_record_types + my @TYPES = qw/A AAAA AFSDB APL CERT CNAME DHCID DLV DNAME DNSKEY DS HIP IPSECKEY KEY KX LOC MX NAPTR NS NSEC NSEC3 NSEC3PARAM PTR RRSIG RP SIG SOA SPF SRV SSHFP TA TKEY TLSA TSIG TXT/; + my $VALID_TYPE = {}; + foreach my $t (@TYPES) { + $VALID_TYPE->{$t} = 1; + } + + if (!defined $r->{name} || $r->{name} eq '') { + die "Record name must be set"; + } + + if (!defined $r->{ttl} || $r->{ttl} eq '') { + die "Record TTL must be set"; + } + + if ($r->{ttl} =~ /\D/ || $r->{ttl} < 0) { + die "Record TTL must be an integer 0 or greater"; + } + + if (!defined $r->{type} || $r->{type} eq '') { + die "Record type must be set"; + } + + if (!$VALID_TYPE->{$r->{type}}) { + die "Unsupported record type: $r->{type}"; + } + + # Lets do some data validation for the records which will cause bind to crash if they're wrong + if ($r->{type} eq 'SOA') { + my $soa_error = "SOA records must take the form: 'server email refresh retry expire negative_cache_ttl'"; + my ($server, $email, $version, $refresh, $retry, $expire, $negative_cache_ttl) = split(/\s+/, $r->{data}); + if (!defined $server || $server eq '') { + die "$soa_error, missing server"; + } + if (!defined $email || $email eq '') { + die "$soa_error, missing email"; + } + if (!defined $refresh || $refresh eq '') { + die "$soa_error, missing refresh"; + } + if ($refresh =~ /\D/ || $refresh <= 0) { + die "$soa_error, refresh must be an integer greater than 0"; + } + if (!defined $retry || $retry eq '') { + die "$soa_error, missing retry"; + } + if ($retry =~ /\D/ || $retry <= 0) { + die "$soa_error, retry must be an integer greater than 0"; + } + if (!defined $expire || $expire eq '') { + die "$soa_error, missing expire"; + } + if ($expire =~ /\D/ || $expire <= 0) { + die "$soa_error, expire must be an integer greater than 0"; + } + if (!defined $negative_cache_ttl || $negative_cache_ttl eq '') { + die "$soa_error, missing negative cache ttl"; + } + if ($negative_cache_ttl =~ /\D/ || $negative_cache_ttl <= 0) { + die "$soa_error, negative cache ttl must be an integer greater than 0"; + } + } + + return 1; +} diff --git a/contrib/dlz/modules/bdbhpt/testing/dns-data.txt b/contrib/dlz/modules/bdbhpt/testing/dns-data.txt new file mode 100644 index 0000000..242cd3d --- /dev/null +++ b/contrib/dlz/modules/bdbhpt/testing/dns-data.txt @@ -0,0 +1,19 @@ +# Name TTL Type Data +@ 3600 SOA ns1.%zone%. root.%zone%. 2012071700 604800 86400 2419200 10800 +@ 3600 NS ns1.%zone%. +@ 3600 MX 5 mx1.%zone%. +@ 3600 MX 10 mx2.%zone%. +@ 3600 TXT This zone brought to you by %driver%! +jabber 3600 A 127.0.0.1 +mx1 3600 A 127.0.0.2 +mx2 3600 A 127.0.0.3 +jabber 3600 A 127.0.0.4 +ns1 3600 A 127.0.0.5 +ns1 3600 AAAA ::1 +voip 3600 A 127.0.0.6 +www 3600 CNAME www1.%zone% +www1 3600 A 127.0.0.7 +_sip._udp 3600 SRV 5 0 5060 voip.%zone%. +_jabber._tcp 3600 SRV 5 0 5269 jabber.%zone%. +_xmpp-client._tcp 3600 SRV 5 0 5222 jabber.%zone%. +_xmpp-server._tcp 3600 SRV 5 0 5269 jabber.%zone%. diff --git a/contrib/dlz/modules/bdbhpt/testing/named.conf b/contrib/dlz/modules/bdbhpt/testing/named.conf new file mode 100644 index 0000000..c8b5722 --- /dev/null +++ b/contrib/dlz/modules/bdbhpt/testing/named.conf @@ -0,0 +1,37 @@ +/* + * 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. + */ + +controls { }; + +options { + directory "."; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { any; }; + listen-on-v6 { none; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-md5; +}; + +controls { + inet 127.0.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +dlz "bdbhpt_dynamic" { + database "dlopen ../dlz_bdbhpt_dynamic.so T . test.db"; +}; diff --git a/contrib/dlz/modules/common/dlz_dbi.c b/contrib/dlz/modules/common/dlz_dbi.c new file mode 100644 index 0000000..88ff632 --- /dev/null +++ b/contrib/dlz/modules/common/dlz_dbi.c @@ -0,0 +1,484 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 and 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 https://mozilla.org/MPL/2.0/. + */ + +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include <ctype.h> +#include <errno.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <dlz_dbi.h> +#include <dlz_list.h> +#include <dlz_minimal.h> +#include <dlz_pthread.h> + +/*% + * properly destroys a querylist by de-allocating the + * memory for each query segment, and then the list itself + */ + +void +destroy_querylist(query_list_t **querylist) { + query_segment_t *tseg = NULL; + query_segment_t *nseg = NULL; + + /* if query list is null, nothing to do */ + if (*querylist == NULL) { + return; + } + + /* start at the top of the list */ + nseg = DLZ_LIST_HEAD(**querylist); + while (nseg != NULL) { /* loop, until end of list */ + tseg = nseg; + /* + * free the query segment's text string but only if it + * was really a query segment, and not a pointer to + * %zone%, or %record%, or %client% + */ + if (tseg->cmd != NULL && tseg->direct) { + free(tseg->cmd); + } + /* get the next query segment, before we destroy this one. */ + nseg = DLZ_LIST_NEXT(nseg, link); + /* deallocate this query segment. */ + free(tseg); + } + /* deallocate the query segment list */ + free(*querylist); +} + +/*% constructs a query list by parsing a string into query segments */ +isc_result_t +build_querylist(const char *query_str, char **zone, char **record, + char **client, query_list_t **querylist, unsigned int flags, + log_t log) { + isc_result_t result; + bool foundzone = false; + bool foundrecord = false; + bool foundclient = false; + char *temp_str = NULL; + char *right_str = NULL; + char *token = NULL; + query_list_t *tql; + query_segment_t *tseg = NULL; + + /* if query string is null, or zero length */ + if (query_str == NULL || strlen(query_str) < 1) { + if ((flags & REQUIRE_QUERY) == 0) { + /* we don't need it were ok. */ + return (ISC_R_SUCCESS); + } else { + /* we did need it, PROBLEM!!! */ + return (ISC_R_FAILURE); + } + } + + /* allocate memory for query list */ + tql = calloc(1, sizeof(query_list_t)); + /* couldn't allocate memory. Problem!! */ + if (tql == NULL) { + return (ISC_R_NOMEMORY); + } + + /* initialize the query segment list */ + DLZ_LIST_INIT(*tql); + + /* make a copy of query_str so we can chop it up */ + temp_str = right_str = strdup(query_str); + /* couldn't make a copy, problem!! */ + if (right_str == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* loop through the string and chop it up */ + for (token = strtok_r(right_str, "$", &temp_str); token; + token = strtok_r(NULL, "$", &temp_str)) + { + /* allocate memory for tseg */ + tseg = calloc(1, sizeof(query_segment_t)); + if (tseg == NULL) { /* no memory, clean everything up. */ + result = ISC_R_NOMEMORY; + goto cleanup; + } + tseg->direct = false; + /* initialize the query segment link */ + DLZ_LINK_INIT(tseg, link); + /* append the query segment to the list */ + DLZ_LIST_APPEND(*tql, tseg, link); + + /* + * split string at the first "$". set query segment to + * left portion + */ + tseg->cmd = strdup(token); + if (tseg->cmd == NULL) { + /* no memory, clean everything up. */ + result = ISC_R_NOMEMORY; + goto cleanup; + } + /* tseg->cmd points directly to a string. */ + tseg->direct = true; + tseg->strlen = strlen(tseg->cmd); + + /* check if we encountered "$zone$" token */ + if (strcasecmp(tseg->cmd, "zone") == 0) { + /* + * we don't really need, or want the "zone" + * text, so get rid of it. + */ + free(tseg->cmd); + /* set tseg->cmd to in-direct zone string */ + tseg->cmd = (char **)zone; + tseg->strlen = 0; + /* tseg->cmd points in-directly to a string */ + tseg->direct = false; + foundzone = true; + /* check if we encountered "$record$" token */ + } else if (strcasecmp(tseg->cmd, "record") == 0) { + /* + * we don't really need, or want the "record" + * text, so get rid of it. + */ + free(tseg->cmd); + /* set tseg->cmd to in-direct record string */ + tseg->cmd = (char **)record; + tseg->strlen = 0; + /* tseg->cmd points in-directly poinsts to a string */ + tseg->direct = false; + foundrecord = true; + /* check if we encountered "$client$" token */ + } else if (strcasecmp(tseg->cmd, "client") == 0) { + /* + * we don't really need, or want the "client" + * text, so get rid of it. + */ + free(tseg->cmd); + /* set tseg->cmd to in-direct record string */ + tseg->cmd = (char **)client; + tseg->strlen = 0; + /* tseg->cmd points in-directly poinsts to a string */ + tseg->direct = false; + foundclient = true; + } + } + + /* we don't need temp_str any more */ + free(right_str); + right_str = NULL; + /* + * add checks later to verify zone and record are found if + * necessary. + */ + + /* if this query requires %client%, make sure we found it */ + if (((flags & REQUIRE_CLIENT) != 0) && (!foundclient)) { + /* Write error message to log */ + if (log != NULL) { + log(ISC_LOG_ERROR, "Required token $client$ not " + "found."); + } + result = ISC_R_FAILURE; + goto flag_fail; + } + + /* if this query requires %record%, make sure we found it */ + if (((flags & REQUIRE_RECORD) != 0) && (!foundrecord)) { + /* Write error message to log */ + if (log != NULL) { + log(ISC_LOG_ERROR, "Required token $record$ not " + "found."); + } + result = ISC_R_FAILURE; + goto flag_fail; + } + + /* if this query requires %zone%, make sure we found it */ + if (((flags & REQUIRE_ZONE) != 0) && (!foundzone)) { + /* Write error message to log */ + if (log != NULL) { + log(ISC_LOG_ERROR, "Required token $zone$ not found."); + } + result = ISC_R_FAILURE; + goto flag_fail; + } + + /* pass back the query list */ + *querylist = (query_list_t *)tql; + + /* return success */ + return (ISC_R_SUCCESS); + +cleanup: + /* get rid of temp_str */ + if (right_str != NULL) { + free(right_str); + } + +flag_fail: + /* get rid of what was build of the query list */ + destroy_querylist(&tql); + return (result); +} + +/*% + * build a query string from query segments, and dynamic segments + * dynamic segments replace where the tokens %zone%, %record%, %client% + * used to be in our queries from named.conf + */ +char * +build_querystring(query_list_t *querylist) { + query_segment_t *tseg = NULL; + unsigned int length = 0; + char *qs = NULL; + + /* start at the top of the list */ + tseg = DLZ_LIST_HEAD(*querylist); + while (tseg != NULL) { + /* + * if this is a query segment, use the + * precalculated string length + */ + if (tseg->direct) { + length += tseg->strlen; + } else { /* calculate string length for dynamic segments. */ + length += strlen(*(char **)tseg->cmd); + } + /* get the next segment */ + tseg = DLZ_LIST_NEXT(tseg, link); + } + + qs = malloc(length + 1); + if (qs == NULL) { + return (NULL); + } + + *qs = '\0'; + /* start at the top of the list again */ + tseg = DLZ_LIST_HEAD(*querylist); + while (tseg != NULL) { + if (tseg->direct) { + /* query segments */ + strcat(qs, tseg->cmd); + } else { + /* dynamic segments */ + strcat(qs, *(char **)tseg->cmd); + } + /* get the next segment */ + tseg = DLZ_LIST_NEXT(tseg, link); + } + + return (qs); +} + +/*% constructs a dbinstance (DBI) */ +isc_result_t +build_dbinstance(const char *allnodes_str, const char *allowxfr_str, + const char *authority_str, const char *findzone_str, + const char *lookup_str, const char *countzone_str, + dbinstance_t **dbi, log_t log) { + isc_result_t result; + dbinstance_t *db = NULL; + int err; + + /* allocate and zero memory for driver structure */ + db = calloc(1, sizeof(dbinstance_t)); + if (db == NULL) { + if (log != NULL) { + log(ISC_LOG_ERROR, "Could not allocate memory for " + "database instance object."); + } + return (ISC_R_NOMEMORY); + } + memset(db, 0, sizeof(dbinstance_t)); + db->dbconn = NULL; + db->client = NULL; + db->record = NULL; + db->zone = NULL; + db->query_buf = NULL; + db->allnodes_q = NULL; + db->allowxfr_q = NULL; + db->authority_q = NULL; + db->findzone_q = NULL; + db->countzone_q = NULL; + db->lookup_q = NULL; + + /* initialize the reference count mutex */ + err = dlz_mutex_init(&db->lock, NULL); + if (err == ENOMEM) { + result = ISC_R_NOMEMORY; + goto cleanup; + } else if (err != 0) { + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + /* build the all nodes query list */ + result = build_querylist(allnodes_str, &db->zone, &db->record, + &db->client, &db->allnodes_q, REQUIRE_ZONE, + log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) { + log(ISC_LOG_ERROR, "Could not build all nodes query " + "list"); + } + goto cleanup; + } + + /* build the allow zone transfer query list */ + result = build_querylist(allowxfr_str, &db->zone, &db->record, + &db->client, &db->allowxfr_q, + REQUIRE_ZONE | REQUIRE_CLIENT, log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) { + log(ISC_LOG_ERROR, "Could not build allow xfr query " + "list"); + } + goto cleanup; + } + + /* build the authority query, query list */ + result = build_querylist(authority_str, &db->zone, &db->record, + &db->client, &db->authority_q, REQUIRE_ZONE, + log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) { + log(ISC_LOG_ERROR, "Could not build authority query " + "list"); + } + goto cleanup; + } + + /* build findzone query, query list */ + result = build_querylist(findzone_str, &db->zone, &db->record, + &db->client, &db->findzone_q, REQUIRE_ZONE, + log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) { + log(ISC_LOG_ERROR, "Could not build find zone query " + "list"); + } + goto cleanup; + } + + /* build countzone query, query list */ + result = build_querylist(countzone_str, &db->zone, &db->record, + &db->client, &db->countzone_q, REQUIRE_ZONE, + log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) { + log(ISC_LOG_ERROR, "Could not build count zone query " + "list"); + } + goto cleanup; + } + + /* build lookup query, query list */ + result = build_querylist(lookup_str, &db->zone, &db->record, + &db->client, &db->lookup_q, REQUIRE_RECORD, + log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) { + log(ISC_LOG_ERROR, "Could not build lookup query list"); + } + goto cleanup; + } + + /* pass back the db instance */ + *dbi = (dbinstance_t *)db; + + /* return success */ + return (ISC_R_SUCCESS); + +cleanup: + /* destroy whatever was build of the db instance */ + destroy_dbinstance(db); + /* return failure */ + return (ISC_R_FAILURE); +} + +void +destroy_dbinstance(dbinstance_t *dbi) { + /* destroy any query lists we created */ + destroy_querylist(&dbi->allnodes_q); + destroy_querylist(&dbi->allowxfr_q); + destroy_querylist(&dbi->authority_q); + destroy_querylist(&dbi->findzone_q); + destroy_querylist(&dbi->countzone_q); + destroy_querylist(&dbi->lookup_q); + + /* get rid of the mutex */ + (void)dlz_mutex_destroy(&dbi->lock); + + /* return, and detach the memory */ + free(dbi); +} + +char * +get_parameter_value(const char *input, const char *key) { + int keylen; + char *keystart; + char value[255]; + int i; + + if (key == NULL || input == NULL || *input == '\0') { + return (NULL); + } + + keylen = strlen(key); + + if (keylen < 1) { + return (NULL); + } + + keystart = strstr(input, key); + + if (keystart == NULL) { + return (NULL); + } + + for (i = 0; i < 255; i++) { + value[i] = keystart[keylen + i]; + if (isspace(value[i]) || value[i] == '\0') { + value[i] = '\0'; + break; + } + } + + return (strdup(value)); +} diff --git a/contrib/dlz/modules/filesystem/Makefile b/contrib/dlz/modules/filesystem/Makefile new file mode 100644 index 0000000..40ecb79 --- /dev/null +++ b/contrib/dlz/modules/filesystem/Makefile @@ -0,0 +1,45 @@ +# Copyright 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 https://mozilla.org/MPL/2.0/. + +# Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. +# +# The development of Dynamically Loadable Zones (DLZ) for Bind 9 was +# conceived and contributed by Rob Butler. +# +# SPDX-License-Identifier: ISC and MPL-2.0 +# +# Permission to use, copy, modify, and distribute this software for any purpose +# with or without fee is hereby granted, provided that the above copyright +# notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +prefix = /usr +libdir = $(prefix)/lib/bind9 + +CFLAGS=-fPIC -g -I../include + +all: dlz_filesystem_dynamic.so + +dir.o: dir.c + $(CC) $(CFLAGS) -c dir.c + +dlz_filesystem_dynamic.so: dlz_filesystem_dynamic.c dir.o + $(CC) $(CFLAGS) -shared -o dlz_filesystem_dynamic.so \ + dlz_filesystem_dynamic.c dir.o + +clean: + rm -f dlz_filesystem_dynamic.so *.o + +install: dlz_filesystem_dynamic.so + mkdir -p $(DESTDIR)$(libdir) + install dlz_filesystem_dynamic.so $(DESTDIR)$(libdir) diff --git a/contrib/dlz/modules/filesystem/dir.c b/contrib/dlz/modules/filesystem/dir.c new file mode 100644 index 0000000..f2b3a4e --- /dev/null +++ b/contrib/dlz/modules/filesystem/dir.c @@ -0,0 +1,118 @@ +/* + * 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/. + */ + +#include "dir.h" +#include <ctype.h> +#include <errno.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "dlz_minimal.h" + +void +dir_init(dir_t *dir) { + dir->entry.name[0] = '\0'; + dir->entry.length = 0; + + dir->handle = NULL; +} + +isc_result_t +dir_open(dir_t *dir, const char *dirname) { + char *p; + isc_result_t result = ISC_R_SUCCESS; + + if (strlen(dirname) + 3 > sizeof(dir->dirname)) { + return (ISC_R_NOSPACE); + } + strcpy(dir->dirname, dirname); + + p = dir->dirname + strlen(dir->dirname); + if (dir->dirname < p && *(p - 1) != '/') { + *p++ = '/'; + } + *p++ = '*'; + *p = '\0'; + + dir->handle = opendir(dirname); + if (dir->handle == NULL) { + switch (errno) { + case ENOTDIR: + case ELOOP: + case EINVAL: + case ENAMETOOLONG: + case EBADF: + result = ISC_R_INVALIDFILE; + break; + case ENOENT: + result = ISC_R_FILENOTFOUND; + break; + case EACCES: + case EPERM: + result = ISC_R_NOPERM; + break; + case ENOMEM: + result = ISC_R_NOMEMORY; + break; + default: + result = ISC_R_UNEXPECTED; + break; + } + } + + return (result); +} + +/*! + * \brief Return previously retrieved file or get next one. + * + * Unix's dirent has + * separate open and read functions, but the Win32 and DOS interfaces open + * the dir stream and reads the first file in one operation. + */ +isc_result_t +dir_read(dir_t *dir) { + struct dirent *entry; + + entry = readdir(dir->handle); + if (entry == NULL) { + return (ISC_R_NOMORE); + } + + if (sizeof(dir->entry.name) <= strlen(entry->d_name)) { + return (ISC_R_UNEXPECTED); + } + + strcpy(dir->entry.name, entry->d_name); + + dir->entry.length = strlen(entry->d_name); + return (ISC_R_SUCCESS); +} + +/*! + * \brief Close directory stream. + */ +void +dir_close(dir_t *dir) { + (void)closedir(dir->handle); + dir->handle = NULL; +} + +/*! + * \brief Reposition directory stream at start. + */ +isc_result_t +dir_reset(dir_t *dir) { + rewinddir(dir->handle); + + return (ISC_R_SUCCESS); +} diff --git a/contrib/dlz/modules/filesystem/dir.h b/contrib/dlz/modules/filesystem/dir.h new file mode 100644 index 0000000..bf96e39 --- /dev/null +++ b/contrib/dlz/modules/filesystem/dir.h @@ -0,0 +1,43 @@ +/* + * 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/. + */ + +#include <dirent.h> +#include <sys/types.h> + +#include <dlz_minimal.h> + +#define DIR_NAMEMAX 256 +#define DIR_PATHMAX 1024 + +typedef struct direntry { + char name[DIR_NAMEMAX]; + unsigned int length; +} direntry_t; + +typedef struct dir { + char dirname[DIR_PATHMAX]; + direntry_t entry; + DIR *handle; +} dir_t; + +void +dir_init(dir_t *dir); + +isc_result_t +dir_open(dir_t *dir, const char *dirname); + +isc_result_t +dir_read(dir_t *dir); + +isc_result_t +dir_reset(dir_t *dir); + +void +dir_close(dir_t *dir); diff --git a/contrib/dlz/modules/filesystem/dlz_filesystem_dynamic.c b/contrib/dlz/modules/filesystem/dlz_filesystem_dynamic.c new file mode 100644 index 0000000..3b8a8bb --- /dev/null +++ b/contrib/dlz/modules/filesystem/dlz_filesystem_dynamic.c @@ -0,0 +1,1008 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 and 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 https://mozilla.org/MPL/2.0/. + */ + +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This provides the externally loadable filesystem DLZ module, without + * update support + */ + +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "dir.h" +#include "dlz_list.h" +#include "dlz_minimal.h" + +typedef struct config_data { + char *basedir; + int basedirsize; + char *datadir; + int datadirsize; + char *xfrdir; + int xfrdirsize; + int splitcnt; + char separator; + char pathsep; + + /* Helper functions from the dlz_dlopen driver */ + log_t *log; + dns_sdlz_putrr_t *putrr; + dns_sdlz_putnamedrr_t *putnamedrr; + dns_dlz_writeablezone_t *writeable_zone; +} config_data_t; + +typedef struct dir_entry dir_entry_t; + +struct dir_entry { + char dirpath[DIR_PATHMAX]; + DLZ_LINK(dir_entry_t) link; +}; + +typedef DLZ_LIST(dir_entry_t) dlist_t; + +/* forward reference */ + +static void +b9_add_helper(struct config_data *cd, const char *helper_name, void *ptr); + +/* + * Private methods + */ +static bool +is_safe(const char *input) { + unsigned int i; + unsigned int len = strlen(input); + + /* check that only allowed characters are in the domain name */ + for (i = 0; i < len; i++) { + /* '.' is allowed, but has special requirements */ + if (input[i] == '.') { + /* '.' is not allowed as first char */ + if (i == 0) { + return (false); + } + /* '..', two dots together is not allowed. */ + if (input[i - 1] == '.') { + return (false); + } + /* '.' is not allowed as last char */ + if (i == len - 1) { + return (false); + } + /* only 1 dot in ok location, continue at next char */ + continue; + } + /* '-' is allowed, continue at next char */ + if (input[i] == '-') { + continue; + } + /* 0-9 is allowed, continue at next char */ + if (input[i] >= '0' && input[i] <= '9') { + continue; + } + /* A-Z uppercase is allowed, continue at next char */ + if (input[i] >= 'A' && input[i] <= 'Z') { + continue; + } + /* a-z lowercase is allowed, continue at next char */ + if (input[i] >= 'a' && input[i] <= 'z') { + continue; + } + + /* + * colon needs to be allowed for IPV6 client + * addresses. Not dangerous in domain names, as not a + * special char. + */ + if (input[i] == ':') { + continue; + } + + /* + * '@' needs to be allowed for in zone data. Not + * dangerous in domain names, as not a special char. + */ + if (input[i] == '@') { + continue; + } + + /* + * if we reach this point we have encountered a + * disallowed char! + */ + return (false); + } + /* everything ok. */ + return (true); +} + +static isc_result_t +create_path_helper(char *out, const char *in, config_data_t *cd) { + char *tmpString; + char *tmpPtr; + int i; + + tmpString = strdup(in); + if (tmpString == NULL) { + return (ISC_R_NOMEMORY); + } + + /* + * don't forget is_safe guarantees '.' will NOT be the + * first/last char + */ + while ((tmpPtr = strrchr(tmpString, '.')) != NULL) { + i = 0; + while (tmpPtr[i + 1] != '\0') { + if (cd->splitcnt < 1) { + strcat(out, (char *)&tmpPtr[i + 1]); + } else { + strncat(out, (char *)&tmpPtr[i + 1], + cd->splitcnt); + } + strncat(out, (char *)&cd->pathsep, 1); + if (cd->splitcnt == 0) { + break; + } + if (strlen((char *)&tmpPtr[i + 1]) <= + (unsigned int)cd->splitcnt) + { + break; + } + i += cd->splitcnt; + } + tmpPtr[0] = '\0'; + } + + /* handle the "first" label properly */ + i = 0; + tmpPtr = tmpString; + while (tmpPtr[i] != '\0') { + if (cd->splitcnt < 1) { + strcat(out, (char *)&tmpPtr[i]); + } else { + strncat(out, (char *)&tmpPtr[i], cd->splitcnt); + } + strncat(out, (char *)&cd->pathsep, 1); + if (cd->splitcnt == 0) { + break; + } + if (strlen((char *)&tmpPtr[i]) <= (unsigned int)cd->splitcnt) { + break; + } + i += cd->splitcnt; + } + + free(tmpString); + return (ISC_R_SUCCESS); +} + +/*% + * Checks to make sure zone and host are safe. If safe, then + * hashes zone and host strings to build a path. If zone / host + * are not safe an error is returned. + */ + +static isc_result_t +create_path(const char *zone, const char *host, const char *client, + config_data_t *cd, char **path) { + char *tmpPath; + int pathsize; + int len; + isc_result_t result; + bool isroot = false; + + /* special case for root zone */ + if (strcmp(zone, ".") == 0) { + isroot = true; + } + + /* if the requested zone is "unsafe", return error */ + if (!isroot && !is_safe(zone)) { + return (ISC_R_FAILURE); + } + + /* if host was passed, verify that it is safe */ + if (host != NULL && !is_safe(host)) { + return (ISC_R_FAILURE); + } + + /* if client was passed, verify that it is safe */ + if (client != NULL && !is_safe(client)) { + return (ISC_R_FAILURE); + } + + /* Determine how much memory the split up string will require */ + if (host != NULL) { + len = strlen(zone) + strlen(host); + } else if (client != NULL) { + len = strlen(zone) + strlen(client); + } else { + len = strlen(zone); + } + + /* + * even though datadir and xfrdir will never be in the same + * string we only waste a few bytes by allocating for both, + * and then we are safe from buffer overruns. + */ + pathsize = len + cd->basedirsize + cd->datadirsize + cd->xfrdirsize + 4; + + /* if we are splitting names, we will need extra space. */ + if (cd->splitcnt > 0) { + pathsize += len / cd->splitcnt; + } + + tmpPath = malloc(pathsize * sizeof(char)); + if (tmpPath == NULL) { + /* write error message */ + cd->log(ISC_LOG_ERROR, "Filesystem driver unable to " + "allocate memory in create_path()."); + result = ISC_R_NOMEMORY; + goto cleanup_mem; + } + + /* + * build path string. + * start out with base directory. + */ + strcpy(tmpPath, cd->basedir); + + /* add zone name - parsed properly */ + if (!isroot) { + result = create_path_helper(tmpPath, zone, cd); + if (result != ISC_R_SUCCESS) { + goto cleanup_mem; + } + } + + /* + * When neither client or host is passed we are building a + * path to see if a zone is supported. We require that a zone + * path have the "data dir" directory contained within it so + * that we know this zone is really supported. Otherwise, + * this zone may not really be supported because we are + * supporting a delagated sub zone. + * + * Example: + * + * We are supporting long.domain.com and using a splitcnt of + * 0. the base dir is "/base-dir/" and the data dir is + * "/.datadir" We want to see if we are authoritative for + * domain.com. Path /base-dir/com/domain/.datadir since + * /base-dir/com/domain/.datadir does not exist, we are not + * authoritative for the domain "domain.com". However we are + * authoritative for the domain "long.domain.com" because the + * path /base-dir/com/domain/long/.datadir does exist! + */ + + /* if client is passed append xfr dir, otherwise append data dir */ + if (client != NULL) { + strcat(tmpPath, cd->xfrdir); + strncat(tmpPath, (char *)&cd->pathsep, 1); + strcat(tmpPath, client); + } else { + strcat(tmpPath, cd->datadir); + } + + /* if host not null, add it. */ + if (host != NULL) { + strncat(tmpPath, (char *)&cd->pathsep, 1); + result = create_path_helper(tmpPath, host, cd); + if (result != ISC_R_SUCCESS) { + goto cleanup_mem; + } + } + + /* return the path we built. */ + *path = tmpPath; + + /* return success */ + result = ISC_R_SUCCESS; + +cleanup_mem: + /* cleanup memory */ + + /* free tmpPath memory */ + if (tmpPath != NULL && result != ISC_R_SUCCESS) { + free(tmpPath); + } + + return (result); +} + +static isc_result_t +process_dir(dir_t *dir, void *passback, config_data_t *cd, dlist_t *dir_list, + unsigned int basedirlen) { + char tmp[DIR_PATHMAX + DIR_NAMEMAX]; + int astPos; + struct stat sb; + isc_result_t result = ISC_R_FAILURE; + char *endp; + char *type; + char *ttlStr; + char *data; + char host[DIR_NAMEMAX]; + char *tmpString; + char *tmpPtr; + int ttl; + int i; + int len; + dir_entry_t *direntry; + bool foundHost; + + tmp[0] = '\0'; /* set 1st byte to '\0' so strcpy works right. */ + host[0] = '\0'; + foundHost = false; + + /* copy base directory name to tmp. */ + strcpy(tmp, dir->dirname); + + /* dir->dirname will always have '*' as the last char. */ + astPos = strlen(dir->dirname) - 1; + + /* if dir_list != NULL, were are performing a zone xfr */ + if (dir_list != NULL) { + /* if splitcnt == 0, determine host from path. */ + if (cd->splitcnt == 0) { + if (strlen(tmp) - 3 > basedirlen) { + tmp[astPos - 1] = '\0'; + tmpString = (char *)&tmp[basedirlen + 1]; + /* handle filesystem's special wildcard "-" */ + if (strcmp(tmpString, "-") == 0) { + strcpy(host, "*"); + } else { + /* + * not special wildcard -- normal name + */ + while ((tmpPtr = strrchr( + tmpString, + cd->pathsep)) != NULL) + { + if ((strlen(host) + + strlen(tmpPtr + 1) + 2) > + DIR_NAMEMAX) + { + continue; + } + strcat(host, tmpPtr + 1); + strcat(host, "."); + tmpPtr[0] = '\0'; + } + if ((strlen(host) + strlen(tmpString) + + 1) <= DIR_NAMEMAX) + { + strcat(host, tmpString); + } + } + + foundHost = true; + /* set tmp again for use later */ + strcpy(tmp, dir->dirname); + } + } else { + /* + * if splitcnt != 0 determine host from + * ".host" directory entry + */ + while (dir_read(dir) == ISC_R_SUCCESS) { + if (strncasecmp(".host", dir->entry.name, 5) == + 0) + { + /* + * handle filesystem's special + * wildcard "-" + */ + if (strcmp((char *)&dir->entry.name[6], + "-") == 0) + { + strcpy(host, "*"); + } else { + strncpy(host, + (char *)&dir->entry + .name[6], + sizeof(host) - 1); + host[255] = '\0'; + } + foundHost = true; + break; + } + } + /* reset dir list for use later */ + dir_reset(dir); + } /* end of else */ + } + + while (dir_read(dir) == ISC_R_SUCCESS) { + cd->log(ISC_LOG_DEBUG(1), + "Filesystem driver Dir name:" + " '%s' Dir entry: '%s'\n", + dir->dirname, dir->entry.name); + + /* skip any entries starting with "." */ + if (dir->entry.name[0] == '.') { + continue; + } + + /* + * get rid of '*', set to NULL. Effectively trims + * string from previous loop to base directory only + * while still leaving memory for concat to be + * performed next. + */ + + tmp[astPos] = '\0'; + + /* add name to base directory name. */ + strcat(tmp, dir->entry.name); + + /* make sure we can stat entry */ + if (stat(tmp, &sb) == 0) { + /* if entry is a directory */ + if ((sb.st_mode & S_IFDIR) != 0) { + /* + * if dir list is NOT NULL, add dir to + * dir list + */ + if (dir_list != NULL) { + direntry = malloc(sizeof(dir_entry_t)); + if (direntry == NULL) { + return (ISC_R_NOMEMORY); + } + strcpy(direntry->dirpath, tmp); + DLZ_LINK_INIT(direntry, link); + DLZ_LIST_APPEND(*dir_list, direntry, + link); + result = ISC_R_SUCCESS; + } + continue; + + /* + * if entry is a file be sure we do + * not add entry to DNS results if we + * are performing a zone xfr and we + * could not find a host entry. + */ + } else if (dir_list != NULL && !foundHost) { + continue; + } + } else { /* if we cannot stat entry, skip it. */ + continue; + } + + type = dir->entry.name; + ttlStr = strchr(type, cd->separator); + if (ttlStr == NULL) { + cd->log(ISC_LOG_ERROR, + "Filesystem driver: " + "%s could not be parsed properly", + tmp); + return (ISC_R_FAILURE); + } + + /* replace separator char with NULL to split string */ + ttlStr[0] = '\0'; + /* start string after NULL of previous string */ + ttlStr = (char *)&ttlStr[1]; + + data = strchr(ttlStr, cd->separator); + if (data == NULL) { + cd->log(ISC_LOG_ERROR, + "Filesystem driver: " + "%s could not be parsed properly", + tmp); + return (ISC_R_FAILURE); + } + + /* replace separator char with NULL to split string */ + data[0] = '\0'; + + /* start string after NULL of previous string */ + data = (char *)&data[1]; + + /* replace all cd->separator chars with a space. */ + len = strlen(data); + + for (i = 0; i < len; i++) { + if (data[i] == cd->separator) { + data[i] = ' '; + } + } + + /* convert text to int, make sure it worked right */ + ttl = strtol(ttlStr, &endp, 10); + if (*endp != '\0' || ttl < 0) { + cd->log(ISC_LOG_ERROR, "Filesystem driver " + "ttl must be a positive number"); + } + + /* pass data back to Bind */ + if (dir_list == NULL) { + result = cd->putrr((dns_sdlzlookup_t *)passback, type, + ttl, data); + } else { + result = cd->putnamedrr((dns_sdlzallnodes_t *)passback, + (char *)host, type, ttl, data); + } + + /* if error, return error right away */ + if (result != ISC_R_SUCCESS) { + return (result); + } + } /* end of while loop */ + + return (result); +} + +/* + * DLZ methods + */ +isc_result_t +dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { + isc_result_t result; + char *path; + struct stat sb; + config_data_t *cd; + path = NULL; + + cd = (config_data_t *)dbdata; + + if (create_path(name, NULL, client, cd, &path) != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + if (stat(path, &sb) != 0) { + result = ISC_R_NOTFOUND; + goto complete_AXFR; + } + + if ((sb.st_mode & S_IFREG) != 0) { + result = ISC_R_SUCCESS; + goto complete_AXFR; + } + + result = ISC_R_NOTFOUND; + +complete_AXFR: + free(path); + return (result); +} + +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) { + isc_result_t result; + dlist_t *dir_list; + config_data_t *cd = (config_data_t *)dbdata; + char *basepath; + unsigned int basepathlen; + struct stat sb; + dir_t dir; + dir_entry_t *dir_entry; + dir_entry_t *next_de; + + basepath = NULL; + + /* allocate memory for list */ + dir_list = malloc(sizeof(dlist_t)); + if (dir_list == NULL) { + result = ISC_R_NOTFOUND; + goto complete_allnds; + } + + /* initialize list */ + DLZ_LIST_INIT(*dir_list); + + if (create_path(zone, NULL, NULL, cd, &basepath) != ISC_R_SUCCESS) { + result = ISC_R_NOTFOUND; + goto complete_allnds; + } + + /* remove path separator at end of path so stat works properly */ + basepathlen = strlen(basepath); + + if (stat(basepath, &sb) != 0) { + result = ISC_R_NOTFOUND; + goto complete_allnds; + } + + if ((sb.st_mode & S_IFDIR) == 0) { + result = ISC_R_NOTFOUND; + goto complete_allnds; + } + + /* initialize and open directory */ + dir_init(&dir); + result = dir_open(&dir, basepath); + + /* if directory open failed, return error. */ + if (result != ISC_R_SUCCESS) { + cd->log(ISC_LOG_ERROR, + "Unable to open %s directory to read entries.", + basepath); + result = ISC_R_FAILURE; + goto complete_allnds; + } + + /* process the directory */ + result = process_dir(&dir, allnodes, cd, dir_list, basepathlen); + + /* close the directory */ + dir_close(&dir); + + if (result != ISC_R_SUCCESS) { + goto complete_allnds; + } + + /* get first dir entry from list. */ + dir_entry = DLZ_LIST_HEAD(*dir_list); + while (dir_entry != NULL) { + result = dir_open(&dir, dir_entry->dirpath); + /* if directory open failed, return error. */ + if (result != ISC_R_SUCCESS) { + cd->log(ISC_LOG_ERROR, + "Unable to open %s " + "directory to read entries.", + basepath); + result = ISC_R_FAILURE; + goto complete_allnds; + } + + /* process the directory */ + result = process_dir(&dir, allnodes, cd, dir_list, basepathlen); + + /* close the directory */ + dir_close(&dir); + + if (result != ISC_R_SUCCESS) { + goto complete_allnds; + } + + dir_entry = DLZ_LIST_NEXT(dir_entry, link); + } /* end while */ + +complete_allnds: + if (dir_list != NULL) { + /* clean up entries from list. */ + dir_entry = DLZ_LIST_HEAD(*dir_list); + while (dir_entry != NULL) { + next_de = DLZ_LIST_NEXT(dir_entry, link); + free(dir_entry); + dir_entry = next_de; + } /* end while */ + free(dir_list); + } + + if (basepath != NULL) { + free(basepath); + } + + return (result); +} + +#if DLZ_DLOPEN_VERSION < 3 +isc_result_t +dlz_findzonedb(void *dbdata, const char *name) +#else /* if DLZ_DLOPEN_VERSION < 3 */ +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif /* if DLZ_DLOPEN_VERSION < 3 */ +{ + isc_result_t result; + config_data_t *cd = (config_data_t *)dbdata; + char *path; + struct stat sb; + path = NULL; + +#if DLZ_DLOPEN_VERSION >= 3 + UNUSED(methods); + UNUSED(clientinfo); +#endif /* if DLZ_DLOPEN_VERSION >= 3 */ + + if (create_path(name, NULL, NULL, cd, &path) != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + cd->log(ISC_LOG_DEBUG(1), + "Filesystem driver Findzone() Checking for path: '%s'\n", path); + + if (stat(path, &sb) != 0) { + result = ISC_R_NOTFOUND; + goto complete_FZ; + } + + if ((sb.st_mode & S_IFDIR) != 0) { + result = ISC_R_SUCCESS; + goto complete_FZ; + } + + result = ISC_R_NOTFOUND; + +complete_FZ: + + free(path); + return (result); +} + +#if DLZ_DLOPEN_VERSION == 1 +isc_result_t +dlz_lookup(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup) +#else /* if DLZ_DLOPEN_VERSION == 1 */ +isc_result_t +dlz_lookup(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif /* if DLZ_DLOPEN_VERSION == 1 */ +{ + isc_result_t result = ISC_R_NOTFOUND; + config_data_t *cd = (config_data_t *)dbdata; + char *path; + struct stat sb; + dir_t dir; + path = NULL; + + UNUSED(lookup); +#if DLZ_DLOPEN_VERSION >= 2 + UNUSED(methods); + UNUSED(clientinfo); +#endif /* if DLZ_DLOPEN_VERSION >= 2 */ + + if (strcmp(name, "*") == 0) { + /* + * handle filesystem's special wildcard "-" + */ + result = create_path(zone, "-", NULL, cd, &path); + } else { + result = create_path(zone, name, NULL, cd, &path); + } + + if (result != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + /* remove path separator at end of path so stat works properly */ + path[strlen(path) - 1] = '\0'; + + cd->log(ISC_LOG_DEBUG(1), + "Filesystem driver lookup() Checking for path: '%s'\n", path); + + if (stat(path, &sb) != 0) { + result = ISC_R_NOTFOUND; + goto complete_lkup; + } + + if ((sb.st_mode & S_IFDIR) == 0) { + result = ISC_R_NOTFOUND; + goto complete_lkup; + } + + /* initialize and open directory */ + dir_init(&dir); + result = dir_open(&dir, path); + + /* if directory open failed, return error. */ + if (result != ISC_R_SUCCESS) { + cd->log(ISC_LOG_ERROR, + "Unable to open %s directory to read entries.", path); + result = ISC_R_FAILURE; + goto complete_lkup; + } + + /* process any records in the directory */ + result = process_dir(&dir, lookup, cd, NULL, 0); + + /* close the directory */ + dir_close(&dir); + +complete_lkup: + + free(path); + return (result); +} + +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], void **dbdata, + ...) { + isc_result_t result = ISC_R_NOMEMORY; + config_data_t *cd; + char *endp; + int len; + char pathsep; + const char *helper_name; + va_list ap; + + UNUSED(dlzname); + + /* allocate memory for our config data and helper functions */ + cd = calloc(1, sizeof(config_data_t)); + if (cd == NULL) { + goto no_mem; + } + + /* zero the memory */ + memset(cd, 0, sizeof(config_data_t)); + + /* Fill in the helper functions */ + va_start(ap, dbdata); + while ((helper_name = va_arg(ap, const char *)) != NULL) { + b9_add_helper(cd, helper_name, va_arg(ap, void *)); + } + va_end(ap); + + /* we require 5 command line args. */ + if (argc != 6) { + cd->log(ISC_LOG_ERROR, "Filesystem driver requires " + "6 command line args."); + result = ISC_R_FAILURE; + goto free_cd; + } + + if (strlen(argv[5]) > 1) { + cd->log(ISC_LOG_ERROR, "Filesystem driver can only " + "accept a single character for " + "separator."); + result = ISC_R_FAILURE; + goto free_cd; + } + + /* verify base dir ends with '/' or '\' */ + len = strlen(argv[1]); + if (argv[1][len - 1] != '\\' && argv[1][len - 1] != '/') { + cd->log(ISC_LOG_ERROR, + "Base dir parameter for filesystem driver " + "should end with %s", + "either '/' or '\\' "); + result = ISC_R_FAILURE; + goto free_cd; + } + + /* determine and save path separator for later */ + if (argv[1][len - 1] == '\\') { + pathsep = '\\'; + } else { + pathsep = '/'; + } + + cd->pathsep = pathsep; + + /* get and store our base directory */ + cd->basedir = strdup(argv[1]); + if (cd->basedir == NULL) { + goto no_mem; + } + cd->basedirsize = strlen(cd->basedir); + + /* get and store our data sub-dir */ + cd->datadir = strdup(argv[2]); + if (cd->datadir == NULL) { + goto no_mem; + } + cd->datadirsize = strlen(cd->datadir); + + /* get and store our zone xfr sub-dir */ + cd->xfrdir = strdup(argv[3]); + if (cd->xfrdir == NULL) { + goto no_mem; + } + cd->xfrdirsize = strlen(cd->xfrdir); + + /* get and store our directory split count */ + cd->splitcnt = strtol(argv[4], &endp, 10); + if (*endp != '\0' || cd->splitcnt < 0) { + cd->log(ISC_LOG_ERROR, "Directory split count must be zero (0) " + "or a positive number"); + } + + /* get and store our separator character */ + cd->separator = *argv[5]; + + /* pass back config data */ + *dbdata = cd; + + /* return success */ + return (ISC_R_SUCCESS); + + /* handle no memory error */ +no_mem: + + /* write error message */ + if (cd != NULL && cd->log != NULL) { + cd->log(ISC_LOG_ERROR, "filesystem_dynamic: Filesystem driver " + "unable to " + "allocate memory for config data."); + } + +free_cd: + /* if we allocated a config data object clean it up */ + if (cd != NULL) { + dlz_destroy(cd); + } + + /* return error */ + return (result); +} + +void +dlz_destroy(void *dbdata) { + config_data_t *cd; + + cd = (config_data_t *)dbdata; + + /* + * free memory for each section of config data that was + * allocated + */ + if (cd->basedir != NULL) { + free(cd->basedir); + } + + if (cd->datadir != NULL) { + free(cd->datadir); + } + + if (cd->xfrdir != NULL) { + free(cd->xfrdir); + } + + /* free config data memory */ + free(cd); +} + +/* + * Return the version of the API + */ +int +dlz_version(unsigned int *flags) { + UNUSED(flags); + return (DLZ_DLOPEN_VERSION); +} + +/* + * Register a helper function from the bind9 dlz_dlopen driver + */ +static void +b9_add_helper(struct config_data *cd, const char *helper_name, void *ptr) { + if (strcmp(helper_name, "log") == 0) { + cd->log = (log_t *)ptr; + } + if (strcmp(helper_name, "putrr") == 0) { + cd->putrr = (dns_sdlz_putrr_t *)ptr; + } + if (strcmp(helper_name, "putnamedrr") == 0) { + cd->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr; + } + if (strcmp(helper_name, "writeable_zone") == 0) { + cd->writeable_zone = (dns_dlz_writeablezone_t *)ptr; + } +} diff --git a/contrib/dlz/modules/include/.clang-format b/contrib/dlz/modules/include/.clang-format new file mode 120000 index 0000000..e919bba --- /dev/null +++ b/contrib/dlz/modules/include/.clang-format @@ -0,0 +1 @@ +../../../../.clang-format.headers
\ No newline at end of file diff --git a/contrib/dlz/modules/include/dlz_dbi.h b/contrib/dlz/modules/include/dlz_dbi.h new file mode 100644 index 0000000..8080570 --- /dev/null +++ b/contrib/dlz/modules/include/dlz_dbi.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * SPDX-License-Identifier: MPL-2.0 and ISC + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdbool.h> + +#include <dlz_list.h> +#include <dlz_minimal.h> +#include <dlz_pthread.h> + +#ifndef DLZ_DBI_H +#define DLZ_DBI_H 1 + +/* + * Types + */ +#define REQUIRE_CLIENT 0x01 +#define REQUIRE_QUERY 0x02 +#define REQUIRE_RECORD 0x04 +#define REQUIRE_ZONE 0x08 + +typedef struct query_segment query_segment_t; +typedef DLZ_LIST(query_segment_t) query_list_t; +typedef struct dbinstance dbinstance_t; +typedef DLZ_LIST(dbinstance_t) db_list_t; +typedef struct driverinstance driverinstance_t; + +/*% + * a query segment is all the text between our special tokens + * special tokens are %zone%, %record%, %client% + */ +struct query_segment { + void *cmd; + unsigned int strlen; + bool direct; + DLZ_LINK(query_segment_t) link; +}; + +/*% + * a database instance contains everything we need for running + * a query against the database. Using it each separate thread + * can dynamically construct a query and execute it against the + * database. The "instance_lock" and locking code in the driver's + * make sure no two threads try to use the same DBI at a time. + */ +struct dbinstance { + void *dbconn; + query_list_t *allnodes_q; + query_list_t *allowxfr_q; + query_list_t *authority_q; + query_list_t *findzone_q; + query_list_t *lookup_q; + query_list_t *countzone_q; + char *query_buf; + char *zone; + char *record; + char *client; + dlz_mutex_t lock; + DLZ_LINK(dbinstance_t) link; +}; + +/* + * Method declarations + */ + +void +destroy_querylist(query_list_t **querylist); + +isc_result_t +build_querylist(const char *query_str, char **zone, char **record, + char **client, query_list_t **querylist, unsigned int flags, + log_t log); + +char * +build_querystring(query_list_t *querylist); + +isc_result_t +build_dbinstance(const char *allnodes_str, const char *allowxfr_str, + const char *authority_str, const char *findzone_str, + const char *lookup_str, const char *countzone_str, + dbinstance_t **dbi, log_t log); + +void +destroy_dbinstance(dbinstance_t *dbi); + +char * +get_parameter_value(const char *input, const char *key); + +#endif /* DLZ_DBI_H */ diff --git a/contrib/dlz/modules/include/dlz_list.h b/contrib/dlz/modules/include/dlz_list.h new file mode 100644 index 0000000..028e567 --- /dev/null +++ b/contrib/dlz/modules/include/dlz_list.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: ISC + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DLZ_LIST_H +#define DLZ_LIST_H 1 + +#define DLZ_LIST(type) \ + struct { \ + type *head, *tail; \ + } +#define DLZ_LIST_INIT(list) \ + do { \ + (list).head = NULL; \ + (list).tail = NULL; \ + } while (0) + +#define DLZ_LINK(type) \ + struct { \ + type *prev, *next; \ + } +#define DLZ_LINK_INIT(elt, link) \ + do { \ + (elt)->link.prev = (void *)(-1); \ + (elt)->link.next = (void *)(-1); \ + } while (0) + +#define DLZ_LIST_HEAD(list) ((list).head) +#define DLZ_LIST_TAIL(list) ((list).tail) + +#define DLZ_LIST_APPEND(list, elt, link) \ + do { \ + if ((list).tail != NULL) \ + (list).tail->link.next = (elt); \ + else \ + (list).head = (elt); \ + (elt)->link.prev = (list).tail; \ + (elt)->link.next = NULL; \ + (list).tail = (elt); \ + } while (0) + +#define DLZ_LIST_PREV(elt, link) ((elt)->link.prev) +#define DLZ_LIST_NEXT(elt, link) ((elt)->link.next) + +#define DLZ_LIST_UNLINK(list, elt, link) \ + do { \ + if ((elt)->link.next != NULL) \ + (elt)->link.next->link.prev = (elt)->link.prev; \ + else \ + (list).tail = (elt)->link.prev; \ + if ((elt)->link.prev != NULL) \ + (elt)->link.prev->link.next = (elt)->link.next; \ + else \ + (list).head = (elt)->link.next; \ + (elt)->link.prev = (void *)(-1); \ + (elt)->link.next = (void *)(-1); \ + } while (0) + +#endif /* DLZ_LIST_H */ diff --git a/contrib/dlz/modules/include/dlz_minimal.h b/contrib/dlz/modules/include/dlz_minimal.h new file mode 100644 index 0000000..b6fa611 --- /dev/null +++ b/contrib/dlz/modules/include/dlz_minimal.h @@ -0,0 +1,336 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: ISC + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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 header provides a minimal set of defines and typedefs needed + * for building an external DLZ module for bind9. When creating a new + * external DLZ driver, please copy this header into your own source + * tree. + */ + +#ifndef DLZ_MINIMAL_H +#define DLZ_MINIMAL_H 1 + +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> + +#include <sys/socket.h> +#include <sys/types.h> +#ifdef ISC_PLATFORM_HAVESYSUNH +#include <sys/un.h> +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ +#include <arpa/inet.h> +#include <net/if.h> +#include <netinet/in.h> + +typedef unsigned int isc_result_t; +typedef uint32_t dns_ttl_t; + +/* + * Define DLZ_DLOPEN_VERSION to different values to use older versions + * of the interface + */ +#ifndef DLZ_DLOPEN_VERSION +#define DLZ_DLOPEN_VERSION 3 +#define DLZ_DLOPEN_AGE 0 +#endif /* ifndef DLZ_DLOPEN_VERSION */ + +/* return these in flags from dlz_version() */ +#define DNS_SDLZFLAG_THREADSAFE 0x00000001U +#define DNS_SDLZFLAG_RELATIVEOWNER 0x00000002U +#define DNS_SDLZFLAG_RELATIVERDATA 0x00000004U + +/* result codes */ +#define ISC_R_SUCCESS 0 +#define ISC_R_NOMEMORY 1 +#define ISC_R_NOPERM 6 +#define ISC_R_NOSPACE 19 +#define ISC_R_NOTFOUND 23 +#define ISC_R_FAILURE 25 +#define ISC_R_NOTIMPLEMENTED 27 +#define ISC_R_NOMORE 29 +#define ISC_R_INVALIDFILE 30 +#define ISC_R_UNEXPECTED 34 +#define ISC_R_FILENOTFOUND 38 + +/* log levels */ +#define ISC_LOG_INFO (-1) +#define ISC_LOG_NOTICE (-2) +#define ISC_LOG_WARNING (-3) +#define ISC_LOG_ERROR (-4) +#define ISC_LOG_CRITICAL (-5) +#define ISC_LOG_DEBUG(level) (level) + +/* other useful definitions */ +#define UNUSED(x) (void)(x) +#define DE_CONST(konst, var) \ + do { \ + union { \ + const void *k; \ + void *v; \ + } _u; \ + _u.k = konst; \ + var = _u.v; \ + } while (0) + +#if !defined(__has_attribute) +#define __has_attribute(x) 0 +#endif /* if !defined(__has_attribute) */ + +#if __GNUC__ >= 7 || __has_attribute(fallthrough) +#define FALLTHROUGH __attribute__((fallthrough)) +#else +/* clang-format off */ +#define FALLTHROUGH do {} while (0) /* FALLTHROUGH */ +/* clang-format on */ +#endif + +#ifdef __GNUC__ +#define UNREACHABLE() __builtin_unreachable() +#else +#define UNREACHABLE() abort() +#endif + +/* opaque structures */ +typedef void *dns_sdlzlookup_t; +typedef void *dns_sdlzallnodes_t; +typedef void *dns_view_t; +typedef void *dns_dlzdb_t; + +#if DLZ_DLOPEN_VERSION > 1 +/* + * Method and type definitions needed for retrieval of client info + * from the caller. + */ +typedef struct isc_sockaddr { + union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +#ifdef ISC_PLATFORM_HAVESYSUNH + struct sockaddr_un sunix; +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ + } type; + unsigned int length; + void *link; +} isc_sockaddr_t; + +typedef struct isc_netaddr { + unsigned int family; + union { + struct in_addr in; + struct in6_addr in6; +#ifdef ISC_PLATFORM_HAVESYSUNH + char un[sizeof(((struct sockaddr_un *)0)->sun_path)]; +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ + } type; + uint32_t zone; +} isc_netaddr_t; + +typedef struct dns_ecs { + isc_netaddr_t addr; + uint8_t source; + uint8_t scope; +} dns_ecs_t; + +#define DNS_CLIENTINFO_VERSION 3 +typedef struct dns_clientinfo { + uint16_t version; + void *data; + void *dbversion; + dns_ecs_t ecs; +} dns_clientinfo_t; + +typedef isc_result_t (*dns_clientinfo_sourceip_t)(dns_clientinfo_t *client, + isc_sockaddr_t **addrp); + +typedef isc_result_t (*dns_clientinfo_version_t)(dns_clientinfo_t *client, + void **addrp); + +#define DNS_CLIENTINFOMETHODS_VERSION 2 +#define DNS_CLIENTINFOMETHODS_AGE 1 +typedef struct dns_clientinfomethods { + uint16_t version; + uint16_t age; + dns_clientinfo_sourceip_t sourceip; +} dns_clientinfomethods_t; +#endif /* DLZ_DLOPEN_VERSION > 1 */ + +#define DNS_ECS_FORMATSIZE \ + sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:XXX.XXX.XXX.XXX%SSSSSSSSSS" \ + "/NNN/NNN") + +/* + * Method definitions for callbacks provided by the dlopen driver + */ +typedef void +log_t(int level, const char *fmt, ...); + +typedef isc_result_t +dns_sdlz_putrr_t(dns_sdlzlookup_t *lookup, const char *type, dns_ttl_t ttl, + const char *data); + +typedef isc_result_t +dns_sdlz_putnamedrr_t(dns_sdlzallnodes_t *allnodes, const char *name, + const char *type, dns_ttl_t ttl, const char *data); + +#if DLZ_DLOPEN_VERSION < 3 +typedef isc_result_t +dns_dlz_writeablezone_t(dns_view_t *view, const char *zone_name); +#else /* DLZ_DLOPEN_VERSION >= 3 */ +typedef isc_result_t +dns_dlz_writeablezone_t(dns_view_t *view, dns_dlzdb_t *dlzdb, + const char *zone_name); +#endif /* DLZ_DLOPEN_VERSION */ + +/* + * prototypes for the functions you can include in your module + */ + +/* + * dlz_version() is required for all DLZ external drivers. It should + * return DLZ_DLOPEN_VERSION. '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. + */ +int +dlz_version(unsigned int *flags); + +/* + * dlz_create() is required for all DLZ external drivers. + */ +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], void **dbdata, + ...); + +/* + * dlz_destroy() is optional, and will be called when the driver is + * unloaded if supplied + */ +void +dlz_destroy(void *dbdata); + +/* + * dlz_findzonedb is required for all DLZ external drivers + */ +#if DLZ_DLOPEN_VERSION < 3 +isc_result_t +dlz_findzonedb(void *dbdata, const char *name); +#else /* DLZ_DLOPEN_VERSION >= 3 */ +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); +#endif /* DLZ_DLOPEN_VERSION */ + +/* + * dlz_lookup is required for all DLZ external drivers + */ +#if DLZ_DLOPEN_VERSION == 1 +isc_result_t +dlz_lookup(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup); +#else /* DLZ_DLOPEN_VERSION > 1 */ +isc_result_t +dlz_lookup(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); +#endif /* DLZ_DLOPEN_VERSION */ + +/* + * dlz_authority() is optional if dlz_lookup() supplies + * authority information (i.e., SOA, NS) for the dns record + */ +isc_result_t +dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup); + +/* + * dlz_allowzonexfr() is optional, and should be supplied if you want to + * support zone transfers + */ +isc_result_t +dlz_allowzonexfr(void *dbdata, const char *name, const char *client); + +/* + * dlz_allnodes() is optional, but must be supplied if supply a + * dlz_allowzonexfr() function + */ +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes); + +/* + * dlz_newversion() is optional. It should be supplied if you want to + * support dynamic updates. + */ +isc_result_t +dlz_newversion(const char *zone, void *dbdata, void **versionp); + +/* + * dlz_closeversion() is optional, but must be supplied if you supply a + * dlz_newversion() function + */ +void +dlz_closeversion(const char *zone, bool commit, void *dbdata, void **versionp); + +/* + * dlz_configure() is optional, but must be supplied if you want to support + * dynamic updates + */ +#if DLZ_DLOPEN_VERSION < 3 +isc_result_t +dlz_configure(dns_view_t *view, void *dbdata); +#else /* DLZ_DLOPEN_VERSION >= 3 */ +isc_result_t +dlz_configure(dns_view_t *view, dns_dlzdb_t *dlzdb, void *dbdata); +#endif /* DLZ_DLOPEN_VERSION */ + +/* + * dlz_ssumatch() is optional, but must be supplied if you want to support + * dynamic 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); + +/* + * dlz_addrdataset() is optional, but must be supplied if you want to + * support dynamic updates + */ +isc_result_t +dlz_addrdataset(const char *name, const char *rdatastr, void *dbdata, + void *version); + +/* + * dlz_subrdataset() is optional, but must be supplied if you want to + * support dynamic updates + */ +isc_result_t +dlz_subrdataset(const char *name, const char *rdatastr, void *dbdata, + void *version); + +/* + * dlz_delrdataset() is optional, but must be supplied if you want to + * support dynamic updates + */ +isc_result_t +dlz_delrdataset(const char *name, const char *type, void *dbdata, + void *version); + +#endif /* DLZ_MINIMAL_H */ diff --git a/contrib/dlz/modules/include/dlz_pthread.h b/contrib/dlz/modules/include/dlz_pthread.h new file mode 100644 index 0000000..4479f83 --- /dev/null +++ b/contrib/dlz/modules/include/dlz_pthread.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: ISC + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DLZ_PTHREAD_H +#define DLZ_PTHREAD_H 1 + +#ifndef PTHREADS +#define PTHREADS 1 +#endif /* ifndef PTHREADS */ + +#ifdef PTHREADS +#include <pthread.h> +#define dlz_mutex_t pthread_mutex_t +#define dlz_mutex_init pthread_mutex_init +#define dlz_mutex_destroy pthread_mutex_destroy +#define dlz_mutex_lock pthread_mutex_lock +#define dlz_mutex_trylock pthread_mutex_trylock +#define dlz_mutex_unlock pthread_mutex_unlock +#else /* !PTHREADS */ +#define dlz_mutex_t void +#define dlz_mutex_init(a, b) (0) +#define dlz_mutex_destroy(a) (0) +#define dlz_mutex_lock(a) (0) +#define dlz_mutex_trylock(a) (0) +#define dlz_mutex_unlock(a) (0) +#endif /* ifdef PTHREADS */ + +#endif /* DLZ_PTHREAD_H */ diff --git a/contrib/dlz/modules/ldap/Makefile b/contrib/dlz/modules/ldap/Makefile new file mode 100644 index 0000000..8e86e3e --- /dev/null +++ b/contrib/dlz/modules/ldap/Makefile @@ -0,0 +1,46 @@ +# Copyright 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 https://mozilla.org/MPL/2.0/. + +# Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. +# +# The development of Dynamically Loadable Zones (DLZ) for Bind 9 was +# conceived and contributed by Rob Butler. +# +# SPDX-License-Identifier: ISC and MPL-2.0 +# +# Permission to use, copy, modify, and distribute this software for any purpose +# with or without fee is hereby granted, provided that the above copyright +# notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +prefix = /usr +libdir = $(prefix)/lib/bind9 + +CFLAGS=-fPIC -g -I../include +LDAP_LIBS=-lldap + +all: dlz_ldap_dynamic.so + +dlz_dbi.o: ../common/dlz_dbi.c + $(CC) $(CFLAGS) -c ../common/dlz_dbi.c + +dlz_ldap_dynamic.so: dlz_ldap_dynamic.c dlz_dbi.o + $(CC) $(CFLAGS) -shared -o dlz_ldap_dynamic.so \ + dlz_ldap_dynamic.c dlz_dbi.o $(LDAP_LIBS) + +clean: + rm -f dlz_ldap_dynamic.so *.o + +install: dlz_ldap_dynamic.so + mkdir -p $(DESTDIR)$(libdir) + install dlz_ldap_dynamic.so $(DESTDIR)$(libdir) diff --git a/contrib/dlz/modules/ldap/dlz_ldap_dynamic.c b/contrib/dlz/modules/ldap/dlz_ldap_dynamic.c new file mode 100644 index 0000000..eca49ff --- /dev/null +++ b/contrib/dlz/modules/ldap/dlz_ldap_dynamic.c @@ -0,0 +1,1254 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 and 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 https://mozilla.org/MPL/2.0/. + */ + +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This provides the externally loadable ldap DLZ module, without + * update support + */ + +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <dlz_dbi.h> +#include <dlz_list.h> +#include <dlz_minimal.h> +#include <dlz_pthread.h> + +/* + * Need older API functions from ldap.h. + */ +#define LDAP_DEPRECATED 1 + +#include <ldap.h> + +#define SIMPLE "simple" +#define KRB41 "krb41" +#define KRB42 "krb42" +#define V2 "v2" +#define V3 "v3" + +#define dbc_search_limit 30 +#define ALLNODES 1 +#define ALLOWXFR 2 +#define AUTHORITY 3 +#define FINDZONE 4 +#define LOOKUP 5 + +/*% + * Structure to hold everything needed by this "instance" of the LDAP + * driver remember, the driver code is only loaded once, but may have + * many separate instances. + */ +typedef struct { +#if PTHREADS + db_list_t *db; /*%< handle to a list of DB */ +#else /* if PTHREADS */ + dbinstance_t *db; /*%< handle to db */ +#endif /* if PTHREADS */ + int method; /*%< security authentication + * method */ + char *user; /*%< who is authenticating */ + char *cred; /*%< password for simple + * authentication method */ + int protocol; /*%< LDAP communication + * protocol version */ + char *hosts; /*%< LDAP server hosts */ + + /* Helper functions from the dlz_dlopen driver */ + log_t *log; + dns_sdlz_putrr_t *putrr; + dns_sdlz_putnamedrr_t *putnamedrr; + dns_dlz_writeablezone_t *writeable_zone; +} ldap_instance_t; + +/* forward references */ + +#if DLZ_DLOPEN_VERSION < 3 +isc_result_t +dlz_findzonedb(void *dbdata, const char *name); +#else /* if DLZ_DLOPEN_VERSION < 3 */ +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); +#endif /* if DLZ_DLOPEN_VERSION < 3 */ + +void +dlz_destroy(void *dbdata); + +static void +b9_add_helper(ldap_instance_t *db, const char *helper_name, void *ptr); + +/* + * Private methods + */ + +/*% checks that the LDAP URL parameters make sense */ +static isc_result_t +dlz_ldap_checkURL(ldap_instance_t *db, char *URL, int attrCnt, + const char *msg) { + isc_result_t result = ISC_R_SUCCESS; + int ldap_result; + LDAPURLDesc *ldap_url = NULL; + + if (!ldap_is_ldap_url(URL)) { + db->log(ISC_LOG_ERROR, "%s query is not a valid LDAP URL", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + ldap_result = ldap_url_parse(URL, &ldap_url); + if (ldap_result != LDAP_SUCCESS || ldap_url == NULL) { + db->log(ISC_LOG_ERROR, "parsing %s query failed", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_count_values(ldap_url->lud_attrs) < attrCnt) { + db->log(ISC_LOG_ERROR, + "%s query must specify at least " + "%d attributes to return", + msg, attrCnt); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_url->lud_host != NULL) { + db->log(ISC_LOG_ERROR, "%s query must not specify a host", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_url->lud_port != 389) { + db->log(ISC_LOG_ERROR, "%s query must not specify a port", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_url->lud_dn == NULL || strlen(ldap_url->lud_dn) < 1) { + db->log(ISC_LOG_ERROR, "%s query must specify a search base", + msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_url->lud_exts != NULL || ldap_url->lud_crit_exts != 0) { + db->log(ISC_LOG_ERROR, + "%s uses extensions. " + "The driver does not support LDAP extensions.", + msg); + result = ISC_R_FAILURE; + goto cleanup; + } + +cleanup: + if (ldap_url != NULL) { + ldap_free_urldesc(ldap_url); + } + + return (result); +} + +/*% Connects / reconnects to LDAP server */ +static isc_result_t +dlz_ldap_connect(ldap_instance_t *dbi, dbinstance_t *dbc) { + isc_result_t result; + int ldap_result; + + /* if we have a connection, get ride of it. */ + if (dbc->dbconn != NULL) { + ldap_unbind_s((LDAP *)dbc->dbconn); + dbc->dbconn = NULL; + } + + /* now connect / reconnect. */ + + /* initialize. */ + dbc->dbconn = ldap_init(dbi->hosts, LDAP_PORT); + if (dbc->dbconn == NULL) { + return (ISC_R_NOMEMORY); + } + + /* set protocol version. */ + ldap_result = ldap_set_option((LDAP *)dbc->dbconn, + LDAP_OPT_PROTOCOL_VERSION, + &(dbi->protocol)); + if (ldap_result != LDAP_SUCCESS) { + result = ISC_R_NOPERM; + goto cleanup; + } + + /* "bind" to server. i.e. send username / pass */ + ldap_result = ldap_bind_s((LDAP *)dbc->dbconn, dbi->user, dbi->cred, + dbi->method); + if (ldap_result != LDAP_SUCCESS) { + result = ISC_R_FAILURE; + goto cleanup; + } + + return (ISC_R_SUCCESS); + +cleanup: + + /* cleanup if failure. */ + if (dbc->dbconn != NULL) { + ldap_unbind_s((LDAP *)dbc->dbconn); + dbc->dbconn = NULL; + } + + return (result); +} + +#if PTHREADS +/*% + * Properly cleans up a list of database instances. + * This function is only used when the driver is compiled for + * multithreaded operation. + */ +static void +dlz_ldap_destroy_dblist(db_list_t *dblist) { + dbinstance_t *ndbi = NULL; + dbinstance_t *dbi = NULL; + + /* get the first DBI in the list */ + ndbi = DLZ_LIST_HEAD(*dblist); + + /* loop through the list */ + while (ndbi != NULL) { + dbi = ndbi; + /* get the next DBI in the list */ + ndbi = DLZ_LIST_NEXT(dbi, link); + /* release DB connection */ + if (dbi->dbconn != NULL) { + ldap_unbind_s((LDAP *)dbi->dbconn); + } + /* release all memory that comprised a DBI */ + destroy_dbinstance(dbi); + } + /* release memory for the list structure */ + free(dblist); +} + +/*% + * Loops through the list of DB instances, attempting to lock + * on the mutex. If successful, the DBI is reserved for use + * and the thread can perform queries against the database. + * If the lock fails, the next one in the list is tried. + * looping continues until a lock is obtained, or until + * the list has been searched dbc_search_limit times. + * This function is only used when the driver is compiled for + * multithreaded operation. + */ +static dbinstance_t * +dlz_ldap_find_avail_conn(ldap_instance_t *ldap) { + dbinstance_t *dbi = NULL; + dbinstance_t *head; + int count = 0; + + /* get top of list */ + head = dbi = DLZ_LIST_HEAD(*ldap->db); + + /* loop through list */ + while (count < dbc_search_limit) { + /* try to lock on the mutex */ + if (dlz_mutex_trylock(&dbi->lock) == 0) { + return (dbi); /* success, return the DBI for use. */ + } + /* not successful, keep trying */ + dbi = DLZ_LIST_NEXT(dbi, link); + + /* check to see if we have gone to the top of the list. */ + if (dbi == NULL) { + count++; + dbi = head; + } + } + + ldap->log(ISC_LOG_INFO, + "LDAP driver unable to find available connection " + "after searching %d times", + count); + return (NULL); +} +#endif /* PTHREADS */ + +static isc_result_t +dlz_ldap_process_results(ldap_instance_t *db, LDAP *dbc, LDAPMessage *msg, + char **attrs, void *ptr, bool allnodes) { + isc_result_t result = ISC_R_SUCCESS; + int i = 0; + int j; + int len; + char *attribute = NULL; + LDAPMessage *entry; + char *endp = NULL; + char *host = NULL; + char *type = NULL; + char *data = NULL; + char **vals = NULL; + int ttl; + + /* get the first entry to process */ + entry = ldap_first_entry(dbc, msg); + if (entry == NULL) { + db->log(ISC_LOG_INFO, "LDAP no entries to process."); + return (ISC_R_FAILURE); + } + + /* loop through all entries returned */ + while (entry != NULL) { + /* reset for this loop */ + ttl = 0; + len = 0; + i = 0; + attribute = attrs[i]; + + /* determine how much space we need for data string */ + for (j = 0; attrs[j] != NULL; j++) { + /* get the list of values for this attribute. */ + vals = ldap_get_values(dbc, entry, attrs[j]); + /* skip empty attributes. */ + if (vals == NULL || ldap_count_values(vals) < 1) { + continue; + } + /* + * we only use the first value. this driver + * does not support multi-valued attributes. + */ + len = len + strlen(vals[0]) + 1; + /* free vals for next loop */ + ldap_value_free(vals); + } + + /* allocate memory for data string */ + data = malloc(len + 1); + if (data == NULL) { + db->log(ISC_LOG_ERROR, "LDAP driver unable to allocate " + "memory " + "while processing results"); + result = ISC_R_FAILURE; + goto cleanup; + } + + /* + * Make sure data is null termed at the beginning so + * we can check if any data was stored to it later. + */ + data[0] = '\0'; + + /* reset j to re-use below */ + j = 0; + + /* loop through the attributes in the order specified. */ + while (attribute != NULL) { + /* get the list of values for this attribute. */ + vals = ldap_get_values(dbc, entry, attribute); + + /* skip empty attributes. */ + if (vals == NULL || vals[0] == NULL) { + /* increment attribute pointer */ + attribute = attrs[++i]; + /* start loop over */ + continue; + } + + /* + * j initially = 0. Increment j each time we + * set a field that way next loop will set + * next field. + */ + switch (j) { + case 0: + j++; + /* + * convert text to int, make sure it + * worked right + */ + ttl = strtol(vals[0], &endp, 10); + if (*endp != '\0' || ttl < 0) { + db->log(ISC_LOG_ERROR, "LDAP driver " + "ttl must " + "be a positive " + "number"); + goto cleanup; + } + break; + case 1: + j++; + type = strdup(vals[0]); + break; + case 2: + j++; + if (allnodes) { + host = strdup(vals[0]); + } else { + strcpy(data, vals[0]); + } + break; + case 3: + j++; + if (allnodes) { + strcpy(data, vals[0]); + } else { + strcat(data, " "); + strcat(data, vals[0]); + } + break; + default: + strcat(data, " "); + strcat(data, vals[0]); + break; + } + + /* free values */ + ldap_value_free(vals); + vals = NULL; + + /* increment attribute pointer */ + attribute = attrs[++i]; + } + + if (type == NULL) { + db->log(ISC_LOG_ERROR, "LDAP driver unable to retrieve " + "DNS type"); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (strlen(data) < 1) { + db->log(ISC_LOG_ERROR, "LDAP driver unable to retrieve " + "DNS data"); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (allnodes && host != NULL) { + dns_sdlzallnodes_t *an = (dns_sdlzallnodes_t *)ptr; + if (strcasecmp(host, "~") == 0) { + result = db->putnamedrr(an, "*", type, ttl, + data); + } else { + result = db->putnamedrr(an, host, type, ttl, + data); + } + if (result != ISC_R_SUCCESS) { + db->log(ISC_LOG_ERROR, + "ldap_dynamic: putnamedrr failed " + "for \"%s %s %u %s\" (%d)", + host, type, ttl, data, result); + } + } else { + dns_sdlzlookup_t *lookup = (dns_sdlzlookup_t *)ptr; + result = db->putrr(lookup, type, ttl, data); + if (result != ISC_R_SUCCESS) { + db->log(ISC_LOG_ERROR, + "ldap_dynamic: putrr failed " + "for \"%s %u %s\" (%s)", + type, ttl, data, result); + } + } + + if (result != ISC_R_SUCCESS) { + db->log(ISC_LOG_ERROR, "LDAP driver failed " + "while sending data to BIND."); + goto cleanup; + } + + /* free memory for type, data and host for next loop */ + free(type); + type = NULL; + + free(data); + data = NULL; + + if (host != NULL) { + free(host); + host = NULL; + } + + /* get the next entry to process */ + entry = ldap_next_entry(dbc, entry); + } + +cleanup: + /* de-allocate memory */ + if (vals != NULL) { + ldap_value_free(vals); + } + if (host != NULL) { + free(host); + } + if (type != NULL) { + free(type); + } + if (data != NULL) { + free(data); + } + + return (result); +} + +/*% + * This function is the real core of the driver. Zone, record + * and client strings are passed in (or NULL is passed if the + * string is not available). The type of query we want to run + * is indicated by the query flag, and the dbdata object is passed + * passed in to. dbdata really holds either: + * 1) a list of database instances (in multithreaded mode) OR + * 2) a single database instance (in single threaded mode) + * The function will construct the query and obtain an available + * database instance (DBI). It will then run the query and hopefully + * obtain a result set. + */ +static isc_result_t +dlz_ldap_get_results(const char *zone, const char *record, const char *client, + unsigned int query, void *dbdata, void *ptr) { + isc_result_t result; + ldap_instance_t *db = (ldap_instance_t *)dbdata; + dbinstance_t *dbi = NULL; + char *querystring = NULL; + LDAPURLDesc *ldap_url = NULL; + int ldap_result = 0; + LDAPMessage *ldap_msg = NULL; + int i; + int entries; + + /* get db instance / connection */ +#if PTHREADS + /* find an available DBI from the list */ + dbi = dlz_ldap_find_avail_conn(db); +#else /* PTHREADS */ + /* + * only 1 DBI - no need to lock instance lock either + * only 1 thread in the whole process, no possible contention. + */ + dbi = (dbinstance_t *)(db->db); +#endif /* PTHREADS */ + + /* if DBI is null, can't do anything else */ + if (dbi == NULL) { + return (ISC_R_FAILURE); + } + + /* set fields */ + if (zone != NULL) { + dbi->zone = strdup(zone); + if (dbi->zone == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { + dbi->zone = NULL; + } + + if (record != NULL) { + dbi->record = strdup(record); + if (dbi->record == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { + dbi->record = NULL; + } + + if (client != NULL) { + dbi->client = strdup(client); + if (dbi->client == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { + dbi->client = NULL; + } + + /* what type of query are we going to run? */ + switch (query) { + case ALLNODES: + /* + * if the query was not passed in from the config file + * then we can't run it. return not_implemented, so + * it's like the code for that operation was never + * built into the driver.... AHHH flexibility!!! + */ + if (dbi->allnodes_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } else { + querystring = build_querystring(dbi->allnodes_q); + } + break; + case ALLOWXFR: + /* same as comments as ALLNODES */ + if (dbi->allowxfr_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } else { + querystring = build_querystring(dbi->allowxfr_q); + } + break; + case AUTHORITY: + /* same as comments as ALLNODES */ + if (dbi->authority_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } else { + querystring = build_querystring(dbi->authority_q); + } + break; + case FINDZONE: + /* this is required. It's the whole point of DLZ! */ + if (dbi->findzone_q == NULL) { + db->log(ISC_LOG_DEBUG(2), "No query specified for " + "findzone. " + "Findzone requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } else { + querystring = build_querystring(dbi->findzone_q); + } + break; + case LOOKUP: + /* this is required. It's also a major point of DLZ! */ + if (dbi->lookup_q == NULL) { + db->log(ISC_LOG_DEBUG(2), "No query specified for " + "lookup. " + "Lookup requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } else { + querystring = build_querystring(dbi->lookup_q); + } + break; + default: + /* + * this should never happen. If it does, the code is + * screwed up! + */ + db->log(ISC_LOG_ERROR, "Incorrect query flag passed to " + "dlz_ldap_get_results"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + /* if the querystring is null, Bummer, outta RAM. UPGRADE TIME!!! */ + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* + * output the full query string during debug so we can see + * what lame error the query has. + */ + db->log(ISC_LOG_DEBUG(1), "Query String: %s", querystring); + + /* break URL down into it's component parts, if error cleanup */ + ldap_result = ldap_url_parse(querystring, &ldap_url); + if (ldap_result != LDAP_SUCCESS || ldap_url == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + for (i = 0; i < 3; i++) { + /* + * dbi->dbconn may be null if trying to reconnect on a + * previous query failed. + */ + if (dbi->dbconn == NULL) { + db->log(ISC_LOG_INFO, "LDAP driver attempting to " + "re-connect"); + + result = dlz_ldap_connect((ldap_instance_t *)dbdata, + dbi); + if (result != ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + continue; + } + } + + /* perform ldap search synchronously */ + ldap_result = + ldap_search_s((LDAP *)dbi->dbconn, ldap_url->lud_dn, + ldap_url->lud_scope, ldap_url->lud_filter, + ldap_url->lud_attrs, 0, &ldap_msg); + + /* + * check return code. No such object is ok, just + * didn't find what we wanted + */ + switch (ldap_result) { + case LDAP_NO_SUCH_OBJECT: + db->log(ISC_LOG_DEBUG(1), "No object found matching " + "query requirements"); + result = ISC_R_NOTFOUND; + goto cleanup; + break; + case LDAP_SUCCESS: /* on success do nothing */ + result = ISC_R_SUCCESS; + i = 3; + break; + case LDAP_SERVER_DOWN: + db->log(ISC_LOG_INFO, "LDAP driver attempting to " + "re-connect"); + result = dlz_ldap_connect((ldap_instance_t *)dbdata, + dbi); + if (result != ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + } + break; + default: + /* + * other errors not ok. Log error message and + * get out + */ + db->log(ISC_LOG_ERROR, "LDAP error: %s", + ldap_err2string(ldap_result)); + result = ISC_R_FAILURE; + goto cleanup; + break; + } + } + + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + switch (query) { + case ALLNODES: + result = dlz_ldap_process_results(db, (LDAP *)dbi->dbconn, + ldap_msg, ldap_url->lud_attrs, + ptr, true); + break; + case AUTHORITY: + case LOOKUP: + result = dlz_ldap_process_results(db, (LDAP *)dbi->dbconn, + ldap_msg, ldap_url->lud_attrs, + ptr, false); + break; + case ALLOWXFR: + entries = ldap_count_entries((LDAP *)dbi->dbconn, ldap_msg); + if (entries == 0) { + result = ISC_R_NOPERM; + } else if (entries > 0) { + result = ISC_R_SUCCESS; + } else { + result = ISC_R_FAILURE; + } + break; + case FINDZONE: + entries = ldap_count_entries((LDAP *)dbi->dbconn, ldap_msg); + if (entries == 0) { + result = ISC_R_NOTFOUND; + } else if (entries > 0) { + result = ISC_R_SUCCESS; + } else { + result = ISC_R_FAILURE; + } + break; + default: + /* + * this should never happen. If it does, the code is + * screwed up! + */ + db->log(ISC_LOG_ERROR, "Incorrect query flag passed to " + "dlz_ldap_get_results"); + result = ISC_R_UNEXPECTED; + } + +cleanup: + /* it's always good to cleanup after yourself */ + + /* if we retrieved results, free them */ + if (ldap_msg != NULL) { + ldap_msgfree(ldap_msg); + } + + if (ldap_url != NULL) { + ldap_free_urldesc(ldap_url); + } + + /* cleanup */ + if (dbi->zone != NULL) { + free(dbi->zone); + } + if (dbi->record != NULL) { + free(dbi->record); + } + if (dbi->client != NULL) { + free(dbi->client); + } + dbi->zone = dbi->record = dbi->client = NULL; + + /* release the lock so another thread can use this dbi */ + (void)dlz_mutex_unlock(&dbi->lock); + + /* release query string */ + if (querystring != NULL) { + free(querystring); + } + + /* return result */ + return (result); +} + +/* + * DLZ methods + */ +isc_result_t +dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { + isc_result_t result; + + /* check to see if we are authoritative for the zone first */ +#if DLZ_DLOPEN_VERSION < 3 + result = dlz_findzonedb(dbdata, name); +#else /* if DLZ_DLOPEN_VERSION < 3 */ + result = dlz_findzonedb(dbdata, name, NULL, NULL); +#endif /* if DLZ_DLOPEN_VERSION < 3 */ + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* get all the zone data */ + result = dlz_ldap_get_results(name, NULL, client, ALLOWXFR, dbdata, + NULL); + return (result); +} + +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) { + return (dlz_ldap_get_results(zone, NULL, NULL, ALLNODES, dbdata, + allnodes)); +} + +isc_result_t +dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup) { + return (dlz_ldap_get_results(zone, NULL, NULL, AUTHORITY, dbdata, + lookup)); +} + +#if DLZ_DLOPEN_VERSION < 3 +isc_result_t +dlz_findzonedb(void *dbdata, const char *name) +#else /* if DLZ_DLOPEN_VERSION < 3 */ +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif /* if DLZ_DLOPEN_VERSION < 3 */ +{ +#if DLZ_DLOPEN_VERSION >= 3 + UNUSED(methods); + UNUSED(clientinfo); +#endif /* if DLZ_DLOPEN_VERSION >= 3 */ + return (dlz_ldap_get_results(name, NULL, NULL, FINDZONE, dbdata, NULL)); +} + +#if DLZ_DLOPEN_VERSION == 1 +isc_result_t +dlz_lookup(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup) +#else /* if DLZ_DLOPEN_VERSION == 1 */ +isc_result_t +dlz_lookup(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif /* if DLZ_DLOPEN_VERSION == 1 */ +{ + isc_result_t result; + +#if DLZ_DLOPEN_VERSION >= 2 + UNUSED(methods); + UNUSED(clientinfo); +#endif /* if DLZ_DLOPEN_VERSION >= 2 */ + + if (strcmp(name, "*") == 0) { + result = dlz_ldap_get_results(zone, "~", NULL, LOOKUP, dbdata, + lookup); + } else { + result = dlz_ldap_get_results(zone, name, NULL, LOOKUP, dbdata, + lookup); + } + return (result); +} + +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], void **dbdata, + ...) { + isc_result_t result = ISC_R_FAILURE; + ldap_instance_t *ldap = NULL; + dbinstance_t *dbi = NULL; + const char *helper_name; + int protocol; + int method; +#if PTHREADS + int dbcount; + char *endp; + int i; +#endif /* PTHREADS */ + va_list ap; + + UNUSED(dlzname); + + /* allocate memory for LDAP instance */ + ldap = calloc(1, sizeof(ldap_instance_t)); + if (ldap == NULL) { + return (ISC_R_NOMEMORY); + } + memset(ldap, 0, sizeof(ldap_instance_t)); + + /* Fill in the helper functions */ + va_start(ap, dbdata); + while ((helper_name = va_arg(ap, const char *)) != NULL) { + b9_add_helper(ldap, helper_name, va_arg(ap, void *)); + } + va_end(ap); + +#if PTHREADS + /* if debugging, let user know we are multithreaded. */ + ldap->log(ISC_LOG_DEBUG(1), "LDAP driver running multithreaded"); +#else /* PTHREADS */ + /* if debugging, let user know we are single threaded. */ + ldap->log(ISC_LOG_DEBUG(1), "LDAP driver running single threaded"); +#endif /* PTHREADS */ + + if (argc < 9) { + ldap->log(ISC_LOG_ERROR, "LDAP driver requires at least " + "8 command line args."); + goto cleanup; + } + + /* no more than 13 arg's should be passed to the driver */ + if (argc > 12) { + ldap->log(ISC_LOG_ERROR, "LDAP driver cannot accept more than " + "11 command line args."); + goto cleanup; + } + + /* determine protocol version. */ + if (strncasecmp(argv[2], V2, strlen(V2)) == 0) { + protocol = 2; + } else if (strncasecmp(argv[2], V3, strlen(V3)) == 0) { + protocol = 3; + } else { + ldap->log(ISC_LOG_ERROR, + "LDAP driver protocol must be either %s or %s", V2, + V3); + goto cleanup; + } + + /* determine connection method. */ + if (strncasecmp(argv[3], SIMPLE, strlen(SIMPLE)) == 0) { + method = LDAP_AUTH_SIMPLE; + } else if (strncasecmp(argv[3], KRB41, strlen(KRB41)) == 0) { + method = LDAP_AUTH_KRBV41; + } else if (strncasecmp(argv[3], KRB42, strlen(KRB42)) == 0) { + method = LDAP_AUTH_KRBV42; + } else { + ldap->log(ISC_LOG_ERROR, + "LDAP driver authentication method must be " + "one of %s, %s or %s", + SIMPLE, KRB41, KRB42); + goto cleanup; + } + + /* multithreaded build can have multiple DB connections */ +#if PTHREADS + /* check how many db connections we should create */ + dbcount = strtol(argv[1], &endp, 10); + if (*endp != '\0' || dbcount < 0) { + ldap->log(ISC_LOG_ERROR, "LDAP driver database connection " + "count " + "must be positive."); + goto cleanup; + } +#endif /* if PTHREADS */ + + /* check that LDAP URL parameters make sense */ + switch (argc) { + case 12: + result = dlz_ldap_checkURL(ldap, argv[11], 0, + "allow zone transfer"); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + FALLTHROUGH; + case 11: + result = dlz_ldap_checkURL(ldap, argv[10], 3, "all nodes"); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + FALLTHROUGH; + case 10: + if (strlen(argv[9]) > 0) { + result = dlz_ldap_checkURL(ldap, argv[9], 3, + "authority"); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + FALLTHROUGH; + case 9: + result = dlz_ldap_checkURL(ldap, argv[8], 3, "lookup"); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + result = dlz_ldap_checkURL(ldap, argv[7], 0, "find zone"); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + break; + default: + /* not really needed, should shut up compiler. */ + result = ISC_R_FAILURE; + } + + /* store info needed to automatically re-connect. */ + ldap->protocol = protocol; + ldap->method = method; + ldap->hosts = strdup(argv[6]); + if (ldap->hosts == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + ldap->user = strdup(argv[4]); + if (ldap->user == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + ldap->cred = strdup(argv[5]); + if (ldap->cred == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + +#if PTHREADS + /* allocate memory for database connection list */ + ldap->db = calloc(1, sizeof(db_list_t)); + if (ldap->db == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* initialize DB connection list */ + DLZ_LIST_INIT(*(ldap->db)); + + /* + * create the appropriate number of database instances (DBI) + * append each new DBI to the end of the list + */ + for (i = 0; i < dbcount; i++) { +#endif /* PTHREADS */ + /* how many queries were passed in from config file? */ + switch (argc) { + case 9: + result = build_dbinstance(NULL, NULL, NULL, argv[7], + argv[8], NULL, &dbi, + ldap->log); + break; + case 10: + result = build_dbinstance(NULL, NULL, argv[9], argv[7], + argv[8], NULL, &dbi, + ldap->log); + break; + case 11: + result = build_dbinstance(argv[10], NULL, argv[9], + argv[7], argv[8], NULL, &dbi, + ldap->log); + break; + case 12: + result = build_dbinstance(argv[10], argv[11], argv[9], + argv[7], argv[8], NULL, &dbi, + ldap->log); + break; + default: + /* not really needed, should shut up compiler. */ + result = ISC_R_FAILURE; + } + + if (result == ISC_R_SUCCESS) { + ldap->log(ISC_LOG_DEBUG(2), "LDAP driver created " + "database instance " + "object."); + } else { /* unsuccessful?, log err msg and cleanup. */ + ldap->log(ISC_LOG_ERROR, "LDAP driver could not create " + "database instance object."); + goto cleanup; + } + +#if PTHREADS + /* when multithreaded, build a list of DBI's */ + DLZ_LINK_INIT(dbi, link); + DLZ_LIST_APPEND(*(ldap->db), dbi, link); +#else /* if PTHREADS */ + /* + * when single threaded, hold onto the one connection + * instance. + */ + ldap->db = dbi; +#endif /* if PTHREADS */ + /* attempt to connect */ + result = dlz_ldap_connect(ldap, dbi); + + /* + * if db connection cannot be created, log err msg and + * cleanup. + */ + switch (result) { + /* success, do nothing */ + case ISC_R_SUCCESS: + break; + /* + * no memory means ldap_init could not + * allocate memory + */ + case ISC_R_NOMEMORY: +#if PTHREADS + ldap->log(ISC_LOG_ERROR, + "LDAP driver could not allocate memory " + "for connection number %u", + i + 1); +#else /* if PTHREADS */ + ldap->log(ISC_LOG_ERROR, "LDAP driver could not allocate " + "memory " + "for connection"); +#endif /* if PTHREADS */ + goto cleanup; + /* + * no perm means ldap_set_option could not set + * protocol version + */ + case ISC_R_NOPERM: + ldap->log(ISC_LOG_ERROR, "LDAP driver could not " + "set protocol version."); + result = ISC_R_FAILURE; + goto cleanup; + /* failure means couldn't connect to ldap server */ + case ISC_R_FAILURE: +#if PTHREADS + ldap->log(ISC_LOG_ERROR, + "LDAP driver could not bind " + "connection number %u to server.", + i + 1); +#else /* if PTHREADS */ + ldap->log(ISC_LOG_ERROR, "LDAP driver could not " + "bind connection to server."); +#endif /* if PTHREADS */ + goto cleanup; + /* + * default should never happen. If it does, + * major errors. + */ + default: + ldap->log(ISC_LOG_ERROR, "dlz_create() failed (%d)", + result); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + +#if PTHREADS + /* set DBI = null for next loop through. */ + dbi = NULL; + } +#endif /* PTHREADS */ + + /* set dbdata to the ldap_instance we created. */ + *dbdata = ldap; + + return (ISC_R_SUCCESS); + +cleanup: + dlz_destroy(ldap); + + return (result); +} + +void +dlz_destroy(void *dbdata) { + if (dbdata != NULL) { + ldap_instance_t *db = (ldap_instance_t *)dbdata; +#if PTHREADS + /* cleanup the list of DBI's */ + if (db->db != NULL) { + dlz_ldap_destroy_dblist((db_list_t *)(db->db)); + } +#else /* PTHREADS */ + if (db->db->dbconn != NULL) { + ldap_unbind_s((LDAP *)(db->db->dbconn)); + } + + /* destroy single DB instance */ + destroy_dbinstance(db->db); +#endif /* PTHREADS */ + + if (db->hosts != NULL) { + free(db->hosts); + } + if (db->user != NULL) { + free(db->user); + } + if (db->cred != NULL) { + free(db->cred); + } + free(dbdata); + } +} + +/* + * Return the version of the API + */ +int +dlz_version(unsigned int *flags) { + *flags |= DNS_SDLZFLAG_RELATIVERDATA; +#if PTHREADS + *flags |= DNS_SDLZFLAG_THREADSAFE; +#else /* if PTHREADS */ + *flags &= ~DNS_SDLZFLAG_THREADSAFE; +#endif /* if PTHREADS */ + return (DLZ_DLOPEN_VERSION); +} + +/* + * Register a helper function from the bind9 dlz_dlopen driver + */ +static void +b9_add_helper(ldap_instance_t *db, const char *helper_name, void *ptr) { + if (strcmp(helper_name, "log") == 0) { + db->log = (log_t *)ptr; + } + if (strcmp(helper_name, "putrr") == 0) { + db->putrr = (dns_sdlz_putrr_t *)ptr; + } + if (strcmp(helper_name, "putnamedrr") == 0) { + db->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr; + } + if (strcmp(helper_name, "writeable_zone") == 0) { + db->writeable_zone = (dns_dlz_writeablezone_t *)ptr; + } +} diff --git a/contrib/dlz/modules/ldap/testing/README b/contrib/dlz/modules/ldap/testing/README new file mode 100644 index 0000000..69b1381 --- /dev/null +++ b/contrib/dlz/modules/ldap/testing/README @@ -0,0 +1,10 @@ +These files were used for testing on Ubuntu Linux using OpenLDAP. + +- Move aside /etc/ldap/slapd.d +- Move slapd.conf to /etc/ldap +- Move dlz.schema to /etc/ldap/schema/dlz.schema +- Run "/etc/init.d/slapd restart" +- Run "ldapadd -x -f example.ldif -D 'cn=Manager,o=bind-dlz' -w secret" + +LDAP server is now loaded with example.com data from the file example.ldif + diff --git a/contrib/dlz/modules/ldap/testing/dlz.schema b/contrib/dlz/modules/ldap/testing/dlz.schema new file mode 100644 index 0000000..d0f0086 --- /dev/null +++ b/contrib/dlz/modules/ldap/testing/dlz.schema @@ -0,0 +1,192 @@ +# +# +# 1.3.6.1.4.1.18420.1.1.X is reserved for attribute types declared by the DLZ project. +# 1.3.6.1.4.1.18420.1.2.X is reserved for object classes declared by the DLZ project. +# 1.3.6.1.4.1.18420.1.3.X is reserved for PRIVATE extensions to the DLZ attribute +# types and object classes that may be needed by end users +# to add security, etc. Attributes and object classes using +# this OID MUST NOT be published outside of an organization +# except to offer them for consideration to become part of the +# standard attributes and object classes published by the DLZ project. + +attributetype ( 1.3.6.1.4.1.18420.1.1.10 + NAME 'dlzZoneName' + DESC 'DNS zone name - domain name not including host name' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.20 + NAME 'dlzHostName' + DESC 'Host portion of a domain name' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.30 + NAME 'dlzData' + DESC 'Data for the resource record' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.40 + NAME 'dlzType' + DESC 'DNS record type - A, SOA, NS, MX, etc...' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.50 + NAME 'dlzSerial' + DESC 'SOA record serial number' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.60 + NAME 'dlzRefresh' + DESC 'SOA record refresh time in seconds' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.70 + NAME 'dlzRetry' + DESC 'SOA retry time in seconds' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.80 + NAME 'dlzExpire' + DESC 'SOA expire time in seconds' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.90 + NAME 'dlzMinimum' + DESC 'SOA minimum time in seconds' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.100 + NAME 'dlzAdminEmail' + DESC 'E-mail address of person responsible for this zone - @ should be replaced with . (period)' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.110 + NAME 'dlzPrimaryNS' + DESC 'Primary name server for this zone - should be host name not IP address' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.120 + NAME 'dlzIPAddr' + DESC 'IP address - IPV4 should be in dot notation xxx.xxx.xxx.xxx IPV6 should be in colon notation xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx' + EQUALITY caseExactIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{40} + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.130 + NAME 'dlzCName' + DESC 'DNS cname' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.140 + NAME 'dlzPreference' + DESC 'DNS MX record preference. Lower numbers have higher preference' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.150 + NAME 'dlzTTL' + DESC 'DNS time to live - how long this record can be cached by caching DNS servers' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.160 + NAME 'dlzRecordID' + DESC 'Unique ID for each DLZ resource record' + SUP name + SINGLE-VALUE ) + +#------------------------------------------------------------------------------ +# Object class definitions +#------------------------------------------------------------------------------ + +objectclass ( 1.3.6.1.4.1.18420.1.2.10 + NAME 'dlzZone' + DESC 'Zone name portion of a domain name' + SUP top STRUCTURAL + MUST ( objectclass $ dlzZoneName ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.20 + NAME 'dlzHost' + DESC 'Host name portion of a domain name' + SUP top STRUCTURAL + MUST ( objectclass $ dlzHostName ) MAY ( description ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.30 + NAME 'dlzAbstractRecord' + DESC 'Data common to all DNS record types' + SUP top ABSTRACT + MUST ( objectclass $ dlzRecordID $ dlzHostName $ dlzType $ dlzTTL ) MAY ( description ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.40 + NAME 'dlzGenericRecord' + DESC 'Generic DNS record - useful when a specific object class has not been defined for a DNS record' + SUP dlzAbstractRecord STRUCTURAL + MUST ( dlzData ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.50 + NAME 'dlzARecord' + DESC 'DNS A record' + SUP dlzAbstractrecord STRUCTURAL + MUST ( dlzIPAddr ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.60 + NAME 'dlzNSRecord' + DESC 'DNS NS record' + SUP dlzGenericRecord STRUCTURAL ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.70 + NAME 'dlzMXRecord' + DESC 'DNS MX record' + SUP dlzGenericRecord STRUCTURAL + MUST ( dlzPreference ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.80 + NAME 'dlzSOARecord' + DESC 'DNS SOA record' + SUP dlzAbstractRecord STRUCTURAL + MUST ( dlzSerial $ dlzRefresh $ dlzRetry + $ dlzExpire $ dlzMinimum $ dlzAdminEmail $ dlzPrimaryNS ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.90 + NAME 'dlzTextRecord' + DESC 'Text data with spaces should be wrapped in double quotes' + SUP dlzGenericRecord STRUCTURAL ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.100 + NAME 'dlzPTRRecord' + DESC 'DNS PTR record' + SUP dlzGenericRecord STRUCTURAL ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.110 + NAME 'dlzCNameRecord' + DESC 'DNS CName record' + SUP dlzGenericRecord STRUCTURAL ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.120 + NAME 'dlzXFR' + DESC 'Host allowed to perform zone transfer' + SUP top STRUCTURAL + MUST ( objectclass $ dlzRecordID $ dlzIPAddr ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.130 + NAME 'dlzDNameRecord' + DESC 'DNS DName record' + SUP dlzGenericRecord STRUCTURAL ) diff --git a/contrib/dlz/modules/ldap/testing/example.ldif b/contrib/dlz/modules/ldap/testing/example.ldif new file mode 100644 index 0000000..fff1793 --- /dev/null +++ b/contrib/dlz/modules/ldap/testing/example.ldif @@ -0,0 +1,192 @@ +# server suffix - o=bind-dlz + +dn: o=bind-dlz +objectclass: organization +o: bind-dlz + +dn: ou=dns,o=bind-dlz +objectclass: organizationalUnit +ou: dns + +dn: dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzZone +dlzZoneName: example.com + +dn: dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: @ + +dn: dlzHostName=www,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: www + +dn: dlzHostName=mail,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: mail + +dn: dlzHostName=backup,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: backup + +dn: dlzHostName=ns1,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: ns1 + +dn: dlzHostName=ns2,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: ns2 + +dn: dlzHostName=~,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: ~ + +dn: dlzHostName=cname,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: cname + +dn: dlzHostName=dname,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: dname + +dn: dlzRecordID=1,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzGenericRecord +dlzRecordID: 1 +dlzHostName: @ +dlzType: txt +dlzData: "this is a text record" +dlzTTL: 10 + +dn: dlzRecordID=2,dlzHostName=www,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 2 +dlzHostName: www +dlzType: a +dlzIPAddr: 192.168.0.1 +dlzTTL: 10 + +dn: dlzRecordID=3,dlzHostName=mail,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 3 +dlzHostName: mail +dlzType: a +dlzIPAddr: 192.168.0.2 +dlzTTL: 10 + +dn: dlzRecordID=4,dlzHostName=backup,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 4 +dlzHostName: backup +dlzType: a +dlzIPAddr: 192.168.0.3 +dlzTTL: 10 + +dn: dlzRecordID=5,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzMXRecord +dlzRecordID: 5 +dlzHostName: @ +dlzType: mx +dlzData: mail +dlzPreference: 20 +dlzTTL: 10 + +dn: dlzRecordID=6,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzMXRecord +dlzRecordID: 6 +dlzHostName: @ +dlzType: mx +dlzData: backup +dlzPreference: 40 +dlzTTL: 10 + +dn: dlzRecordID=7,dlzHostName=www,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzMXRecord +dlzRecordID: 7 +dlzHostName: www +dlzType: mx +dlzData: backup +dlzPreference: 40 +dlzTTL: 10 + +dn: dlzRecordID=8,dlzHostName=www,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzMXRecord +dlzRecordID: 8 +dlzHostName: www +dlzType: mx +dlzData: mail +dlzPreference: 20 +dlzTTL: 10 + +dn: dlzRecordID=9,dlzHostName=ns1,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 9 +dlzHostName: ns1 +dlzType: a +dlzIPAddr: 192.168.0.4 +dlzTTL: 10 + +dn: dlzRecordID=10,dlzHostName=ns2,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 10 +dlzHostName: ns2 +dlzType: a +dlzIPAddr: 192.168.0.5 +dlzTTL: 10 + +dn: dlzRecordID=11,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzSOARecord +dlzRecordID: 11 +dlzHostName: @ +dlzType: soa +dlzSerial: 2 +dlzRefresh: 2800 +dlzRetry: 7200 +dlzExpire: 604800 +dlzMinimum: 86400 +dlzAdminEmail: root.example.com. +dlzPrimaryns: ns1.example.com. +dlzTTL: 10 + +dn: dlzRecordID=12,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzNSRecord +dlzRecordID: 12 +dlzHostName: @ +dlzType: ns +dlzData: ns1.example.com. +dlzTTL: 10 + +dn: dlzRecordID=13,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzNSRecord +dlzRecordID: 13 +dlzHostName: @ +dlzType: ns +dlzData: ns2 +dlzTTL: 10 + +dn: dlzRecordID=14,dlzHostName=~,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 14 +dlzHostName: ~ +dlzType: a +dlzIPAddr: 192.168.0.250 +dlzTTL: 10 + +dn: dlzRecordID=15,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzXFR +dlzRecordID: 15 +dlzIPAddr: 127.0.0.1 + +dn: dlzRecordID=16,dlzHostName=cname,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzCNameRecord +dlzRecordID: 16 +dlzHostName: cname +dlzType: cname +dlzData: www +dlzTTL: 10 + +dn: dlzRecordID=17,dlzHostName=dname,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzDNameRecord +dlzRecordID: 17 +dlzHostName: dname +dlzType: dname +dlzData: example.net. +dlzTTL: 10 diff --git a/contrib/dlz/modules/ldap/testing/named.conf b/contrib/dlz/modules/ldap/testing/named.conf new file mode 100644 index 0000000..3f8378b --- /dev/null +++ b/contrib/dlz/modules/ldap/testing/named.conf @@ -0,0 +1,43 @@ +/* + * 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. + */ + +controls { }; + +options { + directory "."; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { any; }; + listen-on-v6 { none; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-md5; +}; + +controls { + inet 127.0.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +dlz "test" { + database "dlopen ../dlz_ldap_dynamic.so 2 + v3 simple {cn=Manager,o=bind-dlz} {secret} {127.0.0.1} + ldap:///dlzZoneName=$zone$,ou=dns,o=bind-dlz???objectclass=dlzZone + ldap:///dlzHostName=$record$,dlzZoneName=$zone$,ou=dns,o=bind-dlz?dlzTTL,dlzType,dlzPreference,dlzData,dlzIPAddr?sub?(&(objectclass=dlzAbstractRecord)(!(dlzType=soa))) + ldap:///dlzHostName=@,dlzZoneName=$zone$,ou=dns,o=bind-dlz?dlzTTL,dlzType,dlzData,dlzPrimaryNS,dlzAdminEmail,dlzSerial,dlzRefresh,dlzRetry,dlzExpire,dlzMinimum?sub?(&(objectclass=dlzAbstractRecord)(dlzType=soa)) + ldap:///dlzZoneName=$zone$,ou=dns,o=bind-dlz?dlzTTL,dlzType,dlzHostName,dlzPreference,dlzData,dlzIPAddr,dlzPrimaryNS,dlzAdminEmail,dlzSerial,dlzRefresh,dlzRetry,dlzExpire,dlzMinimum?sub?(&(objectclass=dlzAbstractRecord)(!(dlzType=soa))) + ldap:///dlzZoneName=$zone$,ou=dns,o=bind-dlz??sub?(&(objectclass=dlzXFR)(dlzIPAddr=$client$))"; +}; diff --git a/contrib/dlz/modules/ldap/testing/slapd.conf b/contrib/dlz/modules/ldap/testing/slapd.conf new file mode 100644 index 0000000..14c8ffb --- /dev/null +++ b/contrib/dlz/modules/ldap/testing/slapd.conf @@ -0,0 +1,44 @@ +# this is the full path to the core.schema +include /etc/ldap/schema/core.schema + +# this is the full path to the dlz.schema +include /etc/ldap/schema/dlz.schema + +# these files hold the slapd process ID and program args when +# slapd is started. +pidfile /var/run/slapd/slapd.pid +argsfile /var/run/slapd/slapd.args + +modulepath /usr/lib/ldap +moduleload back_hdb + +# this allows ldap version 2 connections. You should comment +# it out if you don't need ldap version 2. +allow bind_v2 + +# this sets up the Berkeley DB database backend for LDAP to use. +database hdb + +# This is the root of the LDAP server. You still need to add +# an entry to this location via a LDIF file, or you won't be +# able to add anything else into the LDAP server. +suffix "o=bind-dlz" + +# this is the "username" you have to use when connecting to the +# ldap server to make updates. Type the whole thing exactly +# as you see it as a parameter to ldapadd. +rootdn "cn=Manager,o=bind-dlz" + +# this is the "password" you have to use when connecting to the +# ldap server to make updates. +rootpw secret + +# this is the directory that the LDAP server will create the +# Berkeley DB backend in. +directory /var/lib/ldap + +# this just adds some indexing to the LDAP server. +# probably should have more to better optimize DLZ LDAP searches. +index cn,sn,uid pres,eq +index objectClass eq + diff --git a/contrib/dlz/modules/mysql/Makefile.in b/contrib/dlz/modules/mysql/Makefile.in new file mode 100644 index 0000000..4aa199e --- /dev/null +++ b/contrib/dlz/modules/mysql/Makefile.in @@ -0,0 +1,52 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# Copyright 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. + +# Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. +# +# The development of Dynamically Loadable Zones (DLZ) for Bind 9 was +# conceived and contributed by Rob Butler. +# +# SPDX-License-Identifier: ISC and MPL-2.0 +# +# Permission to use, copy, modify, and distribute this software for any purpose +# with or without fee is hereby granted, provided that the above copyright +# notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +prefix = /usr +libdir = $(prefix)/lib/bind9 + +CFLAGS=-fPIC -Wall -g -I../include @DLZ_DRIVER_MYSQL_INCLUDES@ +MYSQL_LIBS=@DLZ_DRIVER_MYSQL_LIBS@ + +all: dlz_mysql_dynamic.so + +dlz_dbi.o: ../common/dlz_dbi.c + $(CC) $(CFLAGS) -c ../common/dlz_dbi.c + +dlz_mysql_dynamic.so: dlz_mysql_dynamic.c dlz_dbi.o + $(CC) $(CFLAGS) -shared -o dlz_mysql_dynamic.so \ + dlz_mysql_dynamic.c dlz_dbi.o $(MYSQL_LIBS) + +clean: + rm -f dlz_mysql_dynamic.so *.o + +install: dlz_mysql_dynamic.so + mkdir -p $(DESTDIR)$(libdir) + install dlz_mysql_dynamic.so $(DESTDIR)$(libdir) diff --git a/contrib/dlz/modules/mysql/dlz_mysql_dynamic.c b/contrib/dlz/modules/mysql/dlz_mysql_dynamic.c new file mode 100644 index 0000000..a7b48c5 --- /dev/null +++ b/contrib/dlz/modules/mysql/dlz_mysql_dynamic.c @@ -0,0 +1,1109 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 and 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 https://mozilla.org/MPL/2.0/. + */ + +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This provides the externally loadable MySQL DLZ module, without + * update support + */ + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <mysql/mysql.h> + +#include <dlz_dbi.h> +#include <dlz_list.h> +#include <dlz_minimal.h> +#include <dlz_pthread.h> + +#if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 80000 +typedef bool my_bool; +#endif /* !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 80000 */ + +#define dbc_search_limit 30 +#define ALLNODES 1 +#define ALLOWXFR 2 +#define AUTHORITY 3 +#define FINDZONE 4 +#define COUNTZONE 5 +#define LOOKUP 6 + +#define safeGet(in) in == NULL ? "" : in + +/*% + * Structure to hold everything needed by this "instance" of the MySQL + * module remember, the module code is only loaded once, but may have + * many separate instances. + */ +typedef struct { +#if PTHREADS + db_list_t *db; /*%< handle to a list of DB */ + int dbcount; +#else /* if PTHREADS */ + dbinstance_t *db; /*%< handle to DB */ +#endif /* if PTHREADS */ + + unsigned int flags; + char *dbname; + char *host; + char *user; + char *pass; + char *socket; + int port; + + /* 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; +} mysql_instance_t; + +/* forward references */ +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); + +void +dlz_destroy(void *dbdata); + +static void +b9_add_helper(mysql_instance_t *db, const char *helper_name, void *ptr); + +/* + * Private methods + */ + +void +mysql_destroy(dbinstance_t *db) { + /* release DB connection */ + if (db->dbconn != NULL) { + mysql_close((MYSQL *)db->dbconn); + } + + /* destroy DB instance */ + destroy_dbinstance(db); +} + +#if PTHREADS +/*% + * Properly cleans up a list of database instances. + * This function is only used when the module is compiled for + * multithreaded operation. + */ +static void +mysql_destroy_dblist(db_list_t *dblist) { + dbinstance_t *ndbi = NULL; + dbinstance_t *dbi = NULL; + + ndbi = DLZ_LIST_HEAD(*dblist); + while (ndbi != NULL) { + dbi = ndbi; + ndbi = DLZ_LIST_NEXT(dbi, link); + + mysql_destroy(dbi); + } + + /* release memory for the list structure */ + free(dblist); +} + +/*% + * Loops through the list of DB instances, attempting to lock + * on the mutex. If successful, the DBI is reserved for use + * and the thread can perform queries against the database. + * If the lock fails, the next one in the list is tried. + * looping continues until a lock is obtained, or until + * the list has been searched dbc_search_limit times. + * This function is only used when the module is compiled for + * multithreaded operation. + */ +static dbinstance_t * +mysql_find_avail_conn(mysql_instance_t *mysql) { + dbinstance_t *dbi = NULL, *head; + int count = 0; + + /* get top of list */ + head = dbi = DLZ_LIST_HEAD(*(mysql->db)); + + /* loop through list */ + while (count < dbc_search_limit) { + /* try to lock on the mutex */ + if (dlz_mutex_trylock(&dbi->lock) == 0) { + return (dbi); /* success, return the DBI for use. */ + } + /* not successful, keep trying */ + dbi = DLZ_LIST_NEXT(dbi, link); + + /* check to see if we have gone to the top of the list. */ + if (dbi == NULL) { + count++; + dbi = head; + } + } + + mysql->log(ISC_LOG_INFO, + "MySQL module unable to find available connection " + "after searching %d times", + count); + return (NULL); +} +#endif /* PTHREADS */ + +/*% + * Allocates memory for a new string, and then constructs the new + * string by "escaping" the input string. The new string is + * safe to be used in queries. This is necessary because we cannot + * be sure of what types of strings are passed to us, and we don't + * want special characters in the string causing problems. + */ +static char * +mysqldrv_escape_string(MYSQL *mysql, const char *instr) { + char *outstr; + unsigned int len; + + if (instr == NULL) { + return (NULL); + } + + len = strlen(instr); + outstr = malloc((2 * len * sizeof(char)) + 1); + if (outstr == NULL) { + return (NULL); + } + + mysql_real_escape_string(mysql, outstr, instr, len); + + return (outstr); +} + +/*% + * This function is the real core of the module. Zone, record + * and client strings are passed in (or NULL is passed if the + * string is not available). The type of query we want to run + * is indicated by the query flag, and the dbdata object is passed + * passed in to. dbdata really holds a single database instance. + * The function will construct and run the query, hopefully getting + * a result set. + */ +static isc_result_t +mysql_get_resultset(const char *zone, const char *record, const char *client, + unsigned int query, void *dbdata, MYSQL_RES **rs) { + isc_result_t result; + dbinstance_t *dbi = NULL; + mysql_instance_t *db = (mysql_instance_t *)dbdata; + char *querystring = NULL; + unsigned int i = 0; + unsigned int j = 0; + int qres = 0; + +#if PTHREADS + /* find an available DBI from the list */ + dbi = mysql_find_avail_conn(db); +#else /* PTHREADS */ + /* + * only 1 DBI - no need to lock instance lock either + * only 1 thread in the whole process, no possible contention. + */ + dbi = (dbinstance_t *)(db->db); +#endif /* PTHREADS */ + + if (dbi == NULL) { + return (ISC_R_FAILURE); + } + + /* what type of query are we going to run? */ + switch (query) { + case ALLNODES: + if (dbi->allnodes_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case ALLOWXFR: + if (dbi->allowxfr_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case AUTHORITY: + if (dbi->authority_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case FINDZONE: + if (dbi->findzone_q == NULL) { + db->log(ISC_LOG_DEBUG(2), "No query specified for " + "findzone. " + "Findzone requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } + break; + case COUNTZONE: + if (dbi->countzone_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case LOOKUP: + if (dbi->lookup_q == NULL) { + db->log(ISC_LOG_DEBUG(2), "No query specified for " + "lookup. " + "Lookup requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } + break; + default: + db->log(ISC_LOG_ERROR, "Incorrect query flag passed to " + "mysql_get_resultset"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + if (zone != NULL) { + if (dbi->zone != NULL) { + free(dbi->zone); + } + + dbi->zone = mysqldrv_escape_string((MYSQL *)dbi->dbconn, zone); + if (dbi->zone == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { + dbi->zone = NULL; + } + + if (record != NULL) { + if (dbi->record != NULL) { + free(dbi->record); + } + + dbi->record = mysqldrv_escape_string((MYSQL *)dbi->dbconn, + record); + if (dbi->record == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { + dbi->record = NULL; + } + + if (client != NULL) { + if (dbi->client != NULL) { + free(dbi->client); + } + + dbi->client = mysqldrv_escape_string((MYSQL *)dbi->dbconn, + client); + if (dbi->client == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { + dbi->client = NULL; + } + + /* + * what type of query are we going to run? this time we build + * the actual query to run. + */ + switch (query) { + case ALLNODES: + querystring = build_querystring(dbi->allnodes_q); + break; + case ALLOWXFR: + querystring = build_querystring(dbi->allowxfr_q); + break; + case AUTHORITY: + querystring = build_querystring(dbi->authority_q); + break; + case FINDZONE: + querystring = build_querystring(dbi->findzone_q); + break; + case COUNTZONE: + querystring = build_querystring(dbi->countzone_q); + break; + case LOOKUP: + querystring = build_querystring(dbi->lookup_q); + break; + default: + db->log(ISC_LOG_ERROR, "Incorrect query flag passed to " + "mysql_get_resultset"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* output the full query string when debugging */ + db->log(ISC_LOG_DEBUG(1), "\nQuery String: %s\n", querystring); + + /* attempt query up to 3 times. */ + for (i = 0; i < 3; i++) { + qres = mysql_query((MYSQL *)dbi->dbconn, querystring); + if (qres == 0) { + break; + } + for (j = 0; j < 4; j++) { + if (mysql_ping((MYSQL *)dbi->dbconn) == 0) { + break; + } + } + } + + if (qres == 0) { + result = ISC_R_SUCCESS; + if (query != COUNTZONE) { + *rs = mysql_store_result((MYSQL *)dbi->dbconn); + if (*rs == NULL) { + result = ISC_R_FAILURE; + } + } + } else { + result = ISC_R_FAILURE; + } + +cleanup: + if (dbi->zone != NULL) { + free(dbi->zone); + dbi->zone = NULL; + } + if (dbi->record != NULL) { + free(dbi->record); + dbi->record = NULL; + } + if (dbi->client != NULL) { + free(dbi->client); + dbi->client = NULL; + } + + /* release the lock so another thread can use this dbi */ + (void)dlz_mutex_unlock(&dbi->lock); + + if (querystring != NULL) { + free(querystring); + } + + return (result); +} + +/*% + * The processing of result sets for lookup and authority are + * exactly the same. So that functionality has been moved + * into this function to minimize code. + */ +static isc_result_t +mysql_process_rs(mysql_instance_t *db, dns_sdlzlookup_t *lookup, + MYSQL_RES *rs) { + isc_result_t result = ISC_R_NOTFOUND; + MYSQL_ROW row; + unsigned int fields; + unsigned int j; + char *tmpString; + char *endp; + int ttl; + + fields = mysql_num_fields(rs); /* how many columns in result set */ + row = mysql_fetch_row(rs); /* get a row from the result set */ + while (row != NULL) { + unsigned int len = 0; + + switch (fields) { + case 1: + /* + * one column in rs, it's the data field. use + * default type of A record, and default TTL + * of 86400 + */ + result = db->putrr(lookup, "a", 86400, safeGet(row[0])); + break; + case 2: + /* + * two columns, data field, and data type. + * use default TTL of 86400. + */ + result = db->putrr(lookup, safeGet(row[0]), 86400, + safeGet(row[1])); + break; + case 3: + /* + * three columns, all data no defaults. + * convert text to int, make sure it worked + * right. + */ + ttl = strtol(safeGet(row[0]), &endp, 10); + if (*endp != '\0' || ttl < 0) { + db->log(ISC_LOG_ERROR, "MySQL module ttl must " + "be " + "a positive number"); + return (ISC_R_FAILURE); + } + + result = db->putrr(lookup, safeGet(row[1]), ttl, + safeGet(row[2])); + break; + default: + /* + * more than 3 fields, concatenate the last + * ones together. figure out how long to make + * string. + */ + for (j = 2; j < fields; j++) { + len += strlen(safeGet(row[j])) + 1; + } + + /* + * allocate string memory, allow for NULL to + * term string + */ + tmpString = malloc(len + 1); + if (tmpString == NULL) { + db->log(ISC_LOG_ERROR, "MySQL module unable to " + "allocate " + "memory for temporary " + "string"); + mysql_free_result(rs); + return (ISC_R_FAILURE); + } + + strcpy(tmpString, safeGet(row[2])); + for (j = 3; j < fields; j++) { + strcat(tmpString, " "); + strcat(tmpString, safeGet(row[j])); + } + + ttl = strtol(safeGet(row[0]), &endp, 10); + if (*endp != '\0' || ttl < 0) { + db->log(ISC_LOG_ERROR, "MySQL module ttl must " + "be " + "a positive number"); + free(tmpString); + return (ISC_R_FAILURE); + } + + result = db->putrr(lookup, safeGet(row[1]), ttl, + tmpString); + free(tmpString); + } + + if (result != ISC_R_SUCCESS) { + mysql_free_result(rs); + db->log(ISC_LOG_ERROR, "putrr returned error: %d", + result); + return (ISC_R_FAILURE); + } + + row = mysql_fetch_row(rs); + } + + mysql_free_result(rs); + return (result); +} + +/* + * DLZ methods + */ + +/*% determine if the zone is supported by (in) the database */ +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + isc_result_t result; + MYSQL_RES *rs = NULL; + my_ulonglong rows; + mysql_instance_t *db = (mysql_instance_t *)dbdata; + + UNUSED(methods); + UNUSED(clientinfo); + + result = mysql_get_resultset(name, NULL, NULL, FINDZONE, dbdata, &rs); + if (result != ISC_R_SUCCESS || rs == NULL) { + if (rs != NULL) { + mysql_free_result(rs); + } + + db->log(ISC_LOG_ERROR, "MySQL module unable to return " + "result set for findzone query"); + + return (ISC_R_FAILURE); + } + + /* + * if we returned any rows, the zone is supported. + */ + rows = mysql_num_rows(rs); + mysql_free_result(rs); + if (rows > 0) { + mysql_get_resultset(name, NULL, NULL, COUNTZONE, dbdata, NULL); + return (ISC_R_SUCCESS); + } + + return (ISC_R_NOTFOUND); +} + +/*% Determine if the client is allowed to perform a zone transfer */ +isc_result_t +dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { + isc_result_t result; + mysql_instance_t *db = (mysql_instance_t *)dbdata; + MYSQL_RES *rs = NULL; + my_ulonglong rows; + + /* first check if the zone is supported by the database. */ + result = dlz_findzonedb(dbdata, name, NULL, NULL); + if (result != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + /* + * if we get to this point we know the zone is supported by + * the database the only questions now are is the zone + * transfer is allowed for this client and did the config file + * have an allow zone xfr query. + */ + result = mysql_get_resultset(name, NULL, client, ALLOWXFR, dbdata, &rs); + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + + if (result != ISC_R_SUCCESS || rs == NULL) { + if (rs != NULL) { + mysql_free_result(rs); + } + db->log(ISC_LOG_ERROR, "MySQL module unable to return " + "result set for allow xfr query"); + return (ISC_R_FAILURE); + } + + /* + * count how many rows in result set; if we returned any, + * zone xfr is allowed. + */ + rows = mysql_num_rows(rs); + mysql_free_result(rs); + if (rows > 0) { + return (ISC_R_SUCCESS); + } + + return (ISC_R_NOPERM); +} + +/*% + * If the client is allowed to perform a zone transfer, the next order of + * business is to get all the nodes in the zone, so bind can respond to the + * query. + */ +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) { + isc_result_t result; + mysql_instance_t *db = (mysql_instance_t *)dbdata; + MYSQL_RES *rs = NULL; + MYSQL_ROW row; + unsigned int fields; + unsigned int j; + char *tmpString; + char *endp; + int ttl; + + result = mysql_get_resultset(zone, NULL, NULL, ALLNODES, dbdata, &rs); + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS) { + db->log(ISC_LOG_ERROR, "MySQL module unable to return " + "result set for all nodes query"); + goto cleanup; + } + + result = ISC_R_NOTFOUND; + + fields = mysql_num_fields(rs); /* how many columns in result set */ + row = mysql_fetch_row(rs); /* get a row from the result set */ + while (row != NULL) { + if (fields < 4) { + db->log(ISC_LOG_ERROR, "MySQL module too few fields " + "returned " + "by all nodes query"); + result = ISC_R_FAILURE; + goto cleanup; + } + + ttl = strtol(safeGet(row[0]), &endp, 10); + if (*endp != '\0' || ttl < 0) { + db->log(ISC_LOG_ERROR, "MySQL module ttl must be " + "a positive number"); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (fields == 4) { + result = db->putnamedrr(allnodes, safeGet(row[2]), + safeGet(row[1]), ttl, + safeGet(row[3])); + } else { + unsigned int len = 0; + + /* + * more than 4 fields, concatenate the last + * ones together. + */ + for (j = 3; j < fields; j++) { + len += strlen(safeGet(row[j])) + 1; + } + + tmpString = malloc(len + 1); + if (tmpString == NULL) { + db->log(ISC_LOG_ERROR, "MySQL module unable to " + "allocate " + "memory for temporary " + "string"); + result = ISC_R_FAILURE; + goto cleanup; + } + + strcpy(tmpString, safeGet(row[3])); + for (j = 4; j < fields; j++) { + strcat(tmpString, " "); + strcat(tmpString, safeGet(row[j])); + } + + result = db->putnamedrr(allnodes, safeGet(row[2]), + safeGet(row[1]), ttl, + tmpString); + free(tmpString); + } + + if (result != ISC_R_SUCCESS) { + db->log(ISC_LOG_ERROR, "putnamedrr returned error: %s", + result); + result = ISC_R_FAILURE; + break; + } + + row = mysql_fetch_row(rs); + } + +cleanup: + if (rs != NULL) { + mysql_free_result(rs); + } + + return (result); +} + +/*% + * If the lookup function does not return SOA or NS records for the zone, + * use this function to get that information for named. + */ +isc_result_t +dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup) { + isc_result_t result; + MYSQL_RES *rs = NULL; + mysql_instance_t *db = (mysql_instance_t *)dbdata; + + result = mysql_get_resultset(zone, NULL, NULL, AUTHORITY, dbdata, &rs); + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + + if (result != ISC_R_SUCCESS) { + if (rs != NULL) { + mysql_free_result(rs); + } + db->log(ISC_LOG_ERROR, "MySQL module unable to return " + "result set for authority query"); + return (ISC_R_FAILURE); + } + + /* + * lookup and authority result sets are processed in the same + * manner: mysql_process_rs does the job for both functions. + */ + return (mysql_process_rs(db, lookup, rs)); +} + +/*% If zone is supported, lookup up a (or multiple) record(s) in it */ +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; + MYSQL_RES *rs = NULL; + mysql_instance_t *db = (mysql_instance_t *)dbdata; + + UNUSED(methods); + UNUSED(clientinfo); + + result = mysql_get_resultset(zone, name, NULL, LOOKUP, dbdata, &rs); + + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS) { + if (rs != NULL) { + mysql_free_result(rs); + } + db->log(ISC_LOG_ERROR, "MySQL module unable to return " + "result set for lookup query"); + return (ISC_R_FAILURE); + } + + /* + * lookup and authority result sets are processed in the same + * manner: mysql_process_rs does the job for both functions. + */ + return (mysql_process_rs(db, lookup, rs)); +} + +/*% + * Create an instance of the module. + */ +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], void **dbdata, + ...) { + isc_result_t result = ISC_R_FAILURE; + mysql_instance_t *mysql = NULL; + dbinstance_t *dbi = NULL; + MYSQL *dbc; + char *tmp = NULL; + char *endp; + int j; + const char *helper_name; +#if PTHREADS + int dbcount; + int i; +#endif /* PTHREADS */ + va_list ap; + + UNUSED(dlzname); + + /* allocate memory for MySQL instance */ + mysql = calloc(1, sizeof(mysql_instance_t)); + if (mysql == NULL) { + return (ISC_R_NOMEMORY); + } + memset(mysql, 0, sizeof(mysql_instance_t)); + + /* Fill in the helper functions */ + va_start(ap, dbdata); + while ((helper_name = va_arg(ap, const char *)) != NULL) { + b9_add_helper(mysql, helper_name, va_arg(ap, void *)); + } + va_end(ap); + +#if PTHREADS + /* if debugging, let user know we are multithreaded. */ + mysql->log(ISC_LOG_DEBUG(1), "MySQL module running multithreaded"); +#else /* PTHREADS */ + /* if debugging, let user know we are single threaded. */ + mysql->log(ISC_LOG_DEBUG(1), "MySQL module running single threaded"); +#endif /* PTHREADS */ + + /* verify we have at least 4 arg's passed to the module */ + if (argc < 4) { + mysql->log(ISC_LOG_ERROR, "MySQL module requires " + "at least 4 command line args."); + return (ISC_R_FAILURE); + } + + /* no more than 8 arg's should be passed to the module */ + if (argc > 8) { + mysql->log(ISC_LOG_ERROR, "MySQL module cannot accept " + "more than 7 command line args."); + return (ISC_R_FAILURE); + } + + /* get db name - required */ + mysql->dbname = get_parameter_value(argv[1], "dbname="); + if (mysql->dbname == NULL) { + mysql->log(ISC_LOG_ERROR, "MySQL module requires a dbname " + "parameter."); + result = ISC_R_FAILURE; + goto cleanup; + } + + /* get db port. Not required, but must be > 0 if specified */ + tmp = get_parameter_value(argv[1], "port="); + if (tmp == NULL) { + mysql->port = 0; + } else { + mysql->port = strtol(tmp, &endp, 10); + if (*endp != '\0' || mysql->port < 0) { + mysql->log(ISC_LOG_ERROR, "Mysql module: port " + "must be a positive number."); + free(tmp); + result = ISC_R_FAILURE; + goto cleanup; + } + free(tmp); + } + + mysql->host = get_parameter_value(argv[1], "host="); + mysql->user = get_parameter_value(argv[1], "user="); + mysql->pass = get_parameter_value(argv[1], "pass="); + mysql->socket = get_parameter_value(argv[1], "socket="); + + mysql->flags = CLIENT_REMEMBER_OPTIONS; + + tmp = get_parameter_value(argv[1], "compress="); + if (tmp != NULL) { + if (strcasecmp(tmp, "true") == 0) { + mysql->flags |= CLIENT_COMPRESS; + } + free(tmp); + } + + tmp = get_parameter_value(argv[1], "ssl="); + if (tmp != NULL) { + if (strcasecmp(tmp, "true") == 0) { + mysql->flags |= CLIENT_SSL; + } + free(tmp); + } + + tmp = get_parameter_value(argv[1], "space="); + if (tmp != NULL) { + if (strcasecmp(tmp, "ignore") == 0) { + mysql->flags |= CLIENT_IGNORE_SPACE; + } + free(tmp); + } + +#if PTHREADS + /* multithreaded build can have multiple DB connections */ + tmp = get_parameter_value(argv[1], "threads="); + if (tmp == NULL) { + dbcount = 1; + } else { + dbcount = strtol(tmp, &endp, 10); + if (*endp != '\0' || dbcount < 1) { + mysql->log(ISC_LOG_ERROR, "MySQL database connection " + "count " + "must be positive."); + free(tmp); + result = ISC_R_FAILURE; + goto cleanup; + } + free(tmp); + } + + /* allocate memory for database connection list */ + mysql->db = calloc(1, sizeof(db_list_t)); + if (mysql->db == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* initialize DB connection list */ + DLZ_LIST_INIT(*(mysql->db)); + + /* + * create the appropriate number of database instances (DBI) + * append each new DBI to the end of the list + */ + for (i = 0; i < dbcount; i++) { +#endif /* PTHREADS */ + switch (argc) { + case 4: + result = build_dbinstance(NULL, NULL, NULL, argv[2], + argv[3], NULL, &dbi, + mysql->log); + break; + case 5: + result = build_dbinstance(NULL, NULL, argv[4], argv[2], + argv[3], NULL, &dbi, + mysql->log); + break; + case 6: + result = build_dbinstance(argv[5], NULL, argv[4], + argv[2], argv[3], NULL, &dbi, + mysql->log); + break; + case 7: + result = build_dbinstance(argv[5], argv[6], argv[4], + argv[2], argv[3], NULL, &dbi, + mysql->log); + break; + case 8: + result = build_dbinstance(argv[5], argv[6], argv[4], + argv[2], argv[3], argv[7], + &dbi, mysql->log); + break; + default: + result = ISC_R_FAILURE; + } + + if (result != ISC_R_SUCCESS) { + mysql->log(ISC_LOG_ERROR, "MySQL module could not " + "create " + "database instance object."); + result = ISC_R_FAILURE; + goto cleanup; + } + +#if PTHREADS + /* when multithreaded, build a list of DBI's */ + DLZ_LINK_INIT(dbi, link); + DLZ_LIST_APPEND(*(mysql->db), dbi, link); +#else /* if PTHREADS */ + /* + * when single threaded, hold onto the one connection + * instance. + */ + mysql->db = dbi; +#endif /* if PTHREADS */ + + /* create and set db connection */ + dbi->dbconn = mysql_init(NULL); + if (dbi->dbconn == NULL) { + mysql->log(ISC_LOG_ERROR, "MySQL module could not " + "allocate " + "memory for database " + "connection"); + result = ISC_R_FAILURE; + goto cleanup; + } + + dbc = NULL; + + /* enable automatic reconnection. */ + if (mysql_options((MYSQL *)dbi->dbconn, MYSQL_OPT_RECONNECT, + &(my_bool){ 1 }) != 0) + { + mysql->log(ISC_LOG_WARNING, "MySQL module failed to " + "set " + "MYSQL_OPT_RECONNECT " + "option, continuing"); + } + + for (j = 0; dbc == NULL && j < 4; j++) { + dbc = mysql_real_connect( + (MYSQL *)dbi->dbconn, mysql->host, mysql->user, + mysql->pass, mysql->dbname, mysql->port, + mysql->socket, mysql->flags); + if (dbc == NULL) { + mysql->log(ISC_LOG_ERROR, + "MySQL connection failed: %s", + mysql_error((MYSQL *)dbi->dbconn)); + } + } + + if (dbc == NULL) { + mysql->log(ISC_LOG_ERROR, "MySQL module failed to " + "create " + "database connection after 4 " + "attempts"); + result = ISC_R_FAILURE; + goto cleanup; + } + +#if PTHREADS + /* set DBI = null for next loop through. */ + dbi = NULL; + } +#endif /* PTHREADS */ + + *dbdata = mysql; + + return (ISC_R_SUCCESS); + +cleanup: + dlz_destroy(mysql); + + return (result); +} + +/*% + * Destroy the module. + */ +void +dlz_destroy(void *dbdata) { + mysql_instance_t *db = (mysql_instance_t *)dbdata; +#if PTHREADS + /* cleanup the list of DBI's */ + if (db->db != NULL) { + mysql_destroy_dblist((db_list_t *)(db->db)); + } +#else /* PTHREADS */ + mysql_destroy(db); +#endif /* PTHREADS */ + + if (db->dbname != NULL) { + free(db->dbname); + } + if (db->host != NULL) { + free(db->host); + } + if (db->user != NULL) { + free(db->user); + } + if (db->pass != NULL) { + free(db->pass); + } + if (db->socket != NULL) { + free(db->socket); + } +} + +/* + * Return the version of the API + */ +int +dlz_version(unsigned int *flags) { + *flags |= (DNS_SDLZFLAG_RELATIVEOWNER | DNS_SDLZFLAG_RELATIVERDATA | + DNS_SDLZFLAG_THREADSAFE); + return (DLZ_DLOPEN_VERSION); +} + +/* + * Register a helper function from the bind9 dlz_dlopen driver + */ +static void +b9_add_helper(mysql_instance_t *db, const char *helper_name, void *ptr) { + if (strcmp(helper_name, "log") == 0) { + db->log = (log_t *)ptr; + } + if (strcmp(helper_name, "putrr") == 0) { + db->putrr = (dns_sdlz_putrr_t *)ptr; + } + if (strcmp(helper_name, "putnamedrr") == 0) { + db->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr; + } + if (strcmp(helper_name, "writeable_zone") == 0) { + db->writeable_zone = (dns_dlz_writeablezone_t *)ptr; + } +} diff --git a/contrib/dlz/modules/mysql/testing/README b/contrib/dlz/modules/mysql/testing/README new file mode 100644 index 0000000..a4b87bb --- /dev/null +++ b/contrib/dlz/modules/mysql/testing/README @@ -0,0 +1,7 @@ +These files were used for testing on Ubuntu Linux using MySQL + +- Install MySQL: sudo apt-get install mysql-server +- Run "mysql --user=USER --password=PASSWORD < dlz.schema" to set up database +- Run "mysql --user=USER --password=PASSWORD < dlz.data" to populate it +- update named.conf with correct USER and PASSWORD + diff --git a/contrib/dlz/modules/mysql/testing/dlz.data b/contrib/dlz/modules/mysql/testing/dlz.data new file mode 100644 index 0000000..cef3e13 --- /dev/null +++ b/contrib/dlz/modules/mysql/testing/dlz.data @@ -0,0 +1,12 @@ +use BindDB; +INSERT INTO `records` (`id`, `zone`, `ttl`, `type`, `host`, `mx_priority`, `data`, `primary_ns`, `resp_contact`, `serial`, `refresh`, `retry`, `expire`, `minimum`) VALUES +(1, 'example.com', 86400, 'SOA', '@', NULL, NULL, 'ns1.example.com.', 'info.example.com.', 2011043001, 10800, 7200, 604800, 86400), +(2, 'example.com', 86400, 'NS', '@', NULL, 'ns1.example.com.', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(3, 'example.com', 86400, 'NS', '@', NULL, 'ns2.example.com.', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(4, 'example.com', 86400, 'MX', '@', 10, 'mail.example.com.', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(5, 'example.com', 86400, 'A', '@', NULL, '192.168.0.2', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(6, 'example.com', 86400, 'CNAME', 'www', NULL, '@', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(7, 'example.com', 86400, 'A', 'ns1', NULL, '192.168.0.111', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(8, 'example.com', 86400, 'A', 'ns2', NULL, '192.168.0.222', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(9, 'example.com', 86400, 'A', 'mail', NULL, '192.168.0.3', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(10, 'example.com', 86400, 'TXT', '@', NULL, 'v=spf1 ip:192.168.0.3 ~all', NULL, NULL, NULL, NULL, NULL, NULL, NULL) diff --git a/contrib/dlz/modules/mysql/testing/dlz.schema b/contrib/dlz/modules/mysql/testing/dlz.schema new file mode 100644 index 0000000..f20b59e --- /dev/null +++ b/contrib/dlz/modules/mysql/testing/dlz.schema @@ -0,0 +1,30 @@ +CREATE DATABASE `BindDB` DEFAULT CHARACTER SET latin1; +USE `BindDB`; + +CREATE TABLE IF NOT EXISTS `records` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `zone` varchar(255) NOT NULL, + `ttl` int(11) NOT NULL DEFAULT '86400', + `type` varchar(255) NOT NULL, + `host` varchar(255) NOT NULL DEFAULT '@', + `mx_priority` int(11) DEFAULT NULL, + `data` text, + `primary_ns` varchar(255) DEFAULT NULL, + `resp_contact` varchar(255) DEFAULT NULL, + `serial` bigint(20) DEFAULT NULL, + `refresh` int(11) DEFAULT NULL, + `retry` int(11) DEFAULT NULL, + `expire` int(11) DEFAULT NULL, + `minimum` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `type` (`type`), + KEY `host` (`host`), + KEY `zone` (`zone`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +CREATE TABLE IF NOT EXISTS `xfr` ( + `zone` varchar(255) NOT NULL, + `client` varchar(255) NOT NULL, + KEY `zone` (`zone`), + KEY `client` (`client`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; diff --git a/contrib/dlz/modules/mysql/testing/named.conf b/contrib/dlz/modules/mysql/testing/named.conf new file mode 100644 index 0000000..1152143 --- /dev/null +++ b/contrib/dlz/modules/mysql/testing/named.conf @@ -0,0 +1,46 @@ +/* + * 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. + */ + +controls { }; + +options { + directory "."; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { any; }; + listen-on-v6 { none; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-md5; +}; + +controls { + inet 127.0.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +dlz "test" { + database "dlopen ../dlz_mysql_dynamic.so + { + host=127.0.0.1 port=3306 socket=/tmp/mysql.sock + dbname=BindDB user=USER pass=PASSWORD threads=2 + } + {SELECT zone FROM records WHERE zone = '$zone$'} + {SELECT ttl, type, mx_priority, IF(type = 'TXT', CONCAT('\"',data,'\"'), data) AS data FROM records WHERE zone = '$zone$' AND host = '$record$' AND type <> 'SOA' AND type <> 'NS'} + {SELECT ttl, type, data, primary_ns, resp_contact, serial, refresh, retry, expire, minimum FROM records WHERE zone = '$zone$' AND (type = 'SOA' OR type='NS')} + {SELECT ttl, type, host, mx_priority, IF(type = 'TXT', CONCAT('\"',data,'\"'), data) AS data, resp_contact, serial, refresh, retry, expire, minimum FROM records WHERE zone = '$zone$' AND type <> 'SOA' AND type <> 'NS'} + {SELECT zone FROM xfr where zone='$zone$' AND client = '$client$'}"; +}; diff --git a/contrib/dlz/modules/mysqldyn/Makefile.in b/contrib/dlz/modules/mysqldyn/Makefile.in new file mode 100644 index 0000000..a230e36 --- /dev/null +++ b/contrib/dlz/modules/mysqldyn/Makefile.in @@ -0,0 +1,52 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# Copyright 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. + +# Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. +# +# The development of Dynamically Loadable Zones (DLZ) for Bind 9 was +# conceived and contributed by Rob Butler. +# +# SPDX-License-Identifier: ISC and MPL-2.0 +# +# Permission to use, copy, modify, and distribute this software for any purpose +# with or without fee is hereby granted, provided that the above copyright +# notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +prefix = /usr +libdir = $(prefix)/lib/bind9 + +CFLAGS=-fPIC -Wall -g -I../include @DLZ_DRIVER_MYSQL_INCLUDES@ +MYSQL_LIBS=@DLZ_DRIVER_MYSQL_LIBS@ + +all: dlz_mysqldyn_mod.so + +dlz_dbi.o: ../common/dlz_dbi.c + $(CC) $(CFLAGS) -c ../common/dlz_dbi.c + +dlz_mysqldyn_mod.so: dlz_mysqldyn_mod.c dlz_dbi.o + $(CC) $(CFLAGS) -shared -o dlz_mysqldyn_mod.so \ + dlz_mysqldyn_mod.c dlz_dbi.o $(MYSQL_LIBS) + +clean: + rm -f dlz_mysqldyn_mod.so *.o + +install: dlz_mysqldyn_mod.so + mkdir -p $(DESTDIR)$(libdir) + install dlz_mysqldyn_mod.so $(DESTDIR)$(libdir) diff --git a/contrib/dlz/modules/mysqldyn/README b/contrib/dlz/modules/mysqldyn/README new file mode 100644 index 0000000..6faa35d --- /dev/null +++ b/contrib/dlz/modules/mysqldyn/README @@ -0,0 +1,87 @@ +<!-- +Copyright 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 https://mozilla.org/MPL/2.0/. + +Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. + +The development of Dynamically Loadable Zones (DLZ) for Bind 9 was +conceived and contributed by Rob Butler. + +SPDX-License-Identifier: ISC and MPL-2.0 + +Permission to use, copy, modify, and distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright +notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +--> + +BIND 9 DLZ MySQL module with support for dynamic DNS (DDNS) + +Adapted from code contributed by Marty Lee, Maui Systems Ltd. + +This is a dynamically loadable zone (DLZ) plugin that uses a fixed- +schema MySQL database for back-end storage. It allows zone data +to be updated via dynamic DNS updates, and sends DNS NOTIFY packets +to other name servers when appropriate. + +The database for this module uses the following schema: + + CREATE TABLE `Zones` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `domain` varchar(128) NOT NULL DEFAULT '', + `host` varchar(128) NOT NULL DEFAULT '', + `admin` varchar(128) NOT NULL DEFAULT '', + `serial` int(11) NOT NULL DEFAULT '1', + `expire` int(11) NOT NULL DEFAULT '86400', + `refresh` int(11) NOT NULL DEFAULT '86400', + `retry` int(11) NOT NULL DEFAULT '86400', + `minimum` int(11) NOT NULL DEFAULT '86400', + `ttl` int(11) NOT NULL DEFAULT '86400', + `writeable` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `domain_idx` (`domain`) + ); + + CREATE TABLE `ZoneData` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `zone_id` int(11) NOT NULL, + `name` varchar(128) NOT NULL DEFAULT '', + `type` varchar(16) NOT NULL DEFAULT '', + `ttl` int(11) NOT NULL DEFAULT '86400', + `data` varchar(128) NOT NULL DEFAULT '', + PRIMARY KEY (`id`), + KEY `zone_idx` (`zone_id`), + KEY `name_idx` (`zone_id`, `name`), + KEY `type_idx` (`type`) + ); + +'Zones' contains information about specific zones: + - domain: the zone name + - admin: the zone administrator + - serial, expire, reresh, retry, minimum: values in the SOA record + - ttl: default zone TTL + - writeable: set to true if the zone can be updated via DDNS + +'ZoneData' contains the individual records within the zone: + - zone_id: the 'id' from the corresponding record in Zones + - name: domain name, relative to the zone apex. (Data at the zone + apex itself may use a blank name or "@".) + - type: the RR type, expressed as text + - ttl: the record's TTL + - data: the records rdata, expressed as text. + +To configure this module in named.conf: + +dlz "mysqldlz" { + database "dlopen <path to>/dlz_mysqldyn_mod.so <dbname> [dbhost [dbuser [dbpass]]]"; +}; diff --git a/contrib/dlz/modules/mysqldyn/dlz_mysqldyn_mod.c b/contrib/dlz/modules/mysqldyn/dlz_mysqldyn_mod.c new file mode 100644 index 0000000..bdd0bcc --- /dev/null +++ b/contrib/dlz/modules/mysqldyn/dlz_mysqldyn_mod.c @@ -0,0 +1,1800 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 and 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 https://mozilla.org/MPL/2.0/. + */ + +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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. + */ + +/* + * BIND 9 DLZ MySQL module with support for dynamic DNS (DDNS) + * + * Adapted from code contributed by Marty Lee, Maui Systems Ltd. + * + * See README for database schema and usage details. + */ + +#include <ifaddrs.h> +#include <inttypes.h> +#include <netdb.h> +#include <netinet/in.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include <mysql/errmsg.h> +#include <mysql/mysql.h> + +#include <dlz_list.h> +#include <dlz_minimal.h> +#include <dlz_pthread.h> + +#if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 80000 +typedef bool my_bool; +#endif /* !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 80000 */ + +/* + * The SQL queries that will be used for lookups and updates are defined + * here. They will be processed into queries by the build_query() + * function. + * + * NOTE: Despite appearances, these do NOT use printf-style formatting. + * "%s", with no modifiers, is the only supported directive. + */ + +/* + * Get the NS RRset for a zone + * Arguments: zone-name + */ +#define Q_GETNS \ + "SELECT d.data FROM ZoneData d, Zones z " \ + "WHERE UPPER(d.type) = 'NS' AND LOWER(z.domain) = LOWER('%s') " \ + "AND z.id = d.zone_id" + +/* + * Get a list of zones (ignoring writable or not) + * Arguments: (none) + */ +#define Q_GETZONES "SELECT LOWER(domain), serial FROM Zones" + +/* + * Find a specific zone + * Arguments: zone-name + */ +#define Q_FINDZONE "SELECT id FROM Zones WHERE LOWER(domain) = LOWER('%s')" + +/* + * Get SOA data from zone apex + * Arguments: zone-name + */ +#define Q_GETSOA \ + "SELECT host, admin, serial, refresh, retry, expire, minimum, ttl " \ + "FROM Zones WHERE LOWER(domain) = LOWER('%s')" + +/* + * Get other data from zone apex + * Arguments: zone-name, zone-name (repeated) + */ +#define Q_GETAPEX \ + "SELECT d.type, d.data, d.ttl FROM ZoneData d, Zones z " \ + "WHERE LOWER(z.domain) = LOWER('%s') AND z.id = d.zone_id " \ + "AND LOWER(d.name) IN (LOWER('%s'), '', '@') " \ + "ORDER BY UPPER(d.type) ASC" + +/* + * Get data from non-apex nodes + * Arguments: zone-name, node-name (relative to zone name) + */ +#define Q_GETNODE \ + "SELECT d.type, d.data, d.ttl FROM ZoneData d, Zones z " \ + "WHERE LOWER(z.domain) = LOWER('%s') AND z.id = d.zone_id " \ + "AND LOWER(d.name) = LOWER('%s') " \ + "ORDER BY UPPER(d.type) ASC" + +/* + * Get all data from a zone, for AXFR + * Arguments: zone-name + */ +#define Q_GETALL \ + "SELECT d.name, d.type, d.data, d.ttl FROM ZoneData d, Zones z " \ + "WHERE LOWER(z.domain) = LOWER('%s') AND z.id = d.zone_id" + +/* + * Get SOA serial number for a zone. + * Arguments: zone-name + */ +#define Q_GETSERIAL "SELECT serial FROM Zones WHERE domain = '%s'" + +/* + * Determine whether a zone is writeable, and if so, retrieve zone_id + * Arguments: zone-name + */ +#define Q_WRITEABLE \ + "SELECT id FROM Zones WHERE " \ + "LOWER(domain) = LOWER('%s') AND writeable = 1" + +/* + * Insert data into zone (other than SOA) + * Arguments: zone-id (from Q_WRITEABLE), node-name (relative to zone-name), + * rrtype, rdata text, TTL (in text format) + */ +#define I_DATA \ + "INSERT INTO ZoneData (zone_id, name, type, data, ttl) " \ + "VALUES (%s, LOWER('%s'), UPPER('%s'), '%s', %s)" + +/* + * Update SOA serial number for a zone + * Arguments: new serial number (in text format), zone-id (from Q_WRITEABLE) + */ +#define U_SERIAL "UPDATE Zones SET serial = %s WHERE id = %s" + +/* + * Delete a specific record (non-SOA) from a zone + * + * Arguments: node-name (relative to zone-name), zone-id (from Q_WRITEABLE), + * rrtype, rdata text, TTL (in text format). + */ +#define D_RECORD \ + "DELETE FROM ZoneData WHERE zone_id = %s AND " \ + "LOWER(name) = LOWER('%s') AND UPPER(type) = UPPER('%s') AND " \ + "data = '%s' AND ttl = %s" + +/* + * Delete an entire rrset from a zone + * Arguments: node-name (relative to zone-name), zone-id (from Q_WRITEABLE), + * rrtype. + */ +#define D_RRSET \ + "DELETE FROM ZoneData WHERE zone_id = %s AND " \ + "LOWER(name) = LOWER('%s') AND UPPER(type) = UPPER('%s')" + +/* + * Number of concurrent database connections we support + * - equivalent to maxmium number of concurrent transactions + * that can be 'in-flight' + 1 + */ +#define MAX_DBI 16 + +typedef struct mysql_record { + char zone[255]; + char name[100]; + char type[10]; + char data[200]; + char ttl[10]; +} mysql_record_t; + +typedef struct mysql_instance { + int id; + MYSQL *sock; + int connected; + dlz_mutex_t mutex; +} mysql_instance_t; + +typedef struct mysql_transaction mysql_transaction_t; +struct mysql_transaction { + char *zone; + char *zone_id; + mysql_instance_t *dbi; + mysql_transaction_t *next; +}; + +typedef struct mysql_data { + int debug; + + /* + * Database connection details + */ + char *db_name; + char *db_host; + char *db_user; + char *db_pass; + + /* + * Database structures + */ + mysql_instance_t db[MAX_DBI]; + + /* + * Transactions + */ + mysql_transaction_t *transactions; + + /* + * Mutex for transactions + */ + dlz_mutex_t tx_mutex; + + /* 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; +} mysql_data_t; + +typedef struct mysql_arg mysql_arg_t; +typedef DLZ_LIST(mysql_arg_t) mysql_arglist_t; +struct mysql_arg { + char *arg; + DLZ_LINK(mysql_arg_t) link; +}; + +static const char *modname = "dlz_mysqldyn"; + +/* + * Local functions + */ +static bool +db_connect(mysql_data_t *state, mysql_instance_t *dbi) { + MYSQL *conn; + /* + * Make sure this thread has been through 'init' + */ + mysql_thread_init(); + + if (dbi->connected) { + return (true); + } + + if (state->log != NULL) { + state->log(ISC_LOG_INFO, "%s: init connection %d ", modname, + dbi->id); + } + + conn = mysql_real_connect(dbi->sock, state->db_host, state->db_user, + state->db_pass, state->db_name, 0, NULL, + CLIENT_REMEMBER_OPTIONS); + if (conn == NULL) { + if (state->log != NULL) { + state->log(ISC_LOG_ERROR, + "%s: database connection failed: %s", + modname, mysql_error(dbi->sock)); + } + + dlz_mutex_unlock(&dbi->mutex); + return (false); + } + + dbi->connected = 1; + return (true); +} + +static mysql_instance_t * +get_dbi(mysql_data_t *state) { + int i; + + /* + * Find an available dbi + */ + for (i = 0; i < MAX_DBI; i++) { + if (dlz_mutex_trylock(&state->db[i].mutex) == 0) { + break; + } + } + + if (i == MAX_DBI) { + if (state->debug && state->log != NULL) { + state->log(ISC_LOG_ERROR, + "%s: No available connections", modname); + } + return (NULL); + } + return (&state->db[i]); +} + +/* + * Allocate memory and store an escaped, sanitized version + * of string 'original' + */ +static char * +sanitize(mysql_instance_t *dbi, const char *original) { + char *s; + + if (original == NULL) { + return (NULL); + } + + s = (char *)malloc((strlen(original) * 2) + 1); + if (s != NULL) { + memset(s, 0, (strlen(original) * 2) + 1); + + mysql_real_escape_string(dbi->sock, s, original, + strlen(original)); + } + + return (s); +} + +/* + * Append the string pointed to by 's' to the argument list 'arglist', + * and add the string length to the running total pointed to by 'len'. + */ +static isc_result_t +additem(mysql_arglist_t *arglist, char **s, size_t *len) { + mysql_arg_t *item; + + item = malloc(sizeof(*item)); + if (item == NULL) { + return (ISC_R_NOMEMORY); + } + + DLZ_LINK_INIT(item, link); + item->arg = *s; + *len += strlen(*s); + DLZ_LIST_APPEND(*arglist, item, link); + *s = NULL; + + return (ISC_R_SUCCESS); +} + +/* + * Construct a query string using a variable number of arguments, and + * save it into newly allocated memory. + * + * NOTE: 'command' resembles a printf-style format string, but ONLY + * supports the "%s" directive with no modifiers of any kind. + * + * If 'dbi' is NULL, we attempt to get a temporary database connection; + * otherwise we use the existing one. + */ +static char * +build_query(mysql_data_t *state, mysql_instance_t *dbi, const char *command, + ...) { + isc_result_t result; + bool localdbi = false; + mysql_arglist_t arglist; + mysql_arg_t *item; + char *p, *q, *tmp = NULL, *querystr = NULL; + char *query = NULL; + size_t len = 0; + va_list ap1; + + /* Get a DB instance if needed */ + if (dbi == NULL) { + dbi = get_dbi(state); + if (dbi == NULL) { + return (NULL); + } + localdbi = true; + } + + /* Make sure this instance is connected */ + if (!db_connect(state, dbi)) { + goto fail; + } + + va_start(ap1, command); + DLZ_LIST_INIT(arglist); + q = querystr = strdup(command); + if (querystr == NULL) { + goto fail; + } + + for (;;) { + if (*q == '\0') { + break; + } + + p = strstr(q, "%s"); + if (p != NULL) { + *p = '\0'; + tmp = strdup(q); + if (tmp == NULL) { + goto fail; + } + + result = additem(&arglist, &tmp, &len); + if (result != ISC_R_SUCCESS) { + goto fail; + } + + tmp = sanitize(dbi, va_arg(ap1, const char *)); + if (tmp == NULL) { + goto fail; + } + + result = additem(&arglist, &tmp, &len); + if (result != ISC_R_SUCCESS) { + goto fail; + } + + q = p + 2; + } else { + tmp = strdup(q); + if (tmp == NULL) { + goto fail; + } + + result = additem(&arglist, &tmp, &len); + if (result != ISC_R_SUCCESS) { + goto fail; + } + + break; + } + } + + if (len == 0) { + goto fail; + } + + query = malloc(len + 1); + if (query == NULL) { + goto fail; + } + + *query = '\0'; + for (item = DLZ_LIST_HEAD(arglist); item != NULL; + item = DLZ_LIST_NEXT(item, link)) + { + if (item->arg != NULL) { + strcat(query, item->arg); + } + } + +fail: + va_end(ap1); + + while ((item = DLZ_LIST_HEAD(arglist)) != NULL) { + DLZ_LIST_UNLINK(arglist, item, link); + if (item->arg != NULL) { + free(item->arg); + } + free(item); + } + + if (tmp != NULL) { + free(tmp); + } + if (querystr != NULL) { + free(querystr); + } + + if (dbi != NULL && localdbi) { + dlz_mutex_unlock(&dbi->mutex); + } + + return (query); +} + +/* Does this name end in a dot? */ +static bool +isrelative(const char *s) { + if (s == NULL || s[strlen(s) - 1] == '.') { + return (false); + } + return (true); +} + +/* Return a dot if 's' doesn't already end with one */ +static const char * +dot(const char *s) { + return (isrelative(s) ? "." : ""); +} + +/* + * Generate a full hostname from a (presumably relative) name 'name' + * and a zone name 'zone'; store the result in 'dest' (which must have + * enough space). + */ +static void +fqhn(const char *name, const char *zone, char *dest) { + if (dest == NULL) { + return; + } + + if (strlen(name) == 0 || strcmp(name, "@") == 0) { + sprintf(dest, "%s%s", zone, dot(zone)); + } else { + if (isrelative(name)) { + sprintf(dest, "%s.%s%s", name, zone, dot(zone)); + } else { + strcpy(dest, name); + } + } +} + +/* + * Names are stored in relative form in ZoneData; this function + * removes labels matching 'zone' from the end of 'name'. + */ +static char * +relname(const char *name, const char *zone) { + size_t nlen, zlen; + const char *p; + char *new; + + new = (char *)malloc(strlen(name) + 1); + if (new == NULL) { + return (NULL); + } + + nlen = strlen(name); + zlen = strlen(zone); + + if (nlen < zlen) { + strcpy(new, name); + return (new); + } else if (nlen == zlen || strcasecmp(name, zone) == 0) { + strcpy(new, "@"); + return (new); + } + + p = name + nlen - zlen; + if (strcasecmp(p, zone) != 0 && + (zone[zlen - 1] != '.' || strncasecmp(p, zone, zlen - 1) != 0)) + { + strcpy(new, name); + return (new); + } + + strncpy(new, name, nlen - zlen); + new[nlen - zlen - 1] = '\0'; + return (new); +} + +static isc_result_t +validate_txn(mysql_data_t *state, mysql_transaction_t *txn) { + isc_result_t result = ISC_R_FAILURE; + mysql_transaction_t *txp; + + dlz_mutex_lock(&state->tx_mutex); + for (txp = state->transactions; txp != NULL; txp = txp->next) { + if (txn == txp) { + result = ISC_R_SUCCESS; + break; + } + } + dlz_mutex_unlock(&state->tx_mutex); + + if (result != ISC_R_SUCCESS && state->log != NULL) { + state->log(ISC_LOG_ERROR, "%s: invalid txn %x", modname, txn); + } + + return (result); +} + +static isc_result_t +db_execute(mysql_data_t *state, mysql_instance_t *dbi, const char *query) { + int ret; + + /* Make sure this instance is connected. */ + if (!db_connect(state, dbi)) { + return (ISC_R_FAILURE); + } + + ret = mysql_real_query(dbi->sock, query, strlen(query)); + if (ret != 0) { + if (state->debug && state->log != NULL) { + state->log(ISC_LOG_ERROR, "%s: query '%s' failed: %s", + modname, query, mysql_error(dbi->sock)); + } + return (ISC_R_FAILURE); + } + + if (state->debug && state->log != NULL) { + state->log(ISC_LOG_INFO, "%s: execute(%d) %s", modname, dbi->id, + query); + } + + return (ISC_R_SUCCESS); +} + +static MYSQL_RES * +db_query(mysql_data_t *state, mysql_instance_t *dbi, const char *query) { + isc_result_t result; + bool localdbi = false; + MYSQL_RES *res = NULL; + + if (query == NULL) { + return (NULL); + } + + /* Get a DB instance if needed */ + if (dbi == NULL) { + dbi = get_dbi(state); + if (dbi == NULL) { + return (NULL); + } + localdbi = true; + } + + /* Make sure this instance is connected */ + if (!db_connect(state, dbi)) { + goto fail; + } + + result = db_execute(state, dbi, query); + if (result != ISC_R_SUCCESS) { + goto fail; + } + + res = mysql_store_result(dbi->sock); + if (res == NULL) { + if (state->log != NULL) { + state->log(ISC_LOG_ERROR, + "%s: unable to store result: %s", modname, + mysql_error(dbi->sock)); + } + goto fail; + } + + if (state->debug && state->log != NULL) { + state->log(ISC_LOG_INFO, "%s: query(%d) returned %d rows", + modname, dbi->id, mysql_num_rows(res)); + } + +fail: + if (dbi != NULL && localdbi) { + dlz_mutex_unlock(&dbi->mutex); + } + return (res); +} + +/* + * Generate a DNS NOTIFY packet: + * 12 bytes header + * Question (1) + * strlen(zone) +2 + * 2 bytes qtype + * 2 bytes qclass + * + * -> 18 bytes + strlen(zone) + * + * N.B. Need to be mindful of byte ordering; using htons to map 16bit + * values to the 'on the wire' packet values. + */ +static unsigned char * +make_notify(const char *zone, int *packetlen) { + int i, j; + unsigned char *packet = (unsigned char *)malloc(strlen(zone) + 18); + + if (packet == NULL) { + return (NULL); + } + + *packetlen = strlen(zone) + 18; + memset(packet, 0, *packetlen); + + /* Random query ID */ + i = rand(); + packet[0] = htons(i) & 0xff; + packet[1] = htons(i) >> 8; + + /* Flags (OpCode '4' in bits 14-11), Auth Answer set in bit 10 */ + i = 0x2400; + packet[2] = htons(i) & 0xff; + packet[3] = htons(i) >> 8; + + /* QD Count */ + i = 0x1; + packet[4] = htons(i) & 0xff; + packet[5] = htons(i) >> 8; + + /* Question */ + packet[12] = '.'; + memmove(&packet[13], zone, strlen(zone)); + packet[13 + strlen(zone)] = 0; + + /* Make the question into labels */ + j = 12; + while (packet[j]) { + for (i = j + 1; packet[i] != '\0' && packet[i] != '.'; i++) { + ; + } + packet[j] = i - j - 1; + j = i; + } + + /* Question type */ + i = 6; + packet[j + 1] = htons(i) & 0xff; + packet[j + 2] = htons(i) >> 8; + + /* Queston class */ + i = 1; + packet[j + 3] = htons(i) & 0xff; + packet[j + 4] = htons(i) >> 8; + + return (packet); +} + +static void +send_notify(struct sockaddr_in *addr, const unsigned char *p, const int plen) { + int s; + + addr->sin_family = AF_INET; + addr->sin_port = htons(53); + + if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { + return; + } + + sendto(s, p, plen, 0, (struct sockaddr *)addr, sizeof(*addr)); + close(s); + return; +} + +/* + * Generate and send a DNS NOTIFY packet + */ +static void +notify(mysql_data_t *state, const char *zone, int sn) { + MYSQL_RES *res; + MYSQL_ROW row; + char *query; + unsigned char *packet; + int packetlen; + struct ifaddrs *ifap, *ifa; + char zaddr[INET_ADDRSTRLEN]; + void *addrp = NULL; + + /* Get the name servers from the NS rrset */ + query = build_query(state, NULL, Q_GETNS, zone); + res = db_query(state, NULL, query); + free(query); + if (res == NULL) { + return; + } + + /* Create a DNS NOTIFY packet */ + packet = make_notify(zone, &packetlen); + if (packet == NULL) { + mysql_free_result(res); + return; + } + + /* Get a list of our own addresses */ + if (getifaddrs(&ifap) < 0) { + ifap = NULL; + } + + /* Tell each nameserver of the update */ + while ((row = mysql_fetch_row(res)) != NULL) { + bool local = false; + struct hostent *h; + struct sockaddr_in addr, *sin; + + /* + * Put nameserver rdata through gethostbyname as it + * might be an IP address or a hostname. (XXX: switch + * this to inet_pton/getaddrinfo.) + */ + h = gethostbyname(row[0]); + if (h == NULL) { + continue; + } + + memmove(&addr.sin_addr, h->h_addr, h->h_length); + addrp = &addr.sin_addr; + + /* Get the address for the nameserver into a string */ + inet_ntop(AF_INET, addrp, zaddr, INET_ADDRSTRLEN); + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { + char ifaddr[INET_ADDRSTRLEN]; + + if (ifa->ifa_addr->sa_family != AF_INET) { + continue; + } + + /* Get local address into a string */ + sin = (struct sockaddr_in *)ifa->ifa_addr; + addrp = &sin->sin_addr; + inet_ntop(AF_INET, addrp, ifaddr, INET_ADDRSTRLEN); + + /* See if nameserver address matches this one */ + if (strcmp(ifaddr, zaddr) == 0) { + local = true; + } + } + + if (!local) { + if (state->log != NULL) { + state->log(ISC_LOG_INFO, + "%s: notify %s zone %s serial %d", + modname, row[0], zone, sn); + } + send_notify(&addr, packet, packetlen); + } + } + + mysql_free_result(res); + free(packet); + if (ifap != NULL) { + freeifaddrs(ifap); + } +} + +/* + * Constructs a mysql_record_t structure from 'rdatastr', to be + * used in the dlz_{add,sub,del}rdataset functions below. + */ +static mysql_record_t * +makerecord(mysql_data_t *state, const char *name, const char *rdatastr) { + mysql_record_t *new_record; + char *real_name = NULL, *dclass = NULL, *type = NULL; + char *data = NULL, *ttlstr = NULL, *buf = NULL; + char *saveptr = NULL; + dns_ttl_t ttlvalue; + + new_record = (mysql_record_t *)malloc(sizeof(mysql_record_t)); + + if (new_record == NULL) { + if (state->log != NULL) { + state->log(ISC_LOG_ERROR, + "%s: makerecord - unable to malloc", + modname); + } + return (NULL); + } + + buf = strdup(rdatastr); + if (buf == NULL) { + if (state->log != NULL) { + state->log(ISC_LOG_ERROR, + "%s: makerecord - unable to malloc", + modname); + } + free(new_record); + return (NULL); + } + + /* + * 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 + */ + real_name = strtok_r(buf, "\t", &saveptr); + if (real_name == NULL) { + goto error; + } + + ttlstr = strtok_r(NULL, "\t", &saveptr); + if (ttlstr == NULL || sscanf(ttlstr, "%d", &ttlvalue) != 1) { + 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; + } + + strcpy(new_record->name, name); + strcpy(new_record->type, type); + strcpy(new_record->data, data); + sprintf(new_record->ttl, "%d", ttlvalue); + + free(buf); + return (new_record); + +error: + free(buf); + free(new_record); + return (NULL); +} + +/* + * Remember a helper function from the bind9 dlz_dlopen driver + */ +static void +b9_add_helper(mysql_data_t *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; + } +} + +/* + * DLZ API functions + */ + +/* + * Return the version of the API + */ +int +dlz_version(unsigned int *flags) { + UNUSED(flags); + *flags |= DNS_SDLZFLAG_THREADSAFE; + return (DLZ_DLOPEN_VERSION); +} + +/* + * Called to initialize the driver + */ +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], void **dbdata, + ...) { + mysql_data_t *state; + const char *helper_name; + va_list ap; + int n; + + UNUSED(dlzname); + + state = calloc(1, sizeof(mysql_data_t)); + if (state == NULL) { + return (ISC_R_NOMEMORY); + } + + dlz_mutex_init(&state->tx_mutex, NULL); + state->transactions = NULL; + + /* 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 (state->log != NULL) { + state->log(ISC_LOG_INFO, "loading %s module", modname); + } + + if ((argc < 2) || (argc > 6)) { + if (state->log != NULL) { + state->log(ISC_LOG_ERROR, + "%s: missing args <dbname> " + "[<dbhost> [<user> <pass>]]", + modname); + } + dlz_destroy(state); + return (ISC_R_FAILURE); + } + + state->db_name = strdup(argv[1]); + if (argc > 2) { + state->db_host = strdup(argv[2]); + if (argc > 4) { + state->db_user = strdup(argv[3]); + state->db_pass = strdup(argv[4]); + } else { + state->db_user = strdup("bind"); + state->db_pass = strdup(""); + } + } else { + state->db_host = strdup("localhost"); + state->db_user = strdup("bind"); + state->db_pass = strdup(""); + } + + if (state->log != NULL) { + state->log(ISC_LOG_INFO, "%s: DB=%s, Host=%s, User=%s", modname, + state->db_name, state->db_host, state->db_user); + } + + /* + * Assign the 'state' to dbdata so we get it in our callbacks + */ + + dlz_mutex_lock(&state->tx_mutex); + + /* + * Populate DB instances + */ + if (mysql_thread_safe()) { + for (n = 0; n < MAX_DBI; n++) { + dlz_mutex_init(&state->db[n].mutex, NULL); + dlz_mutex_lock(&state->db[n].mutex); + state->db[n].id = n; + state->db[n].connected = 0; + state->db[n].sock = mysql_init(NULL); + mysql_options(state->db[n].sock, + MYSQL_READ_DEFAULT_GROUP, modname); + mysql_options(state->db[n].sock, MYSQL_OPT_RECONNECT, + &(my_bool){ 1 }); + dlz_mutex_unlock(&state->db[n].mutex); + } + + *dbdata = state; + dlz_mutex_unlock(&state->tx_mutex); + return (ISC_R_SUCCESS); + } + + free(state->db_name); + free(state->db_host); + free(state->db_user); + free(state->db_pass); + dlz_mutex_destroy(&state->tx_mutex); + free(state); + return (ISC_R_FAILURE); +} + +/* + * Shut down the backend + */ +void +dlz_destroy(void *dbdata) { + mysql_data_t *state = (mysql_data_t *)dbdata; + int i; + + if (state->debug && state->log != NULL) { + state->log(ISC_LOG_INFO, "%s: shutting down", modname); + } + + for (i = 0; i < MAX_DBI; i++) { + if (state->db[i].sock) { + mysql_close(state->db[i].sock); + state->db[i].sock = NULL; + } + } + free(state->db_name); + free(state->db_host); + free(state->db_user); + free(state->db_pass); + dlz_mutex_destroy(&state->tx_mutex); + 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) { + UNUSED(methods); + UNUSED(clientinfo); + isc_result_t result = ISC_R_SUCCESS; + mysql_data_t *state = (mysql_data_t *)dbdata; + MYSQL_RES *res; + char *query; + + /* Query the Zones table to see if this zone is present */ + query = build_query(state, NULL, Q_FINDZONE, name); + + if (query == NULL) { + return (ISC_R_NOMEMORY); + } + + res = db_query(state, NULL, query); + if (res == NULL) { + return (ISC_R_FAILURE); + } + + if (mysql_num_rows(res) == 0) { + result = ISC_R_NOTFOUND; + } + + mysql_free_result(res); + return (result); +} + +/* + * Perform a database lookup + */ +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) { + UNUSED(methods); + UNUSED(clientinfo); + isc_result_t result; + mysql_data_t *state = (mysql_data_t *)dbdata; + bool found = false; + char *real_name; + MYSQL_RES *res; + MYSQL_ROW row; + char *query; + mysql_transaction_t *txn = NULL; + mysql_instance_t *dbi = NULL; + + if (state->putrr == NULL) { + if (state->log != NULL) { + state->log(ISC_LOG_ERROR, "%s: dlz_lookup - no putrr", + modname); + } + return (ISC_R_NOTIMPLEMENTED); + } + + /* Are we okay to try to find the txn version? */ + if (clientinfo != NULL && clientinfo->version >= 2) { + txn = (mysql_transaction_t *)clientinfo->dbversion; + if (txn != NULL && validate_txn(state, txn) == ISC_R_SUCCESS) { + dbi = txn->dbi; + } + if (dbi != NULL) { + state->log(ISC_LOG_DEBUG(1), + "%s: lookup in live transaction %p, DBI %p", + modname, txn, dbi); + } + } + + if (strcmp(name, "@") == 0) { + real_name = (char *)malloc(strlen(zone) + 1); + if (real_name == NULL) { + return (ISC_R_NOMEMORY); + } + strcpy(real_name, zone); + } else { + real_name = (char *)malloc(strlen(name) + 1); + if (real_name == NULL) { + return (ISC_R_NOMEMORY); + } + strcpy(real_name, name); + } + + if (strcmp(real_name, zone) == 0) { + /* + * Get the Zones table data for use in the SOA: + * zone admin serial refresh retry expire min + */ + query = build_query(state, dbi, Q_GETSOA, zone); + if (query == NULL) { + free(real_name); + return (ISC_R_NOMEMORY); + } + + res = db_query(state, dbi, query); + free(query); + + if (res == NULL) { + free(real_name); + return (ISC_R_NOTFOUND); + } + + while ((row = mysql_fetch_row(res)) != NULL) { + char host[1024], admin[1024], data[4096]; + int ttl; + + sscanf(row[7], "%d", &ttl); + fqhn(row[0], zone, host); + fqhn(row[1], zone, admin); + + /* zone admin serial refresh retry expire min */ + snprintf(data, sizeof(data), "%s%s %s%s %s %s %s %s %s", + host, dot(host), admin, dot(admin), row[2], + row[3], row[4], row[5], row[6]); + + result = state->putrr(lookup, "soa", ttl, data); + if (result != ISC_R_SUCCESS) { + free(real_name); + mysql_free_result(res); + return (result); + } + } + + mysql_free_result(res); + + /* + * Now we'll get the rest of the apex data + */ + query = build_query(state, dbi, Q_GETAPEX, zone, real_name); + } else { + query = build_query(state, dbi, Q_GETNODE, zone, real_name); + } + + res = db_query(state, dbi, query); + free(query); + + if (res == NULL) { + free(real_name); + return (ISC_R_NOTFOUND); + } + + while ((row = mysql_fetch_row(res)) != NULL) { + int ttl; + sscanf(row[2], "%d", &ttl); + result = state->putrr(lookup, row[0], ttl, row[1]); + if (result != ISC_R_SUCCESS) { + free(real_name); + mysql_free_result(res); + return (result); + } + + found = true; + } + + if (state->debug && state->log != NULL) { + state->log(ISC_LOG_INFO, "%s: dlz_lookup %s/%s/%s - (%d rows)", + modname, name, real_name, zone, mysql_num_rows(res)); + } + + mysql_free_result(res); + free(real_name); + + 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) { + mysql_data_t *state = (mysql_data_t *)dbdata; + + if (state->debug && state->log != NULL) { + state->log(ISC_LOG_INFO, "dlz_allowzonexfr: %s %s", name, + 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) { + isc_result_t result = ISC_R_SUCCESS; + mysql_data_t *state = (mysql_data_t *)dbdata; + MYSQL_RES *res; + MYSQL_ROW row; + char *query; + + UNUSED(zone); + + if (state->debug && (state->log != NULL)) { + state->log(ISC_LOG_INFO, "dlz_allnodes: %s", zone); + } + + if (state->putnamedrr == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + /* + * Get all the ZoneData for this zone + */ + query = build_query(state, NULL, Q_GETALL, zone); + if (query == NULL) { + return (ISC_R_NOMEMORY); + } + + res = db_query(state, NULL, query); + free(query); + if (res == NULL) { + return (ISC_R_NOTFOUND); + } + + while ((row = mysql_fetch_row(res)) != NULL) { + char hostname[1024]; + int ttl; + + sscanf(row[3], "%d", &ttl); + fqhn(row[0], zone, hostname); + result = state->putnamedrr(allnodes, hostname, row[1], ttl, + row[2]); + if (result != ISC_R_SUCCESS) { + break; + } + } + + mysql_free_result(res); + return (result); +} + +/* + * Start a transaction + */ +isc_result_t +dlz_newversion(const char *zone, void *dbdata, void **versionp) { + isc_result_t result = ISC_R_FAILURE; + mysql_data_t *state = (mysql_data_t *)dbdata; + MYSQL_RES *res; + MYSQL_ROW row; + char *query; + char zone_id[16]; + mysql_transaction_t *txn = NULL, *newtx = NULL; + + /* + * Check Zone is writable + */ + query = build_query(state, NULL, Q_WRITEABLE, zone); + if (query == NULL) { + return (ISC_R_NOMEMORY); + } + + res = db_query(state, NULL, query); + free(query); + if (res == NULL) { + return (ISC_R_FAILURE); + } + + if ((row = mysql_fetch_row(res)) == NULL) { + mysql_free_result(res); + return (ISC_R_FAILURE); + } + + strcpy(zone_id, row[0]); + mysql_free_result(res); + + /* + * See if we already have a transaction for this zone + */ + dlz_mutex_lock(&state->tx_mutex); + for (txn = state->transactions; txn != NULL; txn = txn->next) { + if (strcmp(txn->zone, zone) == 0) { + if (state->log != NULL) { + state->log(ISC_LOG_ERROR, + "%s: transaction already " + "started for zone %s", + modname, zone); + } + dlz_mutex_unlock(&state->tx_mutex); + return (ISC_R_FAILURE); + } + } + + /* + * Create new transaction + */ + newtx = (mysql_transaction_t *)calloc(1, sizeof(mysql_transaction_t)); + if (newtx == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + newtx->zone = strdup(zone); + if (newtx->zone == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + newtx->zone_id = strdup(zone_id); + if (newtx->zone_id == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + newtx->dbi = get_dbi(state); + newtx->next = NULL; + + if (newtx->dbi == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + result = db_execute(state, newtx->dbi, "START TRANSACTION"); + if (result != ISC_R_SUCCESS) { + dlz_mutex_unlock(&newtx->dbi->mutex); + goto cleanup; + } + + /* + * Add this tx to front of list + */ + newtx->next = state->transactions; + state->transactions = newtx; + + if (state->debug && (state->log != NULL)) { + state->log(ISC_LOG_INFO, "%s: New tx %x", modname, newtx); + } + +cleanup: + dlz_mutex_unlock(&state->tx_mutex); + if (result == ISC_R_SUCCESS) { + *versionp = (void *)newtx; + } else { + dlz_mutex_unlock(&state->tx_mutex); + if (newtx != NULL) { + if (newtx->zone != NULL) { + free(newtx->zone); + } + if (newtx->zone != NULL) { + free(newtx->zone_id); + } + free(newtx); + } + } + + return (result); +} + +/* + * End a transaction + */ +void +dlz_closeversion(const char *zone, bool commit, void *dbdata, void **versionp) { + isc_result_t result; + mysql_data_t *state = (mysql_data_t *)dbdata; + mysql_transaction_t *txn = (mysql_transaction_t *)*versionp; + mysql_transaction_t *txp; + char *query; + MYSQL_RES *res; + MYSQL_ROW row; + + /* + * Find the transaction + */ + dlz_mutex_lock(&state->tx_mutex); + if (state->transactions == txn) { + /* Tx is first in list; remove it. */ + state->transactions = txn->next; + } else { + txp = state->transactions; + while (txp != NULL) { + if (txp->next != NULL) { + if (txp->next == txn) { + txp->next = txn->next; + break; + } + } + if (txp == txn) { + txp = txn->next; + break; + } + txp = txp->next; + } + } + + /* + * Tidy up + */ + dlz_mutex_unlock(&state->tx_mutex); + *versionp = NULL; + + if (commit) { + int oldsn = 0, newsn = 0; + + /* + * Find out the serial number of the zone out with the + * transaction so we can see if it has incremented or not + */ + query = build_query(state, txn->dbi, Q_GETSERIAL, zone); + if (query == NULL && state->log != NULL) { + state->log(ISC_LOG_ERROR, + "%s: unable to commit transaction %x " + "on zone %s: no memory", + modname, txn, zone); + return; + } + + res = db_query(state, txn->dbi, query); + if (res != NULL) { + while ((row = mysql_fetch_row(res)) != NULL) { + sscanf(row[0], "%d", &oldsn); + } + mysql_free_result(res); + } + + /* + * Commit the transaction to the database + */ + result = db_execute(state, txn->dbi, "COMMIT"); + if (result != ISC_R_SUCCESS && state->log != NULL) { + state->log(ISC_LOG_INFO, + "%s: (%x) commit transaction on zone %s", + modname, txn, zone); + return; + } + + if (state->debug && state->log != NULL) { + state->log(ISC_LOG_INFO, + "%s: (%x) committing transaction " + "on zone %s", + modname, txn, zone); + } + + /* + * Now get the serial number again + */ + query = build_query(state, txn->dbi, Q_GETSERIAL, zone); + res = db_query(state, txn->dbi, query); + free(query); + + if (res != NULL) { + while ((row = mysql_fetch_row(res)) != NULL) { + sscanf(row[0], "%d", &newsn); + } + mysql_free_result(res); + } + + /* + * Look to see if serial numbers have changed + */ + if (newsn > oldsn) { + notify(state, zone, newsn); + } + } else { + result = db_execute(state, txn->dbi, "ROLLBACK"); + if (state->debug && (state->log != NULL)) { + state->log(ISC_LOG_INFO, + "%s: (%x) roll back transaction on zone %s", + modname, txn, zone); + } + } + + /* + * Unlock the mutex for this txn + */ + dlz_mutex_unlock(&txn->dbi->mutex); + + /* + * Free up other structures + */ + free(txn->zone); + free(txn->zone_id); + free(txn); +} + +/* + * Configure a writeable zone + */ +#if DLZ_DLOPEN_VERSION < 3 +isc_result_t +dlz_configure(dns_view_t *view, void *dbdata) +#else /* DLZ_DLOPEN_VERSION >= 3 */ +isc_result_t +dlz_configure(dns_view_t *view, dns_dlzdb_t *dlzdb, void *dbdata) +#endif /* DLZ_DLOPEN_VERSION */ +{ + mysql_data_t *state = (mysql_data_t *)dbdata; + isc_result_t result; + MYSQL_RES *res; + MYSQL_ROW row; + int count; + + /* + * Seed PRNG (used by Notify code) + */ + srand(getpid()); + + if (state->debug && state->log != NULL) { + state->log(ISC_LOG_INFO, "%s: dlz_confgure", modname); + } + + if (state->writeable_zone == NULL) { + if (state->log != NULL) { + state->log(ISC_LOG_ERROR, + "%s: no writeable_zone method available", + modname); + } + return (ISC_R_FAILURE); + } + + /* + * Get a list of Zones (ignore writeable column at this point) + */ + res = db_query(state, NULL, Q_GETZONES); + if (res == NULL) { + return (ISC_R_FAILURE); + } + + count = 0; + while ((row = mysql_fetch_row(res)) != NULL) { + int sn; + sscanf(row[1], "%d", &sn); + notify(state, row[0], sn); + result = state->writeable_zone(view, +#if DLZ_DLOPEN_VERSION >= 3 + dlzdb, +#endif /* if DLZ_DLOPEN_VERSION >= 3 */ + row[0]); + if (result != ISC_R_SUCCESS) { + if (state->log != NULL) { + state->log(ISC_LOG_ERROR, + "%s: failed to configure zone %s", + modname, row[0]); + } + mysql_free_result(res); + return (result); + } + count++; + } + mysql_free_result(res); + + if (state->debug && state->log != NULL) { + state->log(ISC_LOG_INFO, "%s: configured %d zones", modname, + count); + } + 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) { + mysql_data_t *state = (mysql_data_t *)dbdata; + + UNUSED(tcpaddr); + UNUSED(type); + UNUSED(keydatalen); + UNUSED(keydata); + UNUSED(key); + + if (state->debug && state->log != NULL) { + state->log(ISC_LOG_INFO, "%s: allowing update of %s by key %s", + modname, name, signer); + } + return (true); +} + +isc_result_t +dlz_addrdataset(const char *name, const char *rdatastr, void *dbdata, + void *version) { + mysql_data_t *state = (mysql_data_t *)dbdata; + mysql_transaction_t *txn = (mysql_transaction_t *)version; + char *new_name, *query; + mysql_record_t *record; + isc_result_t result; + + if (txn == NULL) { + return (ISC_R_FAILURE); + } + + new_name = relname(name, txn->zone); + if (new_name == NULL) { + return (ISC_R_NOMEMORY); + } + + if (state->debug && (state->log != NULL)) { + state->log(ISC_LOG_INFO, "%s: add (%x) %s (as %s) %s", modname, + version, name, new_name, rdatastr); + } + + record = makerecord(state, new_name, rdatastr); + free(new_name); + if (record == NULL) { + return (ISC_R_FAILURE); + } + + /* Write out data to database */ + if (strcasecmp(record->type, "SOA") != 0) { + query = build_query(state, txn->dbi, I_DATA, txn->zone_id, + record->name, record->type, record->data, + record->ttl); + if (query == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + result = db_execute(state, txn->dbi, query); + free(query); + } else { + /* + * This is an SOA record, so we update: it must exist, + * or we wouldn't have gotten this far. + * SOA: zone admin serial refresh retry expire min + */ + char sn[32]; + sscanf(record->data, "%*s %*s %31s %*s %*s %*s %*s", sn); + query = build_query(state, txn->dbi, U_SERIAL, sn, + txn->zone_id); + if (query == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + result = db_execute(state, txn->dbi, query); + free(query); + } + +cleanup: + free(record); + return (result); +} + +isc_result_t +dlz_subrdataset(const char *name, const char *rdatastr, void *dbdata, + void *version) { + mysql_data_t *state = (mysql_data_t *)dbdata; + mysql_transaction_t *txn = (mysql_transaction_t *)version; + char *new_name, *query; + mysql_record_t *record; + isc_result_t result; + + if (txn == NULL) { + return (ISC_R_FAILURE); + } + + new_name = relname(name, txn->zone); + if (new_name == NULL) { + return (ISC_R_NOMEMORY); + } + + if (state->debug && (state->log != NULL)) { + state->log(ISC_LOG_INFO, "%s: sub (%x) %s %s", modname, version, + name, rdatastr); + } + + record = makerecord(state, new_name, rdatastr); + free(new_name); + if (record == NULL) { + return (ISC_R_FAILURE); + } + /* + * If 'type' isn't 'SOA', delete the records + */ + if (strcasecmp(record->type, "SOA") == 0) { + result = ISC_R_SUCCESS; + } else { + query = build_query(state, txn->dbi, D_RECORD, txn->zone_id, + record->name, record->type, record->data, + record->ttl); + if (query == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + result = db_execute(state, txn->dbi, query); + free(query); + } + +cleanup: + free(record); + return (result); +} + +isc_result_t +dlz_delrdataset(const char *name, const char *type, void *dbdata, + void *version) { + mysql_data_t *state = (mysql_data_t *)dbdata; + mysql_transaction_t *txn = (mysql_transaction_t *)version; + char *new_name, *query; + isc_result_t result; + + if (txn == NULL) { + return (ISC_R_FAILURE); + } + + new_name = relname(name, txn->zone); + if (new_name == NULL) { + return (ISC_R_NOMEMORY); + } + + if (state->debug && (state->log != NULL)) { + state->log(ISC_LOG_INFO, "%s: del (%x) %s %s", modname, version, + name, type); + } + + query = build_query(state, txn->dbi, D_RRSET, txn->zone_id, new_name, + type); + if (query == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + result = db_execute(state, txn->dbi, query); + free(query); + +cleanup: + free(new_name); + return (result); +} diff --git a/contrib/dlz/modules/mysqldyn/testing/README b/contrib/dlz/modules/mysqldyn/testing/README new file mode 100644 index 0000000..862ec6f --- /dev/null +++ b/contrib/dlz/modules/mysqldyn/testing/README @@ -0,0 +1,11 @@ +These files were used for testing on Ubuntu Linux using MySQL + +To set up a test server: +- Install MySQL: sudo apt-get install mysql-server +- Run "mysql --user=USER --password=PASSWORD < dlz.schema" to set up database +- Run "mysql --user=USER --password=PASSWORD < dlz.data" to populate it +- Update named.conf with correct USER and PASSWORD +- Generate a TSIG key: "ddns-confgen -qz example.com" + +To query the database, use "dig -p 5300 @localhost" +To send dynamic updates, use "nsupdate -p 5300 -k ddns.key" diff --git a/contrib/dlz/modules/mysqldyn/testing/dlz.data b/contrib/dlz/modules/mysqldyn/testing/dlz.data new file mode 100644 index 0000000..068ad7a --- /dev/null +++ b/contrib/dlz/modules/mysqldyn/testing/dlz.data @@ -0,0 +1,18 @@ +use BindDB; +insert into `Zones` + ( `id`, `domain`, `host`, `admin`, `serial`, `expire`, + `refresh`, `retry`, `minimum`, `ttl`, `writeable`) VALUES + (1, 'example.com', '@', 'info', 2014040100, 10800, + 7200, 604800, 86400, 86400, 1); + +insert into `ZoneData` + (`id`, `zone_id`, `name`, `type`, `data`) VALUES + ('', 1, '@', 'NS', 'ns1.example.com.'), + ('', 1, '@', 'NS', 'ns2.example.com.'), + ('', 1, '@', 'MX', '10 mail.example.com.'), + ('', 1, '@', 'A', '192.168.0.2'), + ('', 1, '@', 'TXT', '"v=spf1 ip:192.168.0.3 ~all"'), + ('', 1, 'www', 'CNAME', 'example.com.'), + ('', 1, 'mail', 'A', '192.168.0.3'), + ('', 1, 'ns1', 'A', '192.168.1.111'), + ('', 1, 'ns2', 'A', '192.168.1.222'); diff --git a/contrib/dlz/modules/mysqldyn/testing/dlz.schema b/contrib/dlz/modules/mysqldyn/testing/dlz.schema new file mode 100644 index 0000000..a28f912 --- /dev/null +++ b/contrib/dlz/modules/mysqldyn/testing/dlz.schema @@ -0,0 +1,31 @@ +CREATE DATABASE `BindDB` DEFAULT CHARACTER SET latin1; +USE `BindDB`; + +CREATE TABLE `ZoneData` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `zone_id` int(11) NOT NULL, + `name` varchar(128) NOT NULL DEFAULT '', + `type` varchar(16) NOT NULL DEFAULT '', + `data` varchar(128) NOT NULL DEFAULT '', + `ttl` int(11) NOT NULL DEFAULT '86400', + PRIMARY KEY (`id`), + KEY `zone_idx` (`zone_id`), + KEY `name_idx` (`zone_id`, `name`), + KEY `type_idx` (`type`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +CREATE TABLE `Zones` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `domain` varchar(128) NOT NULL DEFAULT '', + `host` varchar(128) NOT NULL DEFAULT '', + `admin` varchar(128) NOT NULL DEFAULT '', + `serial` int(11) NOT NULL DEFAULT '1', + `expire` int(11) NOT NULL DEFAULT '86400', + `refresh` int(11) NOT NULL DEFAULT '86400', + `retry` int(11) NOT NULL DEFAULT '86400', + `minimum` int(11) NOT NULL DEFAULT '86400', + `ttl` int(11) NOT NULL DEFAULT '86400', + `writeable` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `domain_idx` (`domain`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; diff --git a/contrib/dlz/modules/mysqldyn/testing/named.conf b/contrib/dlz/modules/mysqldyn/testing/named.conf new file mode 100644 index 0000000..8131d9c --- /dev/null +++ b/contrib/dlz/modules/mysqldyn/testing/named.conf @@ -0,0 +1,39 @@ +/* + * 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. + */ + +controls { }; + +options { + directory "."; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { any; }; + listen-on-v6 { none; }; + recursion no; +}; + +include "ddns.key"; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-md5; +}; + +controls { + inet 127.0.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +dlz "test" { + database "dlopen ../dlz_mysqldyn_mod.so BindDB localhost root password"; +}; diff --git a/contrib/dlz/modules/perl/Makefile b/contrib/dlz/modules/perl/Makefile new file mode 100644 index 0000000..ec99e00 --- /dev/null +++ b/contrib/dlz/modules/perl/Makefile @@ -0,0 +1,63 @@ +# Copyright 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 https://mozilla.org/MPL/2.0/. + +# Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. +# Copyright (C) John Eaglesham +# +# The development of Dynamically Loadable Zones (DLZ) for Bind 9 was +# conceived and contributed by Rob Butler. +# +# SPDX-License-Identifier: ISC and MPL-2.0 +# +# Permission to use, copy, modify, and distribute this software for any purpose +# with or without fee is hereby granted, provided that the above copyright +# notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# For building the dlz_perl_driver driver we don't use +# the bind9 build structure as the aim is to provide an +# perl_driver that is separable from the bind9 source tree + +CFLAGS += -fPIC -O2 -I../include +FLAGS_PERL ?= perl +LIBNAME = dlz_perl_driver.so + +all: $(LIBNAME) + +dlz_perl_driver.o: dlz_perl_driver.c + $(CC) $(CFLAGS) `${FLAGS_PERL} -MExtUtils::Embed -e ccopts` -c -o dlz_perl_driver.o dlz_perl_driver.c + + +dlz_perl_callback_clientinfo.c: dlz_perl_callback_clientinfo.xs + ${FLAGS_PERL} `${FLAGS_PERL} -MConfig -le 'print $$Config{privlibexp}'`/ExtUtils/xsubpp -prototypes -typemap `${FLAGS_PERL} -MConfig -le 'print $$Config{privlibexp}'`/ExtUtils/typemap dlz_perl_callback_clientinfo.xs > dlz_perl_callback_clientinfo.c + +dlz_perl_callback_clientinfo.o: dlz_perl_callback_clientinfo.c + $(CC) $(CFLAGS) `${FLAGS_PERL} -MExtUtils::Embed -e ccopts` -c -o dlz_perl_callback_clientinfo.o dlz_perl_callback_clientinfo.c + + +dlz_perl_callback.c: dlz_perl_callback.xs + ${FLAGS_PERL} `${FLAGS_PERL} -MConfig -le 'print $$Config{privlibexp}'`/ExtUtils/xsubpp -prototypes -typemap `${FLAGS_PERL} -MConfig -le 'print $$Config{privlibexp}'`/ExtUtils/typemap dlz_perl_callback.xs > dlz_perl_callback.c + +dlz_perl_callback.o: dlz_perl_callback.c + $(CC) $(CFLAGS) `${FLAGS_PERL} -MExtUtils::Embed -e ccopts` -c -o dlz_perl_callback.o dlz_perl_callback.c + + +$(LIBNAME): dlz_perl_driver.o dlz_perl_callback_clientinfo.o dlz_perl_callback.o + $(CC) $(LDFLAGS) -shared -o $(LIBNAME) dlz_perl_driver.o dlz_perl_callback_clientinfo.o dlz_perl_callback.o `${FLAGS_PERL} -MExtUtils::Embed -e ldopts` + +clean: + rm -f dlz_perl_driver.o dlz_perl_driver.so dlz_perl_callback_clientinfo.c dlz_perl_callback_clientinfo.o dlz_perl_callback.c dlz_perl_callback.o + +install: dlz_perl_driver.so + mkdir -p $(DESTDIR)$(libdir) + install dlz_perl_driver.so $(DESTDIR)$(libdir) diff --git a/contrib/dlz/modules/perl/README b/contrib/dlz/modules/perl/README new file mode 100644 index 0000000..324054a --- /dev/null +++ b/contrib/dlz/modules/perl/README @@ -0,0 +1,38 @@ +<!-- +Copyright 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 https://mozilla.org/MPL/2.0/. + +Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. +Copyright (C) John Eaglesham + +The development of Dynamically Loadable Zones (DLZ) for Bind 9 was +conceived and contributed by Rob Butler. + +SPDX-License-Identifier: ISC and MPL-2.0 + +Permission to use, copy, modify, and distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright +notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +--> + + +BIND 9 DLZ Perl module (bind-dlz-tools) + +Written by John Eaglesham <dns@8192.net> + +A dynamically loadable zone (DLZ) plugin embedding a Perl +interpreter in BIND, allowing Perl scripts to be written to +integrate with BIND and serve DNS data. + +More information/updates at http://bind-dlz-tools.sourceforge.net/ diff --git a/contrib/dlz/modules/perl/dlz_perl_callback.xs b/contrib/dlz/modules/perl/dlz_perl_callback.xs new file mode 100644 index 0000000..0b9f504 --- /dev/null +++ b/contrib/dlz/modules/perl/dlz_perl_callback.xs @@ -0,0 +1,88 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 and 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 https://mozilla.org/MPL/2.0/. + */ + +/* + * Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. + * Copyright (C) John Eaglesham + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" +#include "dlz_perl_driver.h" + +#include <dlz_minimal.h> + +/* And some XS code. */ +MODULE = DLZ_Perl PACKAGE = DLZ_Perl + +int +LOG_INFO() + CODE: + RETVAL = ISC_LOG_INFO; + OUTPUT: + RETVAL + +int +LOG_NOTICE() + CODE: + RETVAL = ISC_LOG_NOTICE; + OUTPUT: + RETVAL + +int +LOG_WARNING() + CODE: + RETVAL = ISC_LOG_WARNING; + OUTPUT: + RETVAL + +int +LOG_ERROR() + CODE: + RETVAL = ISC_LOG_ERROR; + OUTPUT: + RETVAL + +int +LOG_CRITICAL() + CODE: + RETVAL = ISC_LOG_CRITICAL; + OUTPUT: + RETVAL + + +void +log(opaque, level, msg) + IV opaque + int level + char *msg + + PREINIT: + log_t *log = (log_t *) opaque; + + CODE: + log( level, msg ); + diff --git a/contrib/dlz/modules/perl/dlz_perl_callback_clientinfo.xs b/contrib/dlz/modules/perl/dlz_perl_callback_clientinfo.xs new file mode 100644 index 0000000..22aec66 --- /dev/null +++ b/contrib/dlz/modules/perl/dlz_perl_callback_clientinfo.xs @@ -0,0 +1,94 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 and 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 https://mozilla.org/MPL/2.0/. + */ + +/* + * Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. + * Copyright (C) John Eaglesham + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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. + */ + +#define ADDR_BUF_LEN INET6_ADDRSTRLEN + +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" +#include "dlz_perl_driver.h" + +#include <dlz_minimal.h> + +/* And some XS code. */ +MODULE = DLZ_Perl::clientinfo PACKAGE = DLZ_Perl::clientinfo + +PROTOTYPES: DISABLE + +void +sourceip(opaque) + SV *opaque + + PREINIT: + const char *ret; + char addr_buf[ADDR_BUF_LEN]; + int port; + isc_sockaddr_t *src; + dlz_perl_clientinfo_opaque *ci; + I32 wantarray = GIMME_V; + + PPCODE: + if (!SvTRUE(opaque) || !SvIOK(opaque)) XSRETURN_EMPTY; + + /* + * Safe, because Perl guarantees that an IV (the type we + * pass into DLZ functions who pass it here) is able to + * hold a pointer. + */ + ci = (dlz_perl_clientinfo_opaque *) SvIV(opaque); + if (wantarray == G_VOID || ci->methods == NULL || + ci->methods->version - ci->methods->age < + DNS_CLIENTINFOMETHODS_VERSION) + XSRETURN_EMPTY; + + ci->methods->sourceip(ci->clientinfo, &src); + + switch (src->type.sa.sa_family) { + case AF_INET: + port = ntohs(src->type.sin.sin_port); + ret = inet_ntop(AF_INET, + &src->type.sin.sin_addr, + addr_buf, ADDR_BUF_LEN); + break; + case AF_INET6: + port = ntohs(src->type.sin6.sin6_port); + ret = inet_ntop(AF_INET6, + &src->type.sin6.sin6_addr, + addr_buf, ADDR_BUF_LEN); + break; + default: + ret = NULL; + } + + if (ret == NULL) XSRETURN_EMPTY; + + XPUSHs(sv_2mortal(newSVpv(addr_buf, strlen(addr_buf)))); + if (wantarray == G_ARRAY) XPUSHs(sv_2mortal(newSViv(port))); + diff --git a/contrib/dlz/modules/perl/dlz_perl_driver.c b/contrib/dlz/modules/perl/dlz_perl_driver.c new file mode 100644 index 0000000..b99b296 --- /dev/null +++ b/contrib/dlz/modules/perl/dlz_perl_driver.c @@ -0,0 +1,715 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 and 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 https://mozilla.org/MPL/2.0/. + */ + +/* + * Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. + * Copyright (C) John Eaglesham + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include "dlz_perl_driver.h" +#include <EXTERN.h> +#include <perl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <dlz_minimal.h> + +#define BUF_LEN 64 /* Should be big enough, right? hah */ + +/* Enable debug logging? */ +#if 0 +#define carp(...) cd->log(ISC_LOG_INFO, __VA_ARGS__); +#else /* if 0 */ +#define carp(...) +#endif /* if 0 */ + +#ifndef MULTIPLICITY +/* This is a pretty terrible work-around for handling HUP/rndc reconfig, but + * the way BIND/DLZ handles reloads causes it to create a second back end + * before removing the first. In the case of a single global interpreter, + * serious problems arise. We can hack around this, but it's much better to do + * it properly and link against a perl compiled with multiplicity. */ +static PerlInterpreter *global_perl = NULL; +static int global_perl_dont_free = 0; +#endif /* ifndef MULTIPLICITY */ + +typedef struct config_data { + PerlInterpreter *perl; + char *perl_source; + SV *perl_class; + + /* Functions given to us by bind9 */ + log_t *log; + dns_sdlz_putrr_t *putrr; + dns_sdlz_putnamedrr_t *putnamedrr; + dns_dlz_writeablezone_t *writeable_zone; +} config_data_t; + +/* Note, this code generates warnings due to lost type qualifiers. This code + * is (almost) verbatim from perlembed, and is known to work correctly despite + * the warnings. + */ +EXTERN_C void xs_init(pTHX); +EXTERN_C void +boot_DynaLoader(pTHX_ CV *cv); +EXTERN_C void +boot_DLZ_Perl__clientinfo(pTHX_ CV *cv); +EXTERN_C void +boot_DLZ_Perl(pTHX_ CV *cv); +EXTERN_C void +xs_init(pTHX) { + const char *file = __FILE__; + dXSUB_SYS; + + /* DynaLoader is a special case */ + newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file); + newXS("DLZ_Perl::clientinfo::bootstrap", boot_DLZ_Perl__clientinfo, + file); + newXS("DLZ_Perl::bootstrap", boot_DLZ_Perl, file); +} + +/* + * methods + */ + +/* + * remember a helper function, from the bind9 dlz_dlopen driver + */ +static void +b9_add_helper(config_data_t *state, const char *helper_name, void *ptr) { + if (strcmp(helper_name, "log") == 0) { + state->log = ptr; + } + if (strcmp(helper_name, "putrr") == 0) { + state->putrr = ptr; + } + if (strcmp(helper_name, "putnamedrr") == 0) { + state->putnamedrr = ptr; + } + if (strcmp(helper_name, "writeable_zone") == 0) { + state->writeable_zone = ptr; + } +} + +int +dlz_version(unsigned int *flags) { + UNUSED(flags); + return (DLZ_DLOPEN_VERSION); +} + +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) { + config_data_t *cd = (config_data_t *)dbdata; + isc_result_t retval; + int rrcount, r; + SV *record_ref; + SV **rr_name; + SV **rr_type; + SV **rr_ttl; + SV **rr_data; +#ifdef MULTIPLICITY + PerlInterpreter *my_perl = cd->perl; +#endif /* ifdef MULTIPLICITY */ + dSP; + + PERL_SET_CONTEXT(cd->perl); + ENTER; + SAVETMPS; + + PUSHMARK(SP); + XPUSHs(cd->perl_class); + XPUSHs(sv_2mortal(newSVpv(zone, 0))); + PUTBACK; + + carp("DLZ Perl: Calling allnodes for zone %s", zone); + rrcount = call_method("allnodes", G_ARRAY | G_EVAL); + carp("DLZ Perl: Call to allnodes returned rrcount of %i", rrcount); + + SPAGAIN; + + if (SvTRUE(ERRSV)) { + (void)POPs; + cd->log(ISC_LOG_ERROR, + "DLZ Perl: allnodes for zone %s died in eval: %s", zone, + SvPV_nolen(ERRSV)); + retval = ISC_R_FAILURE; + goto CLEAN_UP_AND_RETURN; + } + + if (!rrcount) { + retval = ISC_R_NOTFOUND; + goto CLEAN_UP_AND_RETURN; + } + + retval = ISC_R_SUCCESS; + r = 0; + while (r++ < rrcount) { + record_ref = POPs; + if ((!SvROK(record_ref)) || + (SvTYPE(SvRV(record_ref)) != SVt_PVAV)) + { + cd->log(ISC_LOG_ERROR, + "DLZ Perl: allnodes for zone %s " + "returned an invalid value " + "(expected array of arrayrefs)", + zone); + retval = ISC_R_FAILURE; + break; + } + + record_ref = SvRV(record_ref); + + rr_name = av_fetch((AV *)record_ref, 0, 0); + rr_type = av_fetch((AV *)record_ref, 1, 0); + rr_ttl = av_fetch((AV *)record_ref, 2, 0); + rr_data = av_fetch((AV *)record_ref, 3, 0); + + if (rr_name == NULL || rr_type == NULL || rr_ttl == NULL || + rr_data == NULL) + { + cd->log(ISC_LOG_ERROR, + "DLZ Perl: allnodes for zone %s " + "returned an array that was missing data", + zone); + retval = ISC_R_FAILURE; + break; + } + + carp("DLZ Perl: Got record %s/%s = %s", SvPV_nolen(*rr_name), + SvPV_nolen(*rr_type), SvPV_nolen(*rr_data)); + retval = cd->putnamedrr(allnodes, SvPV_nolen(*rr_name), + SvPV_nolen(*rr_type), SvIV(*rr_ttl), + SvPV_nolen(*rr_data)); + if (retval != ISC_R_SUCCESS) { + cd->log(ISC_LOG_ERROR, + "DLZ Perl: putnamedrr in allnodes " + "for zone %s failed with code %i " + "(did lookup return invalid record data?)", + zone, retval); + break; + } + } + +CLEAN_UP_AND_RETURN: + PUTBACK; + FREETMPS; + LEAVE; + + carp("DLZ Perl: Returning from allnodes, r = %i, retval = %i", r, + retval); + + return (retval); +} + +isc_result_t +dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { + config_data_t *cd = (config_data_t *)dbdata; + int r; + isc_result_t retval; +#ifdef MULTIPLICITY + PerlInterpreter *my_perl = cd->perl; +#endif /* ifdef MULTIPLICITY */ + dSP; + + PERL_SET_CONTEXT(cd->perl); + ENTER; + SAVETMPS; + + PUSHMARK(SP); + XPUSHs(cd->perl_class); + XPUSHs(sv_2mortal(newSVpv(name, 0))); + XPUSHs(sv_2mortal(newSVpv(client, 0))); + PUTBACK; + + r = call_method("allowzonexfr", G_SCALAR | G_EVAL); + SPAGAIN; + + if (SvTRUE(ERRSV)) { + /* + * On error there's an undef at the top of the stack. Pop + * it away so we don't leave junk on the stack for the next + * caller. + */ + (void)POPs; + cd->log(ISC_LOG_ERROR, + "DLZ Perl: allowzonexfr died in eval: %s", + SvPV_nolen(ERRSV)); + retval = ISC_R_FAILURE; + } else if (r == 0) { + /* Client returned nothing -- zone not found. */ + retval = ISC_R_NOTFOUND; + } else if (r > 1) { + /* Once again, clean out the stack when possible. */ + while (r--) { + POPi; + } + cd->log(ISC_LOG_ERROR, "DLZ Perl: allowzonexfr returned too " + "many parameters!"); + retval = ISC_R_FAILURE; + } else { + /* + * Client returned true/false -- we're authoritative for + * the zone. + */ + r = POPi; + if (r) { + retval = ISC_R_SUCCESS; + } else { + retval = ISC_R_NOPERM; + } + } + + PUTBACK; + FREETMPS; + LEAVE; + return (retval); +} + +#if DLZ_DLOPEN_VERSION < 3 +isc_result_t +dlz_findzonedb(void *dbdata, const char *name) +#else /* if DLZ_DLOPEN_VERSION < 3 */ +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif /* if DLZ_DLOPEN_VERSION < 3 */ +{ + config_data_t *cd = (config_data_t *)dbdata; + int r; + isc_result_t retval; +#ifdef MULTIPLICITY + PerlInterpreter *my_perl = cd->perl; +#endif /* ifdef MULTIPLICITY */ + +#if DLZ_DLOPEN_VERSION >= 3 + UNUSED(methods); + UNUSED(clientinfo); +#endif /* if DLZ_DLOPEN_VERSION >= 3 */ + + dSP; + carp("DLZ Perl: findzone looking for '%s'", name); + + PERL_SET_CONTEXT(cd->perl); + ENTER; + SAVETMPS; + + PUSHMARK(SP); + XPUSHs(cd->perl_class); + XPUSHs(sv_2mortal(newSVpv(name, 0))); + PUTBACK; + + r = call_method("findzone", G_SCALAR | G_EVAL); + SPAGAIN; + + if (SvTRUE(ERRSV)) { + /* + * On error there's an undef at the top of the stack. Pop + * it away so we don't leave junk on the stack for the next + * caller. + */ + (void)POPs; + cd->log(ISC_LOG_ERROR, "DLZ Perl: findzone died in eval: %s", + SvPV_nolen(ERRSV)); + retval = ISC_R_FAILURE; + } else if (r == 0) { + retval = ISC_R_FAILURE; + } else if (r > 1) { + /* Once again, clean out the stack when possible. */ + while (r--) { + POPi; + } + cd->log(ISC_LOG_ERROR, "DLZ Perl: findzone returned too many " + "parameters!"); + retval = ISC_R_FAILURE; + } else { + r = POPi; + if (r) { + retval = ISC_R_SUCCESS; + } else { + retval = ISC_R_NOTFOUND; + } + } + + PUTBACK; + FREETMPS; + LEAVE; + return (retval); +} + +#if DLZ_DLOPEN_VERSION == 1 +isc_result_t +dlz_lookup(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup) +#else /* if DLZ_DLOPEN_VERSION == 1 */ +isc_result_t +dlz_lookup(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif /* if DLZ_DLOPEN_VERSION == 1 */ +{ + isc_result_t retval; + config_data_t *cd = (config_data_t *)dbdata; + int rrcount, r; + dlz_perl_clientinfo_opaque opaque; + SV *record_ref; + SV **rr_type; + SV **rr_ttl; + SV **rr_data; +#ifdef MULTIPLICITY + PerlInterpreter *my_perl = cd->perl; +#endif /* ifdef MULTIPLICITY */ + +#if DLZ_DLOPEN_VERSION >= 2 + UNUSED(methods); + UNUSED(clientinfo); +#endif /* if DLZ_DLOPEN_VERSION >= 2 */ + + dSP; + PERL_SET_CONTEXT(cd->perl); + ENTER; + SAVETMPS; + + opaque.methods = methods; + opaque.clientinfo = clientinfo; + + PUSHMARK(SP); + XPUSHs(cd->perl_class); + XPUSHs(sv_2mortal(newSVpv(name, 0))); + XPUSHs(sv_2mortal(newSVpv(zone, 0))); + XPUSHs(sv_2mortal(newSViv((IV)&opaque))); + PUTBACK; + + carp("DLZ Perl: Searching for name %s in zone %s", name, zone); + rrcount = call_method("lookup", G_ARRAY | G_EVAL); + carp("DLZ Perl: Call to lookup returned %i", rrcount); + + SPAGAIN; + + if (SvTRUE(ERRSV)) { + (void)POPs; + cd->log(ISC_LOG_ERROR, "DLZ Perl: lookup died in eval: %s", + SvPV_nolen(ERRSV)); + retval = ISC_R_FAILURE; + goto CLEAN_UP_AND_RETURN; + } + + if (!rrcount) { + retval = ISC_R_NOTFOUND; + goto CLEAN_UP_AND_RETURN; + } + + retval = ISC_R_SUCCESS; + r = 0; + while (r++ < rrcount) { + record_ref = POPs; + if ((!SvROK(record_ref)) || + (SvTYPE(SvRV(record_ref)) != SVt_PVAV)) + { + cd->log(ISC_LOG_ERROR, "DLZ Perl: lookup returned an " + "invalid value (expected array " + "of arrayrefs)!"); + retval = ISC_R_FAILURE; + break; + } + + record_ref = SvRV(record_ref); + + rr_type = av_fetch((AV *)record_ref, 0, 0); + rr_ttl = av_fetch((AV *)record_ref, 1, 0); + rr_data = av_fetch((AV *)record_ref, 2, 0); + + if (rr_type == NULL || rr_ttl == NULL || rr_data == NULL) { + cd->log(ISC_LOG_ERROR, + "DLZ Perl: lookup for record %s in " + "zone %s returned an array that was " + "missing data", + name, zone); + retval = ISC_R_FAILURE; + break; + } + + carp("DLZ Perl: Got record %s = %s", SvPV_nolen(*rr_type), + SvPV_nolen(*rr_data)); + retval = cd->putrr(lookup, SvPV_nolen(*rr_type), SvIV(*rr_ttl), + SvPV_nolen(*rr_data)); + + if (retval != ISC_R_SUCCESS) { + cd->log(ISC_LOG_ERROR, + "DLZ Perl: putrr for lookup of %s in " + "zone %s failed with code %i " + "(did lookup return invalid record data?)", + name, zone, retval); + break; + } + } + +CLEAN_UP_AND_RETURN: + PUTBACK; + FREETMPS; + LEAVE; + + carp("DLZ Perl: Returning from lookup, r = %i, retval = %i", r, retval); + + return (retval); +} + +static const char * +#ifdef MULTIPLICITY +missing_perl_method(const char *perl_class_name, PerlInterpreter *my_perl) +#else /* ifdef MULTIPLICITY */ +missing_perl_method(const char *perl_class_name) +#endif /* ifdef MULTIPLICITY */ +{ + char full_name[BUF_LEN]; + const char *methods[] = { "new", "findzone", "lookup", NULL }; + int i = 0; + + while (methods[i] != NULL) { + snprintf(full_name, BUF_LEN, "%s::%s", perl_class_name, + methods[i]); + + if (get_cv(full_name, 0) == NULL) { + return (methods[i]); + } + i++; + } + + return (NULL); +} + +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], void **dbdata, + ...) { + config_data_t *cd; + char *perlrun[] = { (char *)"", NULL, (char *)"dlz perl", NULL }; + char *perl_class_name; + int r; + va_list ap; + const char *helper_name; + const char *missing_method_name; + char *call_argv_args = NULL; +#ifdef MULTIPLICITY + PerlInterpreter *my_perl; +#endif /* ifdef MULTIPLICITY */ + + cd = malloc(sizeof(config_data_t)); + if (cd == NULL) { + return (ISC_R_NOMEMORY); + } + + memset(cd, 0, sizeof(config_data_t)); + + /* fill in the helper functions */ + va_start(ap, dbdata); + while ((helper_name = va_arg(ap, const char *)) != NULL) { + b9_add_helper(cd, helper_name, va_arg(ap, void *)); + } + va_end(ap); + + if (argc < 2) { + cd->log(ISC_LOG_ERROR, + "DLZ Perl '%s': Missing script argument.", dlzname); + free(cd); + return (ISC_R_FAILURE); + } + + if (argc < 3) { + cd->log(ISC_LOG_ERROR, + "DLZ Perl '%s': Missing class name argument.", dlzname); + free(cd); + return (ISC_R_FAILURE); + } + perl_class_name = argv[2]; + + cd->log(ISC_LOG_INFO, "DLZ Perl '%s': Loading '%s' from location '%s'", + dlzname, perl_class_name, argv[1], argc); + +#ifndef MULTIPLICITY + if (global_perl) { + /* + * PERL_SET_CONTEXT not needed here as we're guaranteed to + * have an implicit context thanks to an undefined + * MULTIPLICITY. + */ + PL_perl_destruct_level = 1; + perl_destruct(global_perl); + perl_free(global_perl); + global_perl = NULL; + global_perl_dont_free = 1; + } +#endif /* ifndef MULTIPLICITY */ + + cd->perl = perl_alloc(); + if (cd->perl == NULL) { + free(cd); + return (ISC_R_FAILURE); + } +#ifdef MULTIPLICITY + my_perl = cd->perl; +#endif /* ifdef MULTIPLICITY */ + PERL_SET_CONTEXT(cd->perl); + + /* + * We will re-create the interpreter during an rndc reconfig, so we + * must set this variable per perlembed in order to insure we can + * clean up Perl at a later time. + */ + PL_perl_destruct_level = 1; + perl_construct(cd->perl); + PL_exit_flags |= PERL_EXIT_DESTRUCT_END; + /* Prevent crashes from clients writing to $0 */ + PL_origalen = 1; + + cd->perl_source = strdup(argv[1]); + if (cd->perl_source == NULL) { + free(cd); + return (ISC_R_NOMEMORY); + } + + perlrun[1] = cd->perl_source; + if (perl_parse(cd->perl, xs_init, 3, perlrun, (char **)NULL)) { + cd->log(ISC_LOG_ERROR, + "DLZ Perl '%s': Failed to parse Perl script, aborting", + dlzname); + goto CLEAN_UP_PERL_AND_FAIL; + } + + /* Let Perl know about our callbacks. */ + call_argv("DLZ_Perl::clientinfo::bootstrap", G_DISCARD | G_NOARGS, + &call_argv_args); + call_argv("DLZ_Perl::bootstrap", G_DISCARD | G_NOARGS, &call_argv_args); + + /* + * Run the script. We don't really need to do this since we have + * the init callback, but there's not really a downside either. + */ + if (perl_run(cd->perl)) { + cd->log(ISC_LOG_ERROR, + "DLZ Perl '%s': Script exited with an error, aborting", + dlzname); + goto CLEAN_UP_PERL_AND_FAIL; + } + +#ifdef MULTIPLICITY + if ((missing_method_name = missing_perl_method(perl_class_name, + my_perl))) +#else /* ifdef MULTIPLICITY */ + if ((missing_method_name = missing_perl_method(perl_class_name))) +#endif /* ifdef MULTIPLICITY */ + { + cd->log(ISC_LOG_ERROR, + "DLZ Perl '%s': Missing required function '%s', " + "aborting", + dlzname, missing_method_name); + goto CLEAN_UP_PERL_AND_FAIL; + } + + dSP; + ENTER; + SAVETMPS; + + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(perl_class_name, 0))); + + /* Build flattened hash of config info. */ + XPUSHs(sv_2mortal(newSVpv("log_context", 0))); + XPUSHs(sv_2mortal(newSViv((IV)cd->log))); + + /* Argument to pass to new? */ + if (argc == 4) { + XPUSHs(sv_2mortal(newSVpv("argv", 0))); + XPUSHs(sv_2mortal(newSVpv(argv[3], 0))); + } + + PUTBACK; + + r = call_method("new", G_EVAL | G_SCALAR); + + SPAGAIN; + + if (r) { + cd->perl_class = SvREFCNT_inc(POPs); + } + + PUTBACK; + FREETMPS; + LEAVE; + + if (SvTRUE(ERRSV)) { + (void)POPs; + cd->log(ISC_LOG_ERROR, "DLZ Perl '%s': new died in eval: %s", + dlzname, SvPV_nolen(ERRSV)); + goto CLEAN_UP_PERL_AND_FAIL; + } + + if (!r || !sv_isobject(cd->perl_class)) { + cd->log(ISC_LOG_ERROR, + "DLZ Perl '%s': new failed to return a blessed object", + dlzname); + goto CLEAN_UP_PERL_AND_FAIL; + } + + *dbdata = cd; + +#ifndef MULTIPLICITY + global_perl = cd->perl; +#endif /* ifndef MULTIPLICITY */ + return (ISC_R_SUCCESS); + +CLEAN_UP_PERL_AND_FAIL: + PL_perl_destruct_level = 1; + perl_destruct(cd->perl); + perl_free(cd->perl); + free(cd->perl_source); + free(cd); + return (ISC_R_FAILURE); +} + +void +dlz_destroy(void *dbdata) { + config_data_t *cd = (config_data_t *)dbdata; +#ifdef MULTIPLICITY + PerlInterpreter *my_perl = cd->perl; +#endif /* ifdef MULTIPLICITY */ + + cd->log(ISC_LOG_INFO, "DLZ Perl: Unloading driver."); + +#ifndef MULTIPLICITY + if (!global_perl_dont_free) { +#endif /* ifndef MULTIPLICITY */ + PERL_SET_CONTEXT(cd->perl); + PL_perl_destruct_level = 1; + perl_destruct(cd->perl); + perl_free(cd->perl); +#ifndef MULTIPLICITY + global_perl_dont_free = 0; + global_perl = NULL; + } +#endif /* ifndef MULTIPLICITY */ + + free(cd->perl_source); + free(cd); +} diff --git a/contrib/dlz/modules/perl/dlz_perl_driver.h b/contrib/dlz/modules/perl/dlz_perl_driver.h new file mode 100644 index 0000000..2cfb24d --- /dev/null +++ b/contrib/dlz/modules/perl/dlz_perl_driver.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 and 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 https://mozilla.org/MPL/2.0/. + */ + +/* + * Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. + * Copyright (C) John Eaglesham + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include <dlz_minimal.h> + +/* This is the only part that differs from dlz_minimal.h. */ +typedef struct dlz_perl_clientinfo_opaque { + dns_clientinfomethods_t *methods; + dns_clientinfo_t *clientinfo; +} dlz_perl_clientinfo_opaque; diff --git a/contrib/dlz/modules/perl/testing/dlz_perl_example.pm b/contrib/dlz/modules/perl/testing/dlz_perl_example.pm new file mode 100644 index 0000000..eafa87c --- /dev/null +++ b/contrib/dlz/modules/perl/testing/dlz_perl_example.pm @@ -0,0 +1,185 @@ +# Copyright 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 https://mozilla.org/MPL/2.0/. + +# Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. +# Copyright (C) John Eaglesham +# +# The development of Dynamically Loadable Zones (DLZ) for Bind 9 was +# conceived and contributed by Rob Butler. +# +# SPDX-License-Identifier: ISC and MPL-2.0 +# +# Permission to use, copy, modify, and distribute this software for any purpose +# with or without fee is hereby granted, provided that the above copyright +# notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +package dlz_perl_example; + +use warnings; +use strict; + +use Data::Dumper; +$Data::Dumper::Sortkeys = 1; + +# Constructor. Everything after the class name can be folded into a hash of +# various options and settings. Right now only log_context and argv are +# available. +sub new { + my ( $class, %config ) = @_; + my $self = {}; + bless $self, $class; + + $self->{log} = sub { + my ( $level, $msg ) = @_; + DLZ_Perl::log( $config{log_context}, $level, $msg ); + }; + + if ( $config{argv} ) { warn "Got argv: $config{argv}\n"; } + + $self->{zones} = { + 'example.com' => { + '@' => [ + { + type => 'SOA', + ttl => 86400, + data => + 'ns1.example.com. hostmaster.example.com. 12345 172800 900 1209600 3600', + } + ], + perlrr => [ + { + type => 'A', + ttl => 444, + data => '1.1.1.1', + }, + { + type => 'A', + ttl => 444, + data => '1.1.1.2', + } + ], + perltime => [ + { + code => sub { + return ['TXT', '1', time()]; + }, + }, + ], + sourceip => [ + { + code => sub { + my ( $opaque ) = @_; + # Passing anything other than the proper opaque value, + # 0, or undef to this function will cause a crash (at + # best!). + my ( $addr, $port ) = + DLZ_Perl::clientinfo::sourceip( $opaque ); + if ( !$addr ) { $addr = $port = 'unknown'; } + return ['TXT', '1', $addr], ['TXT', '1', $port]; + }, + }, + ], + }, + }; + + $self->{log}->( + DLZ_Perl::LOG_INFO(), + 'DLZ Perl Script: Called init. Loaded zone data: ' + . Dumper( $self->{zones} ) + ); + return $self; +} + +# Do we have data for this zone? Expects a simple true or false return value. +sub findzone { + my ( $self, $zone ) = @_; + $self->{log}->( + DLZ_Perl::LOG_INFO(), + "DLZ Perl Script: Called findzone, looking for zone $zone" + ); + + return exists $self->{zones}->{$zone}; +} + +# Return the data for a given record in a given zone. The final parameter is +# an opaque value that can be passed to DLZ_Perl::clientinfo::sourceip to +# retrieve the client source IP and port. Expected return value is an array +# of array refs, with each array ref representing one record and containing +# the type, ttl, and data in that order. Data is as it appears in a zone file. +sub lookup { + my ( $self, $name, $zone, $client_info ) = @_; + $self->{log}->( + DLZ_Perl::LOG_INFO(), + "DLZ Perl Script: Called lookup, looking for record $name in zone $zone" + ); + return unless $self->{zones}->{$zone}->{$name}; + + my @results; + foreach my $rr ( @{ $self->{zones}->{$zone}->{$name} } ) { + if ( $rr->{'code'} ) { + my @r = $rr->{'code'}->( $client_info ); + if ( @r ) { + push @results, @r; + } + } else { + push @results, [$rr->{'type'}, $rr->{'ttl'}, $rr->{'data'}]; + } + } + + return @results; +} + +# Will we allow zone transfer for this client? Expects a simple true or false +# return value. +sub allowzonexfr { + my ( $self, $zone, $client ) = @_; + $self->{log}->( + DLZ_Perl::LOG_INFO(), + "DLZ Perl Script: Called allowzonexfr, looking for zone $zone for " . + "client $client" + ); + if ( $client eq '127.0.0.1' ) { return 1; } + return 0; +} + +# Note the return AoA for this method differs from lookup in that it must +# return the name of the record as well as the other data. +sub allnodes { + my ( $self, $zone ) = @_; + my @results; + $self->{log}->( + DLZ_Perl::LOG_INFO(), + "DLZ Perl Script: Called allnodes, looking for zone $zone" + ); + + foreach my $name ( keys %{ $self->{zones}->{$zone} } ) { + foreach my $rr ( @{ $self->{zones}->{$zone}->{$name} } ) { + if ( $rr->{'code'} ) { + my @r = $rr->{'code'}->(); + # The code returns an array of array refs without the name. + # This makes things easy for lookup but hard here. We must + # iterate over each array ref and inject the name into it. + foreach my $a ( @r ) { + unshift @{$a}, $name; + } + push @results, @r; + } else { + push @results, + [$name, $rr->{'type'}, $rr->{'ttl'}, $rr->{'data'}]; + } + } + } + return @results; +} + +1; diff --git a/contrib/dlz/modules/perl/testing/named.conf b/contrib/dlz/modules/perl/testing/named.conf new file mode 100644 index 0000000..293d655 --- /dev/null +++ b/contrib/dlz/modules/perl/testing/named.conf @@ -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. + */ + +options { + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { 127.0.0.1; }; + listen-on-v6 { none; }; + recursion no; + notify no; +}; + +dlz "perl zone" { + database "dlopen ../dlz_perl_driver.so dlz_perl_example.pm dlz_perl_example"; +}; diff --git a/contrib/dlz/modules/sqlite3/Makefile b/contrib/dlz/modules/sqlite3/Makefile new file mode 100644 index 0000000..b7861de --- /dev/null +++ b/contrib/dlz/modules/sqlite3/Makefile @@ -0,0 +1,46 @@ +# Copyright 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 https://mozilla.org/MPL/2.0/. + +# Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. +# +# The development of Dynamically Loadable Zones (DLZ) for Bind 9 was +# conceived and contributed by Rob Butler. +# +# SPDX-License-Identifier: ISC and MPL-2.0 +# +# Permission to use, copy, modify, and distribute this software for any purpose +# with or without fee is hereby granted, provided that the above copyright +# notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +prefix = /usr +libdir = $(prefix)/lib/bind9 + +CFLAGS=-fPIC -g -I../include +SQLITE3_LIBS=-lsqlite3 + +all: dlz_sqlite3_dynamic.so + +dlz_dbi.o: ../common/dlz_dbi.c + $(CC) $(CFLAGS) -c ../common/dlz_dbi.c + +dlz_sqlite3_dynamic.so: dlz_sqlite3_dynamic.c dlz_dbi.o + $(CC) $(CFLAGS) -shared -o dlz_sqlite3_dynamic.so \ + dlz_sqlite3_dynamic.c dlz_dbi.o $(SQLITE3_LIBS) + +clean: + rm -f dlz_sqlite3_dynamic.so *.o + +install: dlz_sqlite3_dynamic.so + mkdir -p $(DESTDIR)$(libdir) + install dlz_sqlite3_dynamic.so $(DESTDIR)$(libdir) diff --git a/contrib/dlz/modules/sqlite3/dlz_sqlite3_dynamic.c b/contrib/dlz/modules/sqlite3/dlz_sqlite3_dynamic.c new file mode 100644 index 0000000..8a920ff --- /dev/null +++ b/contrib/dlz/modules/sqlite3/dlz_sqlite3_dynamic.c @@ -0,0 +1,1093 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 and 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 https://mozilla.org/MPL/2.0/. + */ + +/* + * Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This provides the externally loadable SQLitee DLZ module, without + * update support. Based in part on SQLite code contributed by Tim Tessier. + */ + +#include <sqlite3.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <dlz_dbi.h> +#include <dlz_list.h> +#include <dlz_minimal.h> +#include <dlz_pthread.h> + +#define dbc_search_limit 30 +#define ALLNODES 1 +#define ALLOWXFR 2 +#define AUTHORITY 3 +#define FINDZONE 4 +#define COUNTZONE 5 +#define LOOKUP 6 + +#define safeGet(in) in == NULL ? "" : in + +/*% + * Structure to hold everything needed by this "instance" of the SQLite3 + * module remember, the module code is only loaded once, but may have + * many separate instances. + */ +typedef struct { +#if PTHREADS + db_list_t *db; /*%< handle to a list of DB */ + int dbcount; +#else /* if PTHREADS */ + dbinstance_t *db; /*%< handle to DB */ +#endif /* if PTHREADS */ + + char *dbname; + + /* 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; +} sqlite3_instance_t; + +/* + * SQLite3 result set + */ +typedef struct { + char **pazResult; /* Result of the query */ + int pnRow; /* Number of result rows */ + int pnColumn; /* Number of result columns */ + int curRow; /* Current row */ + char *pzErrmsg; /* Error message */ +} sqlite3_res_t; + +/* forward references */ +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); + +void +dlz_destroy(void *dbdata); + +static void +b9_add_helper(sqlite3_instance_t *db, const char *helper_name, void *ptr); + +/* + * Private methods + */ + +void +sqlite3_destroy(dbinstance_t *db) { + /* release DB connection */ + if (db->dbconn != NULL) { + sqlite3_close((sqlite3 *)db->dbconn); + } + sqlite3_shutdown(); + + /* destroy DB instance */ + destroy_dbinstance(db); +} + +#if PTHREADS +/*% + * Properly cleans up a list of database instances. + * This function is only used when the module is compiled for + * multithreaded operation. + */ +static void +sqlite3_destroy_dblist(db_list_t *dblist) { + dbinstance_t *ndbi = NULL; + dbinstance_t *dbi = NULL; + + ndbi = DLZ_LIST_HEAD(*dblist); + while (ndbi != NULL) { + dbi = ndbi; + ndbi = DLZ_LIST_NEXT(dbi, link); + + sqlite3_destroy(dbi); + } + + /* release memory for the list structure */ + free(dblist); +} + +/*% + * Loops through the list of DB instances, attempting to lock + * on the mutex. If successful, the DBI is reserved for use + * and the thread can perform queries against the database. + * If the lock fails, the next one in the list is tried. + * looping continues until a lock is obtained, or until + * the list has been searched dbc_search_limit times. + * This function is only used when the module is compiled for + * multithreaded operation. + */ +static dbinstance_t * +sqlite3_find_avail(sqlite3_instance_t *sqlite3) { + dbinstance_t *dbi = NULL, *head; + int count = 0; + + /* get top of list */ + head = dbi = DLZ_LIST_HEAD(*(sqlite3->db)); + + /* loop through list */ + while (count < dbc_search_limit) { + /* try to lock on the mutex */ + if (dlz_mutex_trylock(&dbi->lock) == 0) { + return (dbi); /* success, return the DBI for use. */ + } + /* not successful, keep trying */ + dbi = DLZ_LIST_NEXT(dbi, link); + + /* check to see if we have gone to the top of the list. */ + if (dbi == NULL) { + count++; + dbi = head; + } + } + + sqlite3->log(ISC_LOG_INFO, + "SQLite3 module: unable to find available connection " + "after searching %d times", + count); + return (NULL); +} +#endif /* PTHREADS */ + +/*% + * Allocates memory for a new string, and then constructs the new + * string by "escaping" the input string. The new string is + * safe to be used in queries. This is necessary because we cannot + * be sure of what types of strings are passed to us, and we don't + * want special characters in the string causing problems. + */ +static char * +escape_string(const char *instr) { + char *outstr; + char *ptr; + unsigned int len; + unsigned int tlen = 0; + unsigned int atlen = 0; + unsigned int i; + + if (instr == NULL) { + return (NULL); + } + len = strlen(instr); + atlen = (2 * len * sizeof(char)) + 1; + outstr = malloc(atlen); + if (outstr == NULL) { + return (NULL); + } + + ptr = outstr; + for (i = 0; i < len; i++) { + if (tlen > atlen || instr[i] == '\0') { + break; + } + + if (instr[i] == '\'') { + *ptr++ = '\''; + tlen++; + } + + *ptr++ = instr[i]; + tlen++; + } + *ptr = '\0'; + + return (outstr); +} + +/*% + * This function is the real core of the module. Zone, record + * and client strings are passed in (or NULL is passed if the + * string is not available). The type of query we want to run + * is indicated by the query flag, and the dbdata object is passed + * passed in too. dbdata really holds a single database instance. + * The function will construct and run the query, hopefully getting + * a result set. + */ +static isc_result_t +sqlite3_get_resultset(const char *zone, const char *record, const char *client, + unsigned int query, void *dbdata, sqlite3_res_t **rsp) { + isc_result_t result; + dbinstance_t *dbi = NULL; + sqlite3_instance_t *db = (sqlite3_instance_t *)dbdata; + char *querystring = NULL; + sqlite3_res_t *rs = NULL; + int qres = 0; + + if ((query == COUNTZONE && rsp != NULL) || + (query != COUNTZONE && (rsp == NULL || *rsp != NULL))) + { + db->log(ISC_LOG_DEBUG(2), "Invalid result set pointer."); + result = ISC_R_FAILURE; + goto cleanup; + } + +#if PTHREADS + /* find an available DBI from the list */ + dbi = sqlite3_find_avail(db); +#else /* PTHREADS */ + /* + * only 1 DBI - no need to lock instance lock either + * only 1 thread in the whole process, no possible contention. + */ + dbi = (dbinstance_t *)(db->db); +#endif /* PTHREADS */ + + if (dbi == NULL) { + return (ISC_R_FAILURE); + } + + /* what type of query are we going to run? */ + switch (query) { + case ALLNODES: + if (dbi->allnodes_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case ALLOWXFR: + if (dbi->allowxfr_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case AUTHORITY: + if (dbi->authority_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case FINDZONE: + if (dbi->findzone_q == NULL) { + db->log(ISC_LOG_DEBUG(2), "No query specified for " + "findzone. " + "Findzone requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } + break; + case COUNTZONE: + if (dbi->countzone_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case LOOKUP: + if (dbi->lookup_q == NULL) { + db->log(ISC_LOG_DEBUG(2), "No query specified for " + "lookup. " + "Lookup requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } + break; + default: + db->log(ISC_LOG_ERROR, "Incorrect query flag passed to " + "sqlite3_get_resultset"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + if (zone != NULL) { + if (dbi->zone != NULL) { + free(dbi->zone); + } + + dbi->zone = escape_string(zone); + if (dbi->zone == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { + dbi->zone = NULL; + } + + if (record != NULL) { + if (dbi->record != NULL) { + free(dbi->record); + } + + dbi->record = escape_string(record); + if (dbi->record == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { + dbi->record = NULL; + } + + if (client != NULL) { + if (dbi->client != NULL) { + free(dbi->client); + } + + dbi->client = escape_string(client); + if (dbi->client == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { + dbi->client = NULL; + } + + /* + * what type of query are we going to run? this time we build + * the actual query to run. + */ + switch (query) { + case ALLNODES: + querystring = build_querystring(dbi->allnodes_q); + break; + case ALLOWXFR: + querystring = build_querystring(dbi->allowxfr_q); + break; + case AUTHORITY: + querystring = build_querystring(dbi->authority_q); + break; + case FINDZONE: + querystring = build_querystring(dbi->findzone_q); + break; + case COUNTZONE: + querystring = build_querystring(dbi->countzone_q); + break; + case LOOKUP: + querystring = build_querystring(dbi->lookup_q); + break; + default: + db->log(ISC_LOG_ERROR, "Incorrect query flag passed to " + "sqlite3_get_resultset"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* output the full query string when debugging */ + db->log(ISC_LOG_DEBUG(1), "\nQuery String: %s\n", querystring); + + rs = malloc(sizeof(sqlite3_res_t)); + if (rs == NULL) { + db->log(ISC_LOG_ERROR, "Failed to allocate result set"); + result = ISC_R_NOMEMORY; + goto cleanup; + } + memset(rs, 0, sizeof(sqlite3_res_t)); + + qres = sqlite3_get_table(dbi->dbconn, querystring, &rs->pazResult, + &rs->pnRow, &rs->pnColumn, &rs->pzErrmsg); + if (qres != SQLITE_OK) { + db->log(ISC_LOG_DEBUG(1), "SQLite3 query failed; %s", + rs->pzErrmsg != NULL ? rs->pzErrmsg : "unknown error"); + sqlite3_free(rs->pzErrmsg); + rs->pzErrmsg = NULL; + result = ISC_R_FAILURE; + goto cleanup; + } + + result = ISC_R_SUCCESS; + if (query == COUNTZONE) { + sqlite3_free_table(rs->pazResult); + } + + if (rsp != NULL) { + *rsp = rs; + } + +cleanup: + if (dbi->zone != NULL) { + free(dbi->zone); + dbi->zone = NULL; + } + if (dbi->record != NULL) { + free(dbi->record); + dbi->record = NULL; + } + if (dbi->client != NULL) { + free(dbi->client); + dbi->client = NULL; + } + + /* release the lock so another thread can use this dbi */ + (void)dlz_mutex_unlock(&dbi->lock); + + if (querystring != NULL) { + free(querystring); + } + + return (result); +} + +/*% + * The processing of result sets for lookup and authority are + * exactly the same. So that functionality has been moved + * into this function to minimize code. + */ + +char ** +sqlite3_fetch_row(sqlite3_res_t *rs) { + char **retval = NULL; + if (rs != NULL) { + if (rs->pnRow > 0 && rs->curRow < rs->pnRow) { + int index = (rs->curRow + 1) * rs->pnColumn; + retval = &rs->pazResult[index]; + rs->curRow++; + } + } + return (retval); +} + +unsigned int +sqlite3_num_fields(sqlite3_res_t *rs) { + unsigned int retval = 0; + if (rs != NULL) { + retval = rs->pnColumn; + } + return (retval); +} + +unsigned int +sqlite3_num_rows(sqlite3_res_t *rs) { + unsigned int retval = 0; + if (rs != NULL) { + retval = rs->pnRow; + } + return (retval); +} + +void +sqlite3_free_result(sqlite3_res_t *rs) { + if (rs != NULL) { + sqlite3_free_table(rs->pazResult); + free(rs); + } +} + +static isc_result_t +sqlite3_process_rs(sqlite3_instance_t *db, dns_sdlzlookup_t *lookup, + sqlite3_res_t *rs) { + isc_result_t result = ISC_R_NOTFOUND; + char **row; + unsigned int fields; + unsigned int j; + char *tmpString; + char *endp; + int ttl; + + row = sqlite3_fetch_row(rs); /* get a row from the result set */ + fields = sqlite3_num_fields(rs); /* how many columns in result set */ + while (row != NULL) { + unsigned int len = 0; + + switch (fields) { + case 1: + /* + * one column in rs, it's the data field. use + * default type of A record, and default TTL + * of 86400 + */ + result = db->putrr(lookup, "a", 86400, safeGet(row[0])); + break; + case 2: + /* + * two columns, data field, and data type. + * use default TTL of 86400. + */ + result = db->putrr(lookup, safeGet(row[0]), 86400, + safeGet(row[1])); + break; + case 3: + /* + * three columns, all data no defaults. + * convert text to int, make sure it worked + * right. + */ + ttl = strtol(safeGet(row[0]), &endp, 10); + if (*endp != '\0' || ttl < 0) { + db->log(ISC_LOG_ERROR, "SQLite3 module: TTL " + "must be " + "a positive number"); + return (ISC_R_FAILURE); + } + + result = db->putrr(lookup, safeGet(row[1]), ttl, + safeGet(row[2])); + break; + default: + /* + * more than 3 fields, concatenate the last + * ones together. figure out how long to make + * string. + */ + for (j = 2; j < fields; j++) { + len += strlen(safeGet(row[j])) + 1; + } + + /* + * allocate string memory, allow for NULL to + * term string + */ + tmpString = malloc(len + 1); + if (tmpString == NULL) { + db->log(ISC_LOG_ERROR, "SQLite3 module: unable " + "to allocate " + "memory for temporary " + "string"); + sqlite3_free_result(rs); + return (ISC_R_FAILURE); + } + + strcpy(tmpString, safeGet(row[2])); + for (j = 3; j < fields; j++) { + strcat(tmpString, " "); + strcat(tmpString, safeGet(row[j])); + } + + ttl = strtol(safeGet(row[0]), &endp, 10); + if (*endp != '\0' || ttl < 0) { + db->log(ISC_LOG_ERROR, "SQLite3 module: TTL " + "must be " + "a positive number"); + free(tmpString); + return (ISC_R_FAILURE); + } + + result = db->putrr(lookup, safeGet(row[1]), ttl, + tmpString); + free(tmpString); + } + + if (result != ISC_R_SUCCESS) { + sqlite3_free_result(rs); + db->log(ISC_LOG_ERROR, "putrr returned error: %d", + result); + return (ISC_R_FAILURE); + } + + row = sqlite3_fetch_row(rs); + } + + sqlite3_free_result(rs); + return (result); +} + +/* + * DLZ methods + */ + +/*% determine if the zone is supported by (in) the database */ +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + isc_result_t result; + sqlite3_res_t *rs = NULL; + sqlite3_uint64 rows; + sqlite3_instance_t *db = (sqlite3_instance_t *)dbdata; + + UNUSED(methods); + UNUSED(clientinfo); + + result = sqlite3_get_resultset(name, NULL, NULL, FINDZONE, dbdata, &rs); + if (result != ISC_R_SUCCESS || rs == NULL) { + if (rs != NULL) { + sqlite3_free_result(rs); + } + + db->log(ISC_LOG_ERROR, "SQLite3 module: unable to return " + "result set for FINDZONE query"); + + return (ISC_R_FAILURE); + } + + /* + * if we returned any rows, the zone is supported. + */ + rows = sqlite3_num_rows(rs); + sqlite3_free_result(rs); + if (rows > 0) { + sqlite3_get_resultset(name, NULL, NULL, COUNTZONE, dbdata, + NULL); + return (ISC_R_SUCCESS); + } + + return (ISC_R_NOTFOUND); +} + +/*% Determine if the client is allowed to perform a zone transfer */ +isc_result_t +dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { + isc_result_t result; + sqlite3_instance_t *db = (sqlite3_instance_t *)dbdata; + sqlite3_res_t *rs = NULL; + sqlite3_uint64 rows; + + /* first check if the zone is supported by the database. */ + result = dlz_findzonedb(dbdata, name, NULL, NULL); + if (result != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + /* + * if we get to this point we know the zone is supported by + * the database the only questions now are is the zone + * transfer is allowed for this client and did the config file + * have an allow zone xfr query. + */ + result = sqlite3_get_resultset(name, NULL, client, ALLOWXFR, dbdata, + &rs); + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + + if (result != ISC_R_SUCCESS || rs == NULL) { + if (rs != NULL) { + sqlite3_free_result(rs); + } + db->log(ISC_LOG_ERROR, "SQLite3 module: unable to return " + "result set for ALLOWXFR query"); + return (ISC_R_FAILURE); + } + + /* + * count how many rows in result set; if we returned any, + * zone xfr is allowed. + */ + rows = sqlite3_num_rows(rs); + sqlite3_free_result(rs); + if (rows > 0) { + return (ISC_R_SUCCESS); + } + + return (ISC_R_NOPERM); +} + +/*% + * If the client is allowed to perform a zone transfer, the next order of + * business is to get all the nodes in the zone, so bind can respond to the + * query. + */ +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) { + isc_result_t result; + sqlite3_instance_t *db = (sqlite3_instance_t *)dbdata; + sqlite3_res_t *rs = NULL; + char **row; + unsigned int fields; + unsigned int j; + char *tmpString; + char *endp; + int ttl; + + result = sqlite3_get_resultset(zone, NULL, NULL, ALLNODES, dbdata, &rs); + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS) { + db->log(ISC_LOG_ERROR, "SQLite3 module: unable to return " + "result set for all nodes query"); + goto cleanup; + } + + result = ISC_R_NOTFOUND; + + fields = sqlite3_num_fields(rs); + row = sqlite3_fetch_row(rs); + while (row != NULL) { + if (fields < 4) { + db->log(ISC_LOG_ERROR, "SQLite3 module: too few fields " + "returned " + "by ALLNODES query"); + result = ISC_R_FAILURE; + goto cleanup; + } + + ttl = strtol(safeGet(row[0]), &endp, 10); + if (*endp != '\0' || ttl < 0) { + db->log(ISC_LOG_ERROR, "SQLite3 module: TTL must be " + "a positive number"); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (fields == 4) { + result = db->putnamedrr(allnodes, safeGet(row[2]), + safeGet(row[1]), ttl, + safeGet(row[3])); + } else { + unsigned int len = 0; + + /* + * more than 4 fields, concatenate the last + * ones together. + */ + for (j = 3; j < fields; j++) { + len += strlen(safeGet(row[j])) + 1; + } + + tmpString = malloc(len + 1); + if (tmpString == NULL) { + db->log(ISC_LOG_ERROR, "SQLite3 module: unable " + "to allocate " + "memory for temporary " + "string"); + result = ISC_R_FAILURE; + goto cleanup; + } + + strcpy(tmpString, safeGet(row[3])); + for (j = 4; j < fields; j++) { + strcat(tmpString, " "); + strcat(tmpString, safeGet(row[j])); + } + + result = db->putnamedrr(allnodes, safeGet(row[2]), + safeGet(row[1]), ttl, + tmpString); + free(tmpString); + } + + if (result != ISC_R_SUCCESS) { + db->log(ISC_LOG_ERROR, "putnamedrr returned error: %s", + result); + result = ISC_R_FAILURE; + break; + } + + row = sqlite3_fetch_row(rs); + } + +cleanup: + if (rs != NULL) { + sqlite3_free_result(rs); + } + + return (result); +} + +/*% + * If the lookup function does not return SOA or NS records for the zone, + * use this function to get that information for named. + */ +isc_result_t +dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup) { + isc_result_t result; + sqlite3_res_t *rs = NULL; + sqlite3_instance_t *db = (sqlite3_instance_t *)dbdata; + + result = sqlite3_get_resultset(zone, NULL, NULL, AUTHORITY, dbdata, + &rs); + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + + if (result != ISC_R_SUCCESS) { + if (rs != NULL) { + sqlite3_free_result(rs); + } + db->log(ISC_LOG_ERROR, "SQLite3 module: unable to return " + "result set for AUTHORITY query"); + return (ISC_R_FAILURE); + } + + /* + * lookup and authority result sets are processed in the same + * manner: sqlite3_process_rs does the job for both functions. + */ + return (sqlite3_process_rs(db, lookup, rs)); +} + +/*% If zone is supported, lookup up a (or multiple) record(s) in it */ +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; + sqlite3_res_t *rs = NULL; + sqlite3_instance_t *db = (sqlite3_instance_t *)dbdata; + + UNUSED(methods); + UNUSED(clientinfo); + + result = sqlite3_get_resultset(zone, name, NULL, LOOKUP, dbdata, &rs); + + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS) { + if (rs != NULL) { + sqlite3_free_result(rs); + } + db->log(ISC_LOG_ERROR, "SQLite3 module: unable to return " + "result set for LOOKUP query"); + return (ISC_R_FAILURE); + } + + /* + * lookup and authority result sets are processed in the same + * manner: sqlite3_process_rs does the job for both functions. + */ + return (sqlite3_process_rs(db, lookup, rs)); +} + +/*% + * Create an instance of the module. + */ +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], void **dbdata, + ...) { + isc_result_t result = ISC_R_FAILURE; + sqlite3_instance_t *s3 = NULL; + dbinstance_t *dbi = NULL; + sqlite3 *dbc = NULL; + char *tmp = NULL; + char *endp; + const char *helper_name; +#if PTHREADS + int dbcount; + int i, ret; +#endif /* PTHREADS */ + va_list ap; + + UNUSED(dlzname); + + /* allocate memory for SQLite3 instance */ + s3 = calloc(1, sizeof(sqlite3_instance_t)); + if (s3 == NULL) { + return (ISC_R_NOMEMORY); + } + memset(s3, 0, sizeof(sqlite3_instance_t)); + + /* Fill in the helper functions */ + va_start(ap, dbdata); + while ((helper_name = va_arg(ap, const char *)) != NULL) { + b9_add_helper(s3, helper_name, va_arg(ap, void *)); + } + va_end(ap); + +#if PTHREADS + /* if debugging, let user know we are multithreaded. */ + s3->log(ISC_LOG_DEBUG(1), "SQLite3 module: running multithreaded"); +#else /* PTHREADS */ + /* if debugging, let user know we are single threaded. */ + s3->log(ISC_LOG_DEBUG(1), "SQLite3 module: running single threaded"); +#endif /* PTHREADS */ + + /* verify we have at least 4 arg's passed to the module */ + if (argc < 4) { + s3->log(ISC_LOG_ERROR, "SQLite3 module requires " + "at least 4 command line args."); + return (ISC_R_FAILURE); + } + + /* no more than 8 arg's should be passed to the module */ + if (argc > 8) { + s3->log(ISC_LOG_ERROR, "SQLite3 module cannot accept " + "more than 8 command line args."); + return (ISC_R_FAILURE); + } + + /* get db name - required */ + s3->dbname = get_parameter_value(argv[1], "dbname="); + if (s3->dbname == NULL) { + s3->log(ISC_LOG_ERROR, "SQLite3 module requires a dbname " + "parameter."); + result = ISC_R_FAILURE; + goto cleanup; + } + +#if PTHREADS + /* multithreaded build can have multiple DB connections */ + tmp = get_parameter_value(argv[1], "threads="); + if (tmp == NULL) { + dbcount = 1; + } else { + dbcount = strtol(tmp, &endp, 10); + if (*endp != '\0' || dbcount < 1) { + s3->log(ISC_LOG_ERROR, "SQLite3 module: database " + "connection count " + "must be positive."); + free(tmp); + result = ISC_R_FAILURE; + goto cleanup; + } + free(tmp); + } + + /* allocate memory for database connection list */ + s3->db = calloc(1, sizeof(db_list_t)); + if (s3->db == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* initialize DB connection list */ + DLZ_LIST_INIT(*(s3->db)); + + /* + * create the appropriate number of database instances (DBI) + * append each new DBI to the end of the list + */ + for (i = 0; i < dbcount; i++) { +#endif /* PTHREADS */ + switch (argc) { + case 4: + result = build_dbinstance(NULL, NULL, NULL, argv[2], + argv[3], NULL, &dbi, s3->log); + break; + case 5: + result = build_dbinstance(NULL, NULL, argv[4], argv[2], + argv[3], NULL, &dbi, s3->log); + break; + case 6: + result = build_dbinstance(argv[5], NULL, argv[4], + argv[2], argv[3], NULL, &dbi, + s3->log); + break; + case 7: + result = build_dbinstance(argv[5], argv[6], argv[4], + argv[2], argv[3], NULL, &dbi, + s3->log); + break; + case 8: + result = build_dbinstance(argv[5], argv[6], argv[4], + argv[2], argv[3], argv[7], + &dbi, s3->log); + break; + default: + result = ISC_R_FAILURE; + } + + if (result != ISC_R_SUCCESS) { + s3->log(ISC_LOG_ERROR, "SQLite3 module: could not " + "create " + "database instance object."); + result = ISC_R_FAILURE; + goto cleanup; + } + + /* create and set db connection */ + ret = sqlite3_initialize(); + if (ret != SQLITE_OK) { + s3->log(ISC_LOG_ERROR, "SQLite3 module: could not " + "initialize database object."); + result = ISC_R_FAILURE; + goto cleanup; + } + + ret = sqlite3_open(s3->dbname, &dbc); + if (ret != SQLITE_OK) { + s3->log(ISC_LOG_ERROR, + "SQLite3 module: could not " + "open '%s'.", + s3->dbname); + result = ISC_R_FAILURE; + goto cleanup; + } + +#if PTHREADS + /* when multithreaded, build a list of DBI's */ + DLZ_LINK_INIT(dbi, link); + DLZ_LIST_APPEND(*(s3->db), dbi, link); +#else /* if PTHREADS */ + /* + * when single threaded, hold onto the one connection + * instance. + */ + s3->db = dbi; +#endif /* if PTHREADS */ + + dbi->dbconn = dbc; + dbc = NULL; +#if PTHREADS + /* set DBI = null for next loop through. */ + dbi = NULL; + } +#endif /* PTHREADS */ + + *dbdata = s3; + return (ISC_R_SUCCESS); + +cleanup: + dlz_destroy(s3); + + return (result); +} + +/*% + * Destroy the module. + */ +void +dlz_destroy(void *dbdata) { + sqlite3_instance_t *db = (sqlite3_instance_t *)dbdata; +#if PTHREADS + /* cleanup the list of DBI's */ + if (db->db != NULL) { + sqlite3_destroy_dblist((db_list_t *)(db->db)); + } +#else /* PTHREADS */ + sqlite3_destroy(db); +#endif /* PTHREADS */ + + if (db->dbname != NULL) { + free(db->dbname); + } +} + +/* + * Return the version of the API + */ +int +dlz_version(unsigned int *flags) { + *flags |= (DNS_SDLZFLAG_RELATIVEOWNER | DNS_SDLZFLAG_RELATIVERDATA | + DNS_SDLZFLAG_THREADSAFE); + return (DLZ_DLOPEN_VERSION); +} + +/* + * Register a helper function from the bind9 dlz_dlopen driver + */ +static void +b9_add_helper(sqlite3_instance_t *db, const char *helper_name, void *ptr) { + if (strcmp(helper_name, "log") == 0) { + db->log = (log_t *)ptr; + } + if (strcmp(helper_name, "putrr") == 0) { + db->putrr = (dns_sdlz_putrr_t *)ptr; + } + if (strcmp(helper_name, "putnamedrr") == 0) { + db->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr; + } + if (strcmp(helper_name, "writeable_zone") == 0) { + db->writeable_zone = (dns_dlz_writeablezone_t *)ptr; + } +} diff --git a/contrib/dlz/modules/sqlite3/testing/README b/contrib/dlz/modules/sqlite3/testing/README new file mode 100644 index 0000000..c7af001 --- /dev/null +++ b/contrib/dlz/modules/sqlite3/testing/README @@ -0,0 +1,10 @@ +These files were used for testing on Ubuntu Linux using SQLite3 + +- Install SQLite3: sudo apt-get install sqlite3 libsqlite3-dev +- Build sqlite3 DLZ module +- Run "sqlite3 BindDB < dlz.schema" to set up database +- Run "sqlite3 BindDB < dlz.data" to populate it +- Run "named -gc named.conf" +- Send test queries, e.g "dig @localhost -p 5300 example.com", + "dig @localhost -p 5300 axfr example.com" (AXFR should be + allowed from 127.0.0.1 only). diff --git a/contrib/dlz/modules/sqlite3/testing/dlz.data b/contrib/dlz/modules/sqlite3/testing/dlz.data new file mode 100644 index 0000000..015607f --- /dev/null +++ b/contrib/dlz/modules/sqlite3/testing/dlz.data @@ -0,0 +1,18 @@ +INSERT INTO `records` +(`zone`, `ttl`, `type`, `host`, `mx_priority`, `data`, `primary_ns`, `resp_contact`, `serial`, `refresh`, `retry`, `expire`, `minimum`) +VALUES +('example.com', 86400, 'SOA', '@', NULL, NULL, 'ns1.example.com.', 'info.example.com.', 2011043001, 10800, 7200, 604800, 86400), +('example.com', 86400, 'NS', '@', NULL, 'ns1.example.com.', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('example.com', 86400, 'NS', '@', NULL, 'ns2.example.com.', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('example.com', 86400, 'MX', '@', 10, 'mail.example.com.', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('example.com', 86400, 'A', '@', NULL, '192.168.0.2', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('example.com', 86400, 'CNAME', 'www', NULL, '@', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('example.com', 86400, 'A', 'ns1', NULL, '192.168.0.111', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('example.com', 86400, 'A', 'ns2', NULL, '192.168.0.222', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('example.com', 86400, 'A', 'mail', NULL, '192.168.0.3', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('example.com', 86400, 'TXT', '@', NULL, 'v=spf1 ip:192.168.0.3 ~all', NULL, NULL, NULL, NULL, NULL, NULL, NULL); + +INSERT INTO `xfr` +(`zone`, `client`) +VALUES +('example.com', '127.0.0.1'); diff --git a/contrib/dlz/modules/sqlite3/testing/dlz.schema b/contrib/dlz/modules/sqlite3/testing/dlz.schema new file mode 100644 index 0000000..4cbcb34 --- /dev/null +++ b/contrib/dlz/modules/sqlite3/testing/dlz.schema @@ -0,0 +1,28 @@ +CREATE TABLE IF NOT EXISTS `records` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `zone` CHAR(255) NOT NULL, + `ttl` INT NOT NULL DEFAULT '86400', + `type` CHAR(255) NOT NULL, + `host` CHAR(255) NOT NULL DEFAULT '@', + `mx_priority` INT DEFAULT NULL, + `data` text, + `primary_ns` CHAR(255) DEFAULT NULL, + `resp_contact` CHAR(255) DEFAULT NULL, + `serial` bigint DEFAULT NULL, + `refresh` INT DEFAULT NULL, + `retry` INT DEFAULT NULL, + `expire` INT DEFAULT NULL, + `minimum` INT DEFAULT NULL +); + +CREATE INDEX IF NOT EXISTS record_type on records (type); +CREATE INDEX IF NOT EXISTS record_host on records (host); +CREATE INDEX IF NOT EXISTS record_zone on records (zone); + +CREATE TABLE IF NOT EXISTS `xfr` ( + `zone` CHAR(255) NOT NULL, + `client` CHAR(255) NOT NULL +); + +CREATE INDEX IF NOT EXISTS xfr_zone on xfr (zone); +CREATE INDEX IF NOT EXISTS xfr_client on xfr (client); diff --git a/contrib/dlz/modules/sqlite3/testing/named.conf b/contrib/dlz/modules/sqlite3/testing/named.conf new file mode 100644 index 0000000..d5ce0bb --- /dev/null +++ b/contrib/dlz/modules/sqlite3/testing/named.conf @@ -0,0 +1,45 @@ +/* + * 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. + */ + +controls { }; + +options { + directory "."; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { any; }; + listen-on-v6 { none; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-md5; +}; + +controls { + inet 127.0.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +dlz "test" { + database "dlopen ../dlz_sqlite3_dynamic.so + { + dbname=BindDB threads=2 + } + {SELECT zone FROM records WHERE zone = '$zone$'} + {SELECT ttl, type, mx_priority, CASE WHEN type = 'TXT' THEN '\"' || data || '\"' ELSE data END AS data FROM records WHERE zone = '$zone$' AND host = '$record$' AND type <> 'SOA' AND type <> 'NS'} + {SELECT ttl, type, data, primary_ns, resp_contact, serial, refresh, retry, expire, minimum FROM records WHERE zone = '$zone$' AND (type = 'SOA' OR type='NS')} + {SELECT ttl, type, host, mx_priority, CASE WHEN type = 'TXT' THEN '\"' || data || '\"' ELSE data END AS data, resp_contact, serial, refresh, retry, expire, minimum FROM records WHERE zone = '$zone$' AND type <> 'SOA' AND type <> 'NS'} + {SELECT zone FROM xfr where zone='$zone$' AND client = '$client$'}"; +}; diff --git a/contrib/dlz/modules/wildcard/Makefile b/contrib/dlz/modules/wildcard/Makefile new file mode 100644 index 0000000..d09a83a --- /dev/null +++ b/contrib/dlz/modules/wildcard/Makefile @@ -0,0 +1,46 @@ +# Copyright 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 https://mozilla.org/MPL/2.0/. + +# Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. +# Copyright (C) Vadim Goncharov, Russia, vadim_nuclight@mail.ru. +# +# The development of Dynamically Loadable Zones (DLZ) for Bind 9 was +# conceived and contributed by Rob Butler. +# +# SPDX-License-Identifier: ISC and MPL-2.0 +# +# Permission to use, copy, modify, and distribute this software for any purpose +# with or without fee is hereby granted, provided that the above copyright +# notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +prefix = /usr +libdir = $(prefix)/lib/bind9 + +CFLAGS=-fPIC -g -I../include + +all: dlz_wildcard_dynamic.so + +dlz_dbi.o: ../common/dlz_dbi.c + $(CC) $(CFLAGS) -c ../common/dlz_dbi.c + +dlz_wildcard_dynamic.so: dlz_wildcard_dynamic.c dlz_dbi.o + $(CC) $(CFLAGS) -shared -o dlz_wildcard_dynamic.so \ + dlz_wildcard_dynamic.c dlz_dbi.o + +clean: + rm -f dlz_wildcard_dynamic.so *.o + +install: dlz_wildcard_dynamic.so + mkdir -p $(DESTDIR)$(libdir) + install dlz_wildcard_dynamic.so $(DESTDIR)$(libdir) diff --git a/contrib/dlz/modules/wildcard/README b/contrib/dlz/modules/wildcard/README new file mode 100644 index 0000000..2ec6852 --- /dev/null +++ b/contrib/dlz/modules/wildcard/README @@ -0,0 +1,59 @@ +<!-- +Copyright 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 https://mozilla.org/MPL/2.0/. + +Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. +Copyright (C) Vadim Goncharov, Russia, vadim_nuclight@mail.ru. + +The development of Dynamically Loadable Zones (DLZ) for Bind 9 was +conceived and contributed by Rob Butler. + +SPDX-License-Identifier: ISC and MPL-2.0 + +Permission to use, copy, modify, and distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright +notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +--> + +The "wildcard" DLZ module provides a "template" zone for domains matching +a wildcard name. For example, the following DLZ configuration would match +any zone name containing the string "example" and ending with .com, such +as "thisexample.com", "exampleofthat.com", or "anexampleoftheotherthing.com". + + dlz "test" { + database "dlopen ../dlz_wildcard_dynamic.so + *example*.com 10.53.* 1800 + @ 3600 SOA {ns3.example.nil. support.example.nil. 42 14400 7200 2592000 600} + @ 3600 NS ns3.example.nil. + @ 3600 NS ns4.example.nil. + @ 3600 NS ns8.example.nil. + @ 3600 MX {5 mail.example.nil.} + ftp 86400 A 192.0.0.1 + sql 86400 A 192.0.0.2 + tmp {} A 192.0.0.3 + www 86400 A 192.0.0.3 + www 86400 AAAA ::1 + txt 300 TXT {\"you requested $record$ in $zone$\"} + * 86400 A 192.0.0.100"; + }; + +For any zone name matching the wildcard, it would return the data from +the template. "$zone$" is replaced with zone name: i.e., the shortest +possible string of labels in the query name that matches the wildcard. +"$record$" is replaced with the remainder of the query name. In the +example above, a query for "txt.thisexample.com/TXT" would return the +string "you requested txt in thisexample.com". + +Any client whose source address matches the second wildcard ("10.53.*") +is allowed to request a zone transfer. diff --git a/contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c b/contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c new file mode 100644 index 0000000..95221a1 --- /dev/null +++ b/contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c @@ -0,0 +1,773 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 and 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 https://mozilla.org/MPL/2.0/. + */ + +/* + * Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. + * Copyright (C) Vadim Goncharov, Russia, vadim_nuclight@mail.ru. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This provides the externally loadable wildcard DLZ module. + */ + +#include <ctype.h> +#include <inttypes.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <dlz_dbi.h> +#include <dlz_list.h> +#include <dlz_minimal.h> + +#define DE_CONST(konst, var) \ + do { \ + union { \ + const void *k; \ + void *v; \ + } _u; \ + _u.k = konst; \ + var = _u.v; \ + } while (0) + +/* fnmatch() return values. */ +#define FNM_NOMATCH 1 /* Match failed. */ + +/* fnmatch() flags. */ +#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */ +#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */ +#define FNM_PERIOD 0x04 /* Period must be matched by period. */ +#define FNM_LEADING_DIR 0x08 /* Ignore /<tail> after Imatch. */ +#define FNM_CASEFOLD 0x10 /* Case insensitive search. */ +#define FNM_IGNORECASE FNM_CASEFOLD +#define FNM_FILE_NAME FNM_PATHNAME + +/* + * Our data structures. + */ + +typedef struct named_rr nrr_t; +typedef DLZ_LIST(nrr_t) rr_list_t; + +typedef struct config_data { + char *zone_pattern; + char *axfr_pattern; + rr_list_t rrs_list; + char *zone; + char *record; + char *client; + + /* Helper functions from the dlz_dlopen driver */ + log_t *log; + dns_sdlz_putrr_t *putrr; + dns_sdlz_putnamedrr_t *putnamedrr; + dns_dlz_writeablezone_t *writeable_zone; +} config_data_t; + +struct named_rr { + char *name; + char *type; + int ttl; + query_list_t *data; + DLZ_LINK(nrr_t) link; +}; + +/* + * Forward references + */ +static int +rangematch(const char *, char, int, char **); + +static int +fnmatch(const char *pattern, const char *string, int flags); + +static void +b9_add_helper(struct config_data *cd, const char *helper_name, void *ptr); + +static const char * +shortest_match(const char *pattern, const char *string); + +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) { + config_data_t *cd = (config_data_t *)dbdata; + isc_result_t result; + char *querystring = NULL; + nrr_t *nrec; + int i = 0; + + DE_CONST(zone, cd->zone); + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), "dlz_wildcard allnodes called for zone '%s'", + zone); + + result = ISC_R_FAILURE; + + nrec = DLZ_LIST_HEAD(cd->rrs_list); + while (nrec != NULL) { + cd->record = nrec->name; + + querystring = build_querystring(nrec->data); + + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto done; + } + + cd->log(ISC_LOG_DEBUG(2), + "dlz_wildcard allnodes entry num %d: calling " + "putnamedrr(name=%s type=%s ttl=%d qs=%s)", + i++, nrec->name, nrec->type, nrec->ttl, querystring); + + result = cd->putnamedrr(allnodes, nrec->name, nrec->type, + nrec->ttl, querystring); + if (result != ISC_R_SUCCESS) { + goto done; + } + + nrec = DLZ_LIST_NEXT(nrec, link); + } + +done: + cd->zone = NULL; + + if (querystring != NULL) { + free(querystring); + } + + return (result); +} + +isc_result_t +dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { + config_data_t *cd = (config_data_t *)dbdata; + + UNUSED(name); + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), + "dlz_wildcard allowzonexfr called for client '%s'", client); + + if (fnmatch(cd->axfr_pattern, client, FNM_CASEFOLD) == 0) { + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +#if DLZ_DLOPEN_VERSION < 3 +isc_result_t +dlz_findzonedb(void *dbdata, const char *name) +#else /* if DLZ_DLOPEN_VERSION < 3 */ +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif /* if DLZ_DLOPEN_VERSION < 3 */ +{ + config_data_t *cd = (config_data_t *)dbdata; + const char *p; + +#if DLZ_DLOPEN_VERSION >= 3 + UNUSED(methods); + UNUSED(clientinfo); +#endif /* if DLZ_DLOPEN_VERSION >= 3 */ + + p = shortest_match(cd->zone_pattern, name); + if (p == NULL) { + return (ISC_R_NOTFOUND); + } + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), "dlz_wildcard findzonedb matched '%s'", p); + + return (ISC_R_SUCCESS); +} + +#if DLZ_DLOPEN_VERSION == 1 +isc_result_t +dlz_lookup(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup) +#else /* if DLZ_DLOPEN_VERSION == 1 */ +isc_result_t +dlz_lookup(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif /* if DLZ_DLOPEN_VERSION == 1 */ +{ + isc_result_t result; + config_data_t *cd = (config_data_t *)dbdata; + char *querystring = NULL; + const char *p; + char *namebuf; + nrr_t *nrec; + +#if DLZ_DLOPEN_VERSION >= 2 + UNUSED(methods); + UNUSED(clientinfo); +#endif /* if DLZ_DLOPEN_VERSION >= 2 */ + + p = shortest_match(cd->zone_pattern, zone); + if (p == NULL) { + return (ISC_R_NOTFOUND); + } + + DE_CONST(name, cd->record); + DE_CONST(p, cd->zone); + + if ((p != zone) && (strcmp(name, "@") == 0 || strcmp(name, zone) == 0)) + { + size_t len = p - zone; + namebuf = malloc(len); + if (namebuf == NULL) { + return (ISC_R_NOMEMORY); + } + strncpy(namebuf, zone, len - 1); + namebuf[len - 1] = '\0'; + cd->record = namebuf; + } else if (p == zone) { + cd->record = (char *)"@"; + } + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), + "dlz_wildcard_dynamic: lookup for '%s' in '%s': " + "trying '%s' in '%s'", + name, zone, cd->record, cd->zone); + + result = ISC_R_NOTFOUND; + nrec = DLZ_LIST_HEAD(cd->rrs_list); + while (nrec != NULL) { + nrr_t *next = DLZ_LIST_NEXT(nrec, link); + if (strcmp(cd->record, nrec->name) == 0) { + /* We handle authority data in dlz_authority() */ + if (strcmp(nrec->type, "SOA") == 0 || + strcmp(nrec->type, "NS") == 0) + { + nrec = next; + continue; + } + + querystring = build_querystring(nrec->data); + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto done; + } + + result = cd->putrr(lookup, nrec->type, nrec->ttl, + querystring); + if (result != ISC_R_SUCCESS) { + goto done; + } + + result = ISC_R_SUCCESS; + + free(querystring); + querystring = NULL; + } + nrec = next; + } + +done: + cd->zone = NULL; + cd->record = NULL; + + if (querystring != NULL) { + free(querystring); + } + + return (result); +} + +isc_result_t +dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup) { + isc_result_t result; + config_data_t *cd = (config_data_t *)dbdata; + char *querystring = NULL; + nrr_t *nrec; + const char *p; + + p = shortest_match(cd->zone_pattern, zone); + if (p == NULL) { + return (ISC_R_NOTFOUND); + } + + DE_CONST(p, cd->zone); + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), "dlz_wildcard_dynamic: authority for '%s'", + zone); + + result = ISC_R_NOTFOUND; + nrec = DLZ_LIST_HEAD(cd->rrs_list); + while (nrec != NULL) { + if (strcmp("@", nrec->name) == 0) { + isc_result_t presult; + + querystring = build_querystring(nrec->data); + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto done; + } + + presult = cd->putrr(lookup, nrec->type, nrec->ttl, + querystring); + if (presult != ISC_R_SUCCESS) { + result = presult; + goto done; + } + + result = ISC_R_SUCCESS; + + free(querystring); + querystring = NULL; + } + nrec = DLZ_LIST_NEXT(nrec, link); + } + +done: + cd->zone = NULL; + + if (querystring != NULL) { + free(querystring); + } + + return (result); +} + +static void +destroy_rrlist(config_data_t *cd) { + nrr_t *trec, *nrec; + + nrec = DLZ_LIST_HEAD(cd->rrs_list); + + while (nrec != NULL) { + trec = nrec; + + destroy_querylist(&trec->data); + + if (trec->name != NULL) { + free(trec->name); + } + if (trec->type != NULL) { + free(trec->type); + } + trec->name = trec->type = NULL; + + /* Get the next record, before we destroy this one. */ + nrec = DLZ_LIST_NEXT(nrec, link); + + free(trec); + } +} + +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], void **dbdata, + ...) { + config_data_t *cd; + char *endp; + unsigned int i; + int def_ttl; + nrr_t *trec = NULL; + isc_result_t result; + const char *helper_name; + va_list ap; + + if (argc < 8 || argc % 4 != 0) { + return (ISC_R_FAILURE); + } + + cd = calloc(1, sizeof(config_data_t)); + if (cd == NULL) { + return (ISC_R_NOMEMORY); + } + memset(cd, 0, sizeof(config_data_t)); + + /* Fill in the helper functions */ + va_start(ap, dbdata); + while ((helper_name = va_arg(ap, const char *)) != NULL) { + b9_add_helper(cd, helper_name, va_arg(ap, void *)); + } + va_end(ap); + + /* + * Write info message to log + */ + cd->log(ISC_LOG_INFO, + "Loading '%s' using DLZ_wildcard driver. " + "Zone: %s, AXFR allowed for: %s, $TTL: %s", + dlzname, argv[1], argv[2], argv[3]); + + /* initialize the records list here to simplify cleanup */ + DLZ_LIST_INIT(cd->rrs_list); + + cd->zone_pattern = strdup(argv[1]); + cd->axfr_pattern = strdup(argv[2]); + if (cd->zone_pattern == NULL || cd->axfr_pattern == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + def_ttl = strtol(argv[3], &endp, 10); + if (*endp != '\0' || def_ttl < 0) { + def_ttl = 3600; + cd->log(ISC_LOG_ERROR, "default TTL invalid, using 3600"); + } + + for (i = 4; i < argc; i += 4) { + result = ISC_R_NOMEMORY; + + trec = malloc(sizeof(nrr_t)); + if (trec == NULL) { + goto full_cleanup; + } + + memset(trec, 0, sizeof(nrr_t)); + + /* Initialize the record link */ + DLZ_LINK_INIT(trec, link); + /* Append the record to the list */ + DLZ_LIST_APPEND(cd->rrs_list, trec, link); + + trec->name = strdup(argv[i]); + if (trec->name == NULL) { + goto full_cleanup; + } + + trec->type = strdup(argv[i + 2]); + if (trec->type == NULL) { + goto full_cleanup; + } + + trec->ttl = strtol(argv[i + 1], &endp, 10); + if (argv[i + 1][0] == '\0' || *endp != '\0' || trec->ttl < 0) { + trec->ttl = def_ttl; + } + + result = build_querylist(argv[i + 3], &cd->zone, &cd->record, + &cd->client, &trec->data, 0, cd->log); + /* If unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + cd->log(ISC_LOG_ERROR, + "Could not build RR data list at argv[%d]", + i + 3); + goto full_cleanup; + } + } + + *dbdata = cd; + + return (ISC_R_SUCCESS); + +full_cleanup: + destroy_rrlist(cd); + +cleanup: + if (cd->zone_pattern != NULL) { + free(cd->zone_pattern); + } + if (cd->axfr_pattern != NULL) { + free(cd->axfr_pattern); + } + free(cd); + + return (result); +} + +void +dlz_destroy(void *dbdata) { + config_data_t *cd = (config_data_t *)dbdata; + + /* + * Write debugging message to log + */ + cd->log(ISC_LOG_DEBUG(2), "Unloading DLZ_wildcard driver."); + + destroy_rrlist(cd); + + free(cd->zone_pattern); + free(cd->axfr_pattern); + free(cd); +} + +/* + * Return the version of the API + */ +int +dlz_version(unsigned int *flags) { + UNUSED(flags); + /* XXX: ok to set DNS_SDLZFLAG_THREADSAFE here? */ + return (DLZ_DLOPEN_VERSION); +} + +/* + * Register a helper function from the bind9 dlz_dlopen driver + */ +static void +b9_add_helper(struct config_data *cd, const char *helper_name, void *ptr) { + if (strcmp(helper_name, "log") == 0) { + cd->log = (log_t *)ptr; + } + if (strcmp(helper_name, "putrr") == 0) { + cd->putrr = (dns_sdlz_putrr_t *)ptr; + } + if (strcmp(helper_name, "putnamedrr") == 0) { + cd->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr; + } + if (strcmp(helper_name, "writeable_zone") == 0) { + cd->writeable_zone = (dns_dlz_writeablezone_t *)ptr; + } +} + +static const char * +shortest_match(const char *pattern, const char *string) { + const char *p = string; + if (pattern == NULL || p == NULL || *p == '\0') { + return (NULL); + } + + p += strlen(p); + while (p-- > string) { + if (*p == '.') { + if (fnmatch(pattern, p + 1, FNM_CASEFOLD) == 0) { + return (p + 1); + } + } + } + if (fnmatch(pattern, string, FNM_CASEFOLD) == 0) { + return (string); + } + + return (NULL); +} + +/* + * The helper functions stolen from the FreeBSD kernel (sys/libkern/fnmatch.c). + * + * Why don't we use fnmatch(3) from libc? Because it is not thread-safe, and + * it is not thread-safe because it supports multibyte characters. But here, + * in BIND, we want to be thread-safe and don't need multibyte - DNS names are + * always ASCII. + */ +#define EOS '\0' + +#define RANGE_MATCH 1 +#define RANGE_NOMATCH 0 +#define RANGE_ERROR (-1) + +static int +fnmatch(const char *pattern, const char *string, int flags) { + const char *stringstart; + char *newp; + char c, test; + + for (stringstart = string;;) { + switch (c = *pattern++) { + case EOS: + if ((flags & FNM_LEADING_DIR) && *string == '/') { + return (0); + } + return (*string == EOS ? 0 : FNM_NOMATCH); + case '?': + if (*string == EOS) { + return (FNM_NOMATCH); + } + if (*string == '/' && (flags & FNM_PATHNAME)) { + return (FNM_NOMATCH); + } + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + { + return (FNM_NOMATCH); + } + ++string; + break; + case '*': + c = *pattern; + /* Collapse multiple stars. */ + while (c == '*') { + c = *++pattern; + } + + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + { + return (FNM_NOMATCH); + } + + /* Optimize for pattern with * at end or before /. */ + if (c == EOS) { + if (flags & FNM_PATHNAME) { + return ((flags & FNM_LEADING_DIR) || + index(string, + '/') == + NULL + ? 0 + : FNM_NOMATCH); + } else { + return (0); + } + } else if (c == '/' && flags & FNM_PATHNAME) { + if ((string = index(string, '/')) == NULL) { + return (FNM_NOMATCH); + } + break; + } + + /* General case, use recursion. */ + while ((test = *string) != EOS) { + if (!fnmatch(pattern, string, + flags & ~FNM_PERIOD)) + { + return (0); + } + if (test == '/' && flags & FNM_PATHNAME) { + break; + } + ++string; + } + return (FNM_NOMATCH); + case '[': + if (*string == EOS) { + return (FNM_NOMATCH); + } + if (*string == '/' && (flags & FNM_PATHNAME)) { + return (FNM_NOMATCH); + } + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + { + return (FNM_NOMATCH); + } + + switch (rangematch(pattern, *string, flags, &newp)) { + case RANGE_ERROR: + goto norm; + case RANGE_MATCH: + pattern = newp; + break; + case RANGE_NOMATCH: + return (FNM_NOMATCH); + } + ++string; + break; + case '\\': + if (!(flags & FNM_NOESCAPE)) { + if ((c = *pattern++) == EOS) { + c = '\\'; + --pattern; + } + } + FALLTHROUGH; + default: + norm: + if (c == *string) { + } else if ((flags & FNM_CASEFOLD) && + (tolower((unsigned char)c) == + tolower((unsigned char)*string))) + { + } else { + return (FNM_NOMATCH); + } + string++; + break; + } + } + UNREACHABLE(); +} + +static int +rangematch(const char *pattern, char test, int flags, char **newp) { + int negate, ok; + char c, c2; + + /* + * A bracket expression starting with an unquoted circumflex + * character produces unspecified results (IEEE 1003.2-1992, + * 3.13.2). This implementation treats it like '!', for + * consistency with the regular expression syntax. + * J.T. Conklin (conklin@ngai.kaleida.com) + */ + if ((negate = (*pattern == '!' || *pattern == '^'))) { + ++pattern; + } + + if (flags & FNM_CASEFOLD) { + test = tolower((unsigned char)test); + } + + /* + * A right bracket shall lose its special meaning and represent + * itself in a bracket expression if it occurs first in the list. + * -- POSIX.2 2.8.3.2 + */ + ok = 0; + c = *pattern++; + do { + if (c == '\\' && !(flags & FNM_NOESCAPE)) { + c = *pattern++; + } + if (c == EOS) { + return (RANGE_ERROR); + } + + if (c == '/' && (flags & FNM_PATHNAME)) { + return (RANGE_NOMATCH); + } + + if (flags & FNM_CASEFOLD) { + c = tolower((unsigned char)c); + } + + if (*pattern == '-' && (c2 = *(pattern + 1)) != EOS && + c2 != ']') + { + pattern += 2; + if (c2 == '\\' && !(flags & FNM_NOESCAPE)) { + c2 = *pattern++; + } + if (c2 == EOS) { + return (RANGE_ERROR); + } + + if (flags & FNM_CASEFOLD) { + c2 = tolower((unsigned char)c2); + } + + if (c <= test && test <= c2) { + ok = 1; + } + } else if (c == test) { + ok = 1; + } + } while ((c = *pattern++) != ']'); + + *newp = (char *)(uintptr_t)pattern; + return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH); +} diff --git a/contrib/dlz/modules/wildcard/testing/named.conf b/contrib/dlz/modules/wildcard/testing/named.conf new file mode 100644 index 0000000..cd46706 --- /dev/null +++ b/contrib/dlz/modules/wildcard/testing/named.conf @@ -0,0 +1,55 @@ +/* + * 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. + */ + +controls { }; + +options { + directory "."; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { any; }; + listen-on-v6 { none; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-md5; +}; + +controls { + inet 127.0.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +/* + * This will match any zone name containing the string "example" and + * ending with .com, such as "thisexample.com", "exampleofthat.com", + * or "anexampleoftheotherthing.com". + */ +dlz "test" { + database "dlopen ../dlz_wildcard_dynamic.so + *example*.com 10.53.* 1800 + @ 3600 SOA {ns3.example.nil. support.example.nil. 42 14400 7200 2592000 600} + @ 3600 NS ns3.example.nil. + @ 3600 NS ns4.example.nil. + @ 3600 NS ns8.example.nil. + @ 3600 MX {5 mail.example.nil.} + ftp 86400 A 192.0.0.1 + sql 86400 A 192.0.0.2 + tmp {} A 192.0.0.3 + www 86400 A 192.0.0.3 + www 86400 AAAA ::1 + txt 300 TXT {\"you requested $record$ in $zone$\"} + * 86400 A 192.0.0.100"; +}; diff --git a/contrib/dnspriv/README.md b/contrib/dnspriv/README.md new file mode 100644 index 0000000..8fa6795 --- /dev/null +++ b/contrib/dnspriv/README.md @@ -0,0 +1,23 @@ +<!-- + - 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. +--> +### DNS Privacy in BIND + +This directory contains sample configuration files to enable BIND, +with Nginx as a TLS proxy, to provide DNS over TLS. + +`named.conf` configures a validating recursive name server to listen +on the localhost address at port 8853. + +`nginx.conf` configures a TLS proxy to listen on port 853 and +forward queries and responses to `named`. + +For more information, please see +[https://dnsprivacy.org/wiki/](https://dnsprivacy.org/wiki/) diff --git a/contrib/dnspriv/named.conf b/contrib/dnspriv/named.conf new file mode 100644 index 0000000..e5ab3cd --- /dev/null +++ b/contrib/dnspriv/named.conf @@ -0,0 +1,20 @@ +/* + * 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 { + listen-on port 8853 { 127.0.0.1; }; + allow-query { localhost; }; + recursion yes; + dnssec-validation auto; + tcp-clients 1024; +}; diff --git a/contrib/dnspriv/nginx.conf b/contrib/dnspriv/nginx.conf new file mode 100644 index 0000000..83f2a88 --- /dev/null +++ b/contrib/dnspriv/nginx.conf @@ -0,0 +1,43 @@ +# 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. + +# uncomment to choose an appropriate UID/GID; default is 'nobody' +# user bind bind; + +worker_processes auto; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; + multi_accept on; +} + +stream { + upstream dns_tcp_servers { + server 127.0.0.1:8853; + } + + server { + listen 853 ssl; + proxy_pass dns_tcp_servers; + + # update to a suitable SSL certificate (e.g. from LetsEncrypt), + # and uncomment the following lines: + # ssl_certificate /etc/nginx/lego/certificates/<cert>.crt; + # ssl_certificate_key /etc/nginx/lego/certificates/<cert>.key; + + ssl_protocols TLSv1.2; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + ssl_session_tickets on; + ssl_session_timeout 4h; + ssl_handshake_timeout 30s; + } +} diff --git a/contrib/kasp/README b/contrib/kasp/README new file mode 100644 index 0000000..fb897f1 --- /dev/null +++ b/contrib/kasp/README @@ -0,0 +1,11 @@ +This directory is for tools and scripts related to the OpenDNSSEC KASP +("key and signature policy") format. Currently it only contains +"kasp2policy.py", a python script for converting KASP key policy +to the "dnssec.policy" format that is used by dnssec-keymgr. + +This depends on PLY (python lex/yacc) and on the "isc.dnskey" module in +bin/python/isc. + +Basic test: +$ python kasp2policy.py kasp.xml > policy.out +$ diff policy.out policy.good diff --git a/contrib/kasp/kasp.xml b/contrib/kasp/kasp.xml new file mode 100644 index 0000000..a92d096 --- /dev/null +++ b/contrib/kasp/kasp.xml @@ -0,0 +1,146 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> + +<!-- Sample KASP file to use for testing kasp2policy.py. --> +<KASP> + <Policy name="Policy1"> + <Description>A default policy that will + amaze you and your friends</Description> + <Signatures> + <Resign>PT5M</Resign> + <Refresh>PT5M</Refresh> + <Validity> + <Default>PT15M</Default> + <Denial>PT15M</Denial> + </Validity> + <Jitter>PT2M</Jitter> + <InceptionOffset>PT1M</InceptionOffset> + </Signatures> + + <Denial> + <NSEC> + </NSEC> + </Denial> + + <Keys> + <!-- Parameters for both KSK and ZSK --> + <TTL>PT1M</TTL> + <RetireSafety>PT0S</RetireSafety> + <PublishSafety>PT0S</PublishSafety> + + <!-- Parameters for KSK only --> + <KSK> + <Algorithm length="2048">5</Algorithm> + <Lifetime>PT40M</Lifetime> + <Repository>softHSM</Repository> + <Standby>1</Standby> + </KSK> + + <!-- Parameters for ZSK only --> + <ZSK> + <Algorithm length="2048">5</Algorithm> + <Lifetime>PT25M</Lifetime> + <Repository>softHSM</Repository> + <Standby>1</Standby> + </ZSK> + </Keys> + + <Zone> + <PropagationDelay>PT0S</PropagationDelay> + <SOA> + <TTL>PT0S</TTL> + <Minimum>PT0S</Minimum> + <Serial>unixtime</Serial> + </SOA> + </Zone> + + <Parent> + <PropagationDelay>PT8M</PropagationDelay> + <DS> + <TTL>PT0S</TTL> + </DS> + <SOA> + <TTL>PT0S</TTL> + <Minimum>PT0S</Minimum> + </SOA> + </Parent> + </Policy> + <Policy name="Policy2"> + <Description>A default policy that will amaze you and your friends</Description> + <Signatures> + <Resign>PT7M</Resign> + <Refresh>PT7M</Refresh> + <Validity> + <Default>PT15M</Default> + <Denial>PT16M</Denial> + </Validity> + <Jitter>PT2M</Jitter> + <InceptionOffset>PT1M</InceptionOffset> + </Signatures> + + <Denial> + <NSEC3> + <Resalt>P120D</Resalt> + <Hash> + <Algorithm>1</Algorithm> + <Iterations>5</Iterations> + <Salt length="8"/> + </Hash> + </NSEC3> + </Denial> + + <Keys> + <!-- Parameters for both KSK and ZSK --> + <TTL>PT15M</TTL> + <RetireSafety>PT0S</RetireSafety> + <PublishSafety>PT0S</PublishSafety> + + <!-- Parameters for KSK only --> + <KSK> + <Algorithm length="2048">7</Algorithm> + <Lifetime>PT45M</Lifetime> + <Repository>softHSM</Repository> + <Standby>1</Standby> + </KSK> + + <!-- Parameters for ZSK only --> + <ZSK> + <Algorithm length="2048">7</Algorithm> + <Lifetime>PT25M</Lifetime> + <Repository>softHSM</Repository> + <Standby>1</Standby> + </ZSK> + </Keys> + + <Zone> + <PropagationDelay>PT0S</PropagationDelay> + <SOA> + <TTL>PT0S</TTL> + <Minimum>PT0S</Minimum> + <Serial>unixtime</Serial> + </SOA> + </Zone> + + <Parent> + <PropagationDelay>PT12M</PropagationDelay> + <DS> + <TTL>PT0S</TTL> + </DS> + <SOA> + <TTL>PT0S</TTL> + <Minimum>PT0S</Minimum> + </SOA> + </Parent> + </Policy> +</KASP> diff --git a/contrib/kasp/kasp2policy.py b/contrib/kasp/kasp2policy.py new file mode 100644 index 0000000..a2536a7 --- /dev/null +++ b/contrib/kasp/kasp2policy.py @@ -0,0 +1,223 @@ +#!/usr/bin/python + +# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +# 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. + +from xml.etree import cElementTree as ET +from collections import defaultdict +import re +from ply import yacc +from ply import lex +from isc import dnskey + + +############################################################################ +# Translate KASP duration values into seconds +############################################################################ +class KaspTime: + # pylint: disable=invalid-name + class KTLex: + # pylint: disable=invalid-name + + tokens = ("P", "T", "Y", "M", "D", "H", "S", "NUM") + + t_P = r"(?i)P" + t_T = r"(?i)T" + t_Y = r"(?i)Y" + t_M = r"(?i)M" + t_D = r"(?i)D" + t_H = r"(?i)H" + t_S = r"(?i)S" + + @staticmethod + def t_NUM(t): + r"\d+" + t.value = int(t.value) + return t + + @staticmethod + def t_error(t): + print("Illegal character '%s'" % t.value[0]) + t.lexer.skip(1) + + def __init__(self): + self.lexer = lex.lex(object=self) + + def __init__(self): + self.lexer = self.KTLex() + self.tokens = self.lexer.tokens + self.parser = yacc.yacc(debug=False, write_tables=False, module=self) + + def parse(self, text): + self.lexer.lexer.lineno = 0 + return self.parser.parse(text) + + @staticmethod + def p_ktime_4(p): + "ktime : P periods T times" + p[0] = p[2] + p[4] + + @staticmethod + def p_ktime_3(p): + "ktime : P T times" + p[0] = p[3] + + @staticmethod + def p_ktime_2(p): + "ktime : P periods" + p[0] = p[2] + + @staticmethod + def p_periods_1(p): + "periods : period" + p[0] = p[1] + + @staticmethod + def p_periods_2(p): + "periods : periods period" + p[0] = p[1] + p[2] + + @staticmethod + def p_times_1(p): + "times : time" + p[0] = p[1] + + @staticmethod + def p_times_2(p): + "times : times time" + p[0] = p[1] + p[2] + + @staticmethod + def p_period(p): + """period : NUM Y + | NUM M + | NUM D""" + if p[2].lower() == "y": + p[0] = int(p[1]) * 31536000 + elif p[2].lower() == "m": + p[0] = int(p[1]) * 2592000 + elif p[2].lower() == "d": + p[0] += int(p[1]) * 86400 + + @staticmethod + def p_time(p): + """time : NUM H + | NUM M + | NUM S""" + if p[2].lower() == "h": + p[0] = int(p[1]) * 3600 + elif p[2].lower() == "m": + p[0] = int(p[1]) * 60 + elif p[2].lower() == "s": + p[0] = int(p[1]) + + @staticmethod + def p_error(): + print("Syntax error") + + +############################################################################ +# Load the contents of a KASP XML file as a python dictionary +############################################################################ +class Kasp: + # pylint: disable=invalid-name + + @staticmethod + def _todict(t): + d = {t.tag: {} if t.attrib else None} + children = list(t) + if children: + dd = defaultdict(list) + for dc in map(Kasp._todict, children): + for k, v in dc.iteritems(): + dd[k].append(v) + k = {k: v[0] if len(v) == 1 else v for k, v in dd.items()} + d = {t.tag: k} + if t.attrib: + d[t.tag].update(("@" + k, v) for k, v in t.attrib.iteritems()) + if t.text: + text = t.text.strip() + if children or t.attrib: + if text: + d[t.tag]["#text"] = text + else: + d[t.tag] = text + return d + + def __init__(self, filename): + self._dict = Kasp._todict(ET.parse(filename).getroot()) + + def __getitem__(self, key): + return self._dict[key] + + def __len__(self): + return len(self._dict) + + def __iter__(self): + return self._dict.__iter__() + + def __repr__(self): + return repr(self._dict) + + +############################################################################ +# Load the contents of a KASP XML file as a python dictionary +############################################################################ +if __name__ == "__main__": + import sys + + if len(sys.argv) < 2: + print("Usage: kasp2policy <filename>") + sys.exit(1) + + KINFO = Kasp(sys.argv[1]) + try: + KINFO = Kasp(sys.argv[1]) + except FileNotFoundError: + print("%s: unable to load KASP file '%s'" % (sys.argv[0], sys.argv[1])) + sys.exit(1) + + KT = KaspTime() + FIRST = True + + for policy in KINFO["KASP"]["Policy"]: + if not policy["@name"] or not policy["Keys"]: + continue + if not FIRST: + print("") + FIRST = False + if policy["Description"]: + desc = policy["Description"].strip() + print("# %s" % re.sub(r"\n\s*", "\n# ", desc)) + print("policy %s {" % policy["@name"]) + ksk = policy["Keys"]["KSK"] + zsk = policy["Keys"]["ZSK"] + kalg = ksk["Algorithm"] + zalg = zsk["Algorithm"] + algnum = kalg["#text"] or zalg["#text"] + if algnum: + print("\talgorithm %s;" % dnskey.algstr(int(algnum))) + if policy["Keys"]["TTL"]: + print("\tkeyttl %d;" % KT.parse(policy["Keys"]["TTL"])) + if kalg["@length"]: + print("\tkey-size ksk %d;" % int(kalg["@length"])) + if zalg["@length"]: + print("\tkey-size zsk %d;" % int(zalg["@length"])) + if ksk["Lifetime"]: + print("\troll-period ksk %d;" % KT.parse(ksk["Lifetime"])) + if zsk["Lifetime"]: + print("\troll-period zsk %d;" % KT.parse(zsk["Lifetime"])) + if ksk["Standby"]: + print("\tstandby ksk %d;" % int(ksk["Standby"])) + if zsk["Standby"]: + print("\tstandby zsk %d;" % int(zsk["Standby"])) + print("};") diff --git a/contrib/kasp/policy.good b/contrib/kasp/policy.good new file mode 100644 index 0000000..18c6360 --- /dev/null +++ b/contrib/kasp/policy.good @@ -0,0 +1,24 @@ +# A default policy that will +# amaze you and your friends +policy Policy1 { + algorithm RSASHA1; + keyttl 60; + key-size ksk 2048; + key-size zsk 2048; + roll-period ksk 2400; + roll-period zsk 1500; + standby ksk 1; + standby zsk 1; +}; + +# A default policy that will amaze you and your friends +policy Policy2 { + algorithm NSEC3RSASHA1; + keyttl 900; + key-size ksk 2048; + key-size zsk 2048; + roll-period ksk 2700; + roll-period zsk 1500; + standby ksk 1; + standby zsk 1; +}; diff --git a/contrib/scripts/catzhash.py b/contrib/scripts/catzhash.py new file mode 100644 index 0000000..98f6c9e --- /dev/null +++ b/contrib/scripts/catzhash.py @@ -0,0 +1,35 @@ +#!/usr/bin/python + +# 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. + +# catzhash.py: generate the SHA-1 hash of a domain name in wire format. +# +# This can be used to determine the label to use in a catalog zone to +# represent the specified zone. For example, the zone +# "domain.example" can be represented in a catalog zone called +# "catalog.example" by adding the following record: +# +# 5960775ba382e7a4e09263fc06e7c00569b6a05c.zones.catalog.example. \ +# IN PTR domain.example. +# +# The label "5960775ba382e7a4e09263fc06e7c00569b6a05c" is the output of +# this script when run with the argument "domain.example". + +import sys +import hashlib +import dns.name + +if len(sys.argv) < 2: + print("Usage: %s name" % sys.argv[0]) + +NAME = dns.name.from_text(sys.argv[1]).to_wire() +print(hashlib.sha1(NAME).hexdigest()) diff --git a/contrib/scripts/check-secure-delegation.pl.in b/contrib/scripts/check-secure-delegation.pl.in new file mode 100644 index 0000000..f2a3a19 --- /dev/null +++ b/contrib/scripts/check-secure-delegation.pl.in @@ -0,0 +1,116 @@ +#!@PERL@ + +# 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. + +use warnings; +use FileHandle; +use IPC::Open2; +use POSIX qw/strftime/; + +# +# We only compare keyid / DNSSEC algorithm pairs. If this succeeds then +# the crypto will likely succeed. If it fails then the crypto will definitely +# fail. +# +$prefix = "@prefix@"; +$dig = "$prefix/bin/dig +cd +dnssec +noall +answer"; +$dsfromkey = "$prefix/sbin/dnssec-dsfromkey -1 -A -f /dev/stdin"; + +# Get "now" in a RRSIG datestamp format. +$now = strftime "%Y%m%d%H%M%S", gmtime; + +foreach $zone (@ARGV) { + my %algorithms = (); + my %dnskeygood = (); + my %dnskeyalg = (); + my %dnskey = (); + my %dsgood = (); + my %ds = (); + + # Read the DS records and extract the key id, algorithm pairs + open(DS, "$dig -t DS -q $zone|") || die("dig DS failed"); + while(<DS>) { + @words = split; + if ($words[3] eq "RRSIG" && $words[4] eq "DS") { + next if ($words[8] >= $now && $words[9] <= $now); + print "BAD SIG DATES: $_"; + } + next if ($words[3] ne "DS"); + $ds{"$words[4] $words[5]"} = 1; + $algorithms{"$words[5]"} = 1; + } + close(DS); + + # Read the RRSIG(DNSKEY) records and extract the key id, + # algorithm pairs. Set good if we have a match against the DS + # records. DNSKEY records should be before the RRSIG records. + open(DNSKEY, "$dig -t DNSKEY -q $zone|") || die("dig DNSKEY failed"); + while (<DNSKEY>) { + @words = split; + if ($words[3] eq "DNSKEY") { + $dnskeyalg{"$words[6]"} = 1; + next if (! -e "/dev/stdin"); + # get the key id ($dswords[3]). + $pid = open2(*Reader, *Writer, "$dsfromkey $zone"); + die("dsfromkey failed") if ($pid == -1); + print Writer "$_"; + close(Writer); + $line = <Reader>; + close(Reader); + @dswords = split /\s/, $line; + $dnskey{"$dswords[3] $dswords[4]"} = 1; + next; + } + next if ($words[3] ne "RRSIG" || $words[4] ne "DNSKEY"); + if ($words[8] >= $now && $words[9] <= $now) { + # If we don't have /dev/stdin then just check for the + # RRSIG otherwise check for both the DNSKEY and + # RRSIG. + $dsgood{"$words[5]"} = 1 + if (! -e "/dev/stdin" && + exists($ds{"$words[10] $words[5]"})); + $dsgood{"$words[5]"} = 1 + if (exists($ds{"$words[10] $words[5]"}) && + exists($dnskey{"$words[10] $words[5]"})); + $dnskeygood{"$words[5]"} = 1 + if (! -e "/dev/stdin"); + $dnskeygood{"$words[5]"} = 1 + if (exists($dnskey{"$words[10] $words[5]"})); + } else { + $dnskeygood{"$words[5]"} = 1; + print "BAD SIG DATES: $_"; + } + } + close(DNSKEY); + + # Do we have signatures for all DNSKEY algorithms? + foreach $alg ( keys %dnskeyalg ) { + print "Missing $zone DNSKEY RRSIG for algorithm $alg\n" + if (!exists($dnskeygood{$alg})); + } + + # Do we have a matching self signed DNSKEY for all DNSSEC algorithms + # in the DS records. + $count = 0; + foreach $alg ( keys %algorithms ) { + if (exists($dsgood{$alg})) { + print "$zone algorithm $alg good " . + "(found DS / self signed DNSKEY pair)\n"; + } else { + print "$zone algorithm $alg bad " . + "(no DS / self signed DNSKEY pair found)\n"; + } + $count++; + } + print "$zone has no secure delegation records\n" + if (! $count); +} diff --git a/contrib/scripts/check5011.pl b/contrib/scripts/check5011.pl new file mode 100644 index 0000000..814295a --- /dev/null +++ b/contrib/scripts/check5011.pl @@ -0,0 +1,210 @@ +#!/usr/bin/perl + +# 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. + +use warnings; +use strict; + +use POSIX qw(strftime); +my $now = strftime "%Y%m%d%H%M%S", gmtime; + +sub ext8601 ($) { + my $d = shift; + $d =~ s{(....)(..)(..)(..)(..)(..)} + {$1-$2-$3.$4:$5:$6+0000}; + return $d; +} + +sub getkey ($$) { + my $h = shift; + my $k = shift; + m{\s+(\d+)\s+(\d+)\s+(\d+)\s+[(]\s*$}; + $k->{flags} = $1; + $k->{protocol} = $2; + $k->{algorithm} = $3; + my $data = "("; + while (<$h>) { + s{^\s+}{}; + s{\s+$}{}; + last if m{^[)]}; + $data .= $_; + } + m{ alg = (\S+)\s*; key id = (\d+)}; + $k->{alg} = $1; + $k->{id} = $2; + $k->{data} = $data; + return $k; +} + +sub fmtkey ($) { + my $k = shift; + return sprintf "%16s tag %s", $k->{name}, $k->{id}; +} + +sub printstatus ($) { + my $a = shift; + if ($a->{removehd} ne "19700101000000") { + printf " untrusted and to be removed at %s\n", ext8601 $a->{removehd}; + } elsif ($a->{addhd} le $now) { + printf " trusted\n"; + } else { + printf " waiting for %s\n", ext8601 $a->{addhd}; + } +} + +sub digkeys ($) { + my $name = shift; + my $keys; + open my $d, "-|", qw{dig +multiline DNSKEY}, $name; + while (<$d>) { + next unless m{^([a-z0-9.-]*)\s+\d+\s+IN\s+DNSKEY\s+}; + next unless $name eq $1; + push @$keys, getkey $d, { name => $name }; + } + return $keys; +} + +my $anchor; +my $owner = "."; +while (<>) { + next unless m{^([a-z0-9.-]*)\s+KEYDATA\s+(\d+)\s+(\d+)\s+(\d+)\s+}; + my $k = getkey *ARGV, { + name => $1, + refresh => $2, + addhd => $3, + removehd => $4, + }; + if ($k->{name} eq "") { + $k->{name} = $owner; + } else { + $owner = $k->{name}; + } + $k->{name} =~ s{[.]*$}{.}; + push @{$anchor->{$k->{name}}}, $k; +} + +for my $name (keys %$anchor) { + my $keys = digkeys $name; + my $anchors = $anchor->{$name}; + for my $k (@$keys) { + if ($k->{flags} & 1) { + printf "%s %s", fmtkey $k, $k->{alg}; + } else { + # ZSK - skipping + next; + } + if ($k->{flags} & 512) { + print " revoked;"; + } + my $a; + for my $t (@$anchors) { + if ($t->{data} eq $k->{data} and + $t->{protocol} eq $k->{protocol} and + $t->{algorithm} eq $k->{algorithm}) { + $t->{matched} = 1; + $a = $t; + last; + } + } + if (not defined $a) { + print " no trust anchor\n"; + next; + } + printstatus $a; + } + for my $a (@$anchors) { + next if $a->{matched}; + printf "%s %s missing;", fmtkey $a, $a->{alg}; + printstatus $a; + } +} + +exit; + +__END__ + +=head1 NAME + +check5011 - summarize DNSSEC trust anchor status + +=head1 SYNOPSIS + +check5011 <I<managed-keys.bind>> + +=head1 DESCRIPTION + +The BIND managed-keys file contains DNSSEC trust anchors +that can be automatically updated according to RFC 5011. The +B<check5011> program reads this file and prints a summary of the +status of the trust anchors. It fetches the corresponding +DNSKEY records using B<dig> and compares them to the trust anchors. + +Each key is printed on a line with its name, its tag, and its +algorithm, followed by a summary of its status. + +=over + +=item C<trusted> + +The key is currently trusted. + +=item C<waiting for ...> + +The key is new, and B<named> is waiting for the "add hold-down" period +to pass before the key will be trusted. + +=item C<untrusted and to be removed at ...> + +The key was revoked and will be removed at the stated time. + +=item C<no trust anchor> + +The key is present in the DNS but not in the managed-keys file. + +=item C<revoked> + +The key has its revoked flag set. This is printed before the key's +trust anchor status which should normally be C<untrusted...> if +B<named> has observed the revocation. + +=item C<missing> + +There is no DNSKEY record for this trust anchor. This is printed +before the key's trust anchor status. + +=back + +By default the managed keys are stored in a file called +F<managed-keys.bind> in B<named>'s working directory. This location +can be changed with B<named>'s B<managed-keys-directory> option. If +you are using views the file may be named with the SHA256 hash of a +view name with a F<.mkeys> extension added. + +=head1 AUTHOR + +=over + +=item Written by Tony Finch <fanf2@cam.ac.uk> <dot@dotat.at> + +=item at the University of Cambridge Computing Service. + +=item You may do anything with this. It has no warranty. + +=item L<http://creativecommons.org/publicdomain/zero/1.0/> + +=back + +=head1 SEE ALSO + +dig(1), named(8) + +=cut diff --git a/contrib/scripts/dnssec-keyset.sh b/contrib/scripts/dnssec-keyset.sh new file mode 100644 index 0000000..9bf02c6 --- /dev/null +++ b/contrib/scripts/dnssec-keyset.sh @@ -0,0 +1,207 @@ +#!/bin/sh + +# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +# 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. + +HELP=" +Generates a set of <count> successive DNSSEC keys for <zone> +Key timings are based on a pre-publication rollover strategy + + <life> (lifetime) is the key active lifetime in days [default 180] + <intro> (introduction time) is the number of days from publication + to activation of a key [default 30] + <ret> (retirement time) is the number of days from inactivation + to deletion of a key [default 30] + +Options: + -a <alg> Cryptographic algorithm. See man dnssec-keygen for defaults. + -b <bits> Number of bits in the key. See man dnssec-keygen for defaults. + -k if present, generate Key Signing Keys (KSKs). Otherwise, + generate Zone Signing Keys (ZSKs). + -3 If present and if -a is not specified, use an NSEC3- + capable algorithm. See man dnssec-keygen for defaults. + -i <date> Inception date of the set of keys, in 'mm/dd/yyyy' format. + The first two keys will be published by this date, and the + first one will be activated. Default is today. + -f <index> Index of first key generated. Defaults to 0. + -K <dir> Key repository: write keys to this directory. Defaults to CWD. + -d Dry run. No actual keys generated if present." + +USAGE="Usage: +`basename $0` [-a <alg>] [-b <bits>] [-k] [-3] [-i <date>] + [-f <index>] [-d] <zone> <count> [<life>] [<intro>] [<ret>]" + +ALGFLAG='' +BITSFLAG='' +KSKFLAG='' +NSEC3FLAG='' +KEYREPO='' +DRYRUN=false +OPTKSK=false +K=0 +INCEP=`date +%m/%d/%Y` + +# Parse command line options +while getopts ":a:b:df:hkK:3i:" thisOpt +do + case $thisOpt in + a) + ALGFLAG=" -a $OPTARG" + ;; + b) + BITSFLAG=" -b $OPTARG" + ;; + d) + DRYRUN=true + ;; + f) + OPTKSK=true + K=$OPTARG + ;; + h) + echo "$USAGE" + echo "$HELP" + exit 0 + ;; + k) + KSKFLAG=" -f KSK" + ;; + K) + KEYREPO=$OPTARG + ;; + 3) + NSEC3FLAG=" -3" + ;; + i) + INCEP=$OPTARG + ;; + *) + echo 'Unrecognized option.' + echo "$USAGE" + exit 1 + ;; + esac +done +shift `expr $OPTIND - 1` + +# Check that required arguments are present +if [ $# -gt 5 -o $# -lt 2 ]; then + echo "$USAGE" + exit 1 +fi + +# Remaining arguments: +# DNS zone name +ZONE=$1 +shift + +# Number of keys to be generated +COUNT=$1 +shift + +# Key active lifetime +LIFE=${1:-180} +[ $# -ne 0 ] && shift + +# Key introduction time (publication to activation) +INTRO=${1:-30} +[ $# -ne 0 ] && shift + +# Key retirement time (inactivation to deletion) +RET=${1:-30} + +# Today's date in dnssec-keygen format (YYYYMMDD) +TODAY=`date +%Y%m%d` + +# Key repository defaults to CWD +if [ -z "$KEYREPO" ]; then + KEYREPO="." +fi + +if $DRYRUN; then + echo 'Dry Run (no key files generated)' +elif [ ! -d "$KEYREPO" ]; then + # Create the key repository if it does not currently exist + mkdir -p "$KEYREPO" +fi + +# Iterate through the key set. K is the index, zero-based. +KLAST=`expr $K + $COUNT` +while [ $K -lt $KLAST ]; do + KEYLABEL="Key `printf \"%02d\" $K`:" + # Epoch of the current key + # (zero for the first key, increments of key lifetime) + # The epoch is in days relative to the inception date of the key set + EPOCH=`expr $LIFE \* $K` + # Activation date in days is the same as the epoch + ACTIVATE=$EPOCH + # Publication date in days relative to the key epoch + PUBLISH=`expr $EPOCH - $LIFE - $INTRO` + # Inactivation date in days relative to the key epoch + INACTIVE=`expr $EPOCH + $LIFE` + # Deletion date in days relative to the key epoch + DELETE=`expr $EPOCH + $LIFE + $RET` + + # ... these values should not precede the key epoch + [ $ACTIVATE -lt 0 ] && ACTIVATE=0 + [ $PUBLISH -lt 0 ] && PUBLISH=0 + [ $INACTIVE -lt 0 ] && INACTIVE=0 + [ $DELETE -lt 0 ] && DELETE=0 + + # Key timing dates in dnssec-keygen format (YYYYMMDD): + # publication, activation, inactivation, deletion + PDATE=`date -d "$INCEP +$PUBLISH day" +%Y%m%d` + ADATE=`date -d "$INCEP +$ACTIVATE day" +%Y%m%d` + IDATE=`date -d "$INCEP +$INACTIVE day" +%Y%m%d` + DDATE=`date -d "$INCEP +$DELETE day" +%Y%m%d` + + # Construct the dnssec-keygen command including all the specified options. + # Suppress key generation progress information, and save the key in + # the $KEYREPO directory. + KEYGENCMD="dnssec-keygen -q$ALGFLAG$BITSFLAG$NSEC3FLAG$KSKFLAG -P $PDATE -A $ADATE -I $IDATE -D $DDATE -K $KEYREPO $ZONE" + echo "$KEYLABEL $KEYGENCMD" + + # Generate the key and retrieve its name + if $DRYRUN; then + KEYNAME="DryRunKey-`printf \"%02d\" $K`" + else + KEYNAME=`$KEYGENCMD` + fi + + # Indicate the key status based on key timing dates relative to today + if [ $TODAY -ge $DDATE ]; then + echo "$KEYLABEL $KEYNAME is obsolete post deletion date." + elif [ $TODAY -ge $IDATE ]; then + echo "$KEYLABEL $KEYNAME is published and inactive prior to deletion date." + elif [ $TODAY -ge $ADATE ]; then + echo "$KEYLABEL $KEYNAME is published and active." + elif [ $TODAY -ge $PDATE ]; then + echo "$KEYLABEL $KEYNAME is published prior to activation date." + else + echo "$KEYLABEL $KEYNAME is pending publication." + fi + + # For published KSKs, generate the required DS records, + # saving them to the file $KEYREPO/DS-$KEYNAME + if $OPTKSK && [ $TODAY -ge $PDATE -a $TODAY -lt $DDATE ]; then + echo "$KEYLABEL $KEYNAME (KSK) requires the publication of DS records in the parent zone." + if $DRYRUN; then + echo "$KEYLABEL No DS-$KEYNAME file created." + else + dnssec-dsfromkey "$KEYREPO/$KEYNAME" > "$KEYREPO/DS-$KEYNAME" + echo "$KEYLABEL See $KEYREPO/DS-$KEYNAME." + fi + fi + K=`expr $K + 1` +done + +exit 0 diff --git a/contrib/scripts/named-bootconf.sh b/contrib/scripts/named-bootconf.sh new file mode 100644 index 0000000..273588d --- /dev/null +++ b/contrib/scripts/named-bootconf.sh @@ -0,0 +1,301 @@ +#!/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. + +# $NetBSD: named-bootconf.sh,v 1.5 1998/12/15 01:00:53 tron Exp $ +# +# Copyright (c) 1995, 1998 The NetBSD Foundation, Inc. +# All rights reserved. +# +# This code is derived from software contributed to The NetBSD Foundation +# by Matthias Scheler. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +if [ ${OPTIONFILE-X} = X ]; then + WORKDIR=/tmp/`date +%s`.$$ + ( umask 077 ; mkdir $WORKDIR ) || { + echo "unable to create work directory '$WORKDIR'" >&2 + exit 1 + } + OPTIONFILE=$WORKDIR/options + ZONEFILE=$WORKDIR/zones + COMMENTFILE=$WORKDIR/comments + export OPTIONFILE ZONEFILE COMMENTFILE + touch $OPTIONFILE $ZONEFILE $COMMENTFILE + DUMP=1 +else + DUMP=0 +fi + +while read CMD ARGS; do + class= + CMD=`echo "${CMD}" | tr '[A-Z]' '[a-z]'` + case $CMD in + \; ) + echo \# $ARGS >>$COMMENTFILE + ;; + cache ) + set - X $ARGS + shift + if [ $# -eq 2 ]; then + (echo "" + cat $COMMENTFILE + echo "zone \"$1\" {" + echo " type hint;" + echo " file \"$2\";" + echo "};") >>$ZONEFILE + rm -f $COMMENTFILE + touch $COMMENTFILE + fi + ;; + directory ) + set - X $ARGS + shift + if [ $# -eq 1 ]; then + (cat $COMMENTFILE + echo " directory \"$1\";") >>$OPTIONFILE + rm -f $COMMENTFILE + touch $COMMENTFILE + + DIRECTORY=$1 + export DIRECTORY + fi + ;; + forwarders ) + (cat $COMMENTFILE + echo " forwarders {" + for ARG in $ARGS; do + echo " $ARG;" + done + echo " };") >>$OPTIONFILE + rm -f $COMMENTFILE + touch $COMMENTFILE + ;; + include ) + if [ "$ARGS" != "" ]; then + (cd ${DIRECTORY-.}; cat $ARGS) | $0 + fi + ;; + limit ) + ARGS=`echo "${ARGS}" | tr '[A-Z]' '[a-z]'` + set - X $ARGS + shift + if [ $# -eq 2 ]; then + cat $COMMENTFILE >>$OPTIONFILE + case $1 in + datasize | files | transfers-in | transfers-per-ns ) + echo " $1 $2;" >>$OPTIONFILE + ;; + esac + rm -f $COMMENTFILE + touch $COMMENTFILE + fi + ;; + options ) + ARGS=`echo "${ARGS}" | tr '[A-Z]' '[a-z]'` + cat $COMMENTFILE >>$OPTIONFILE + for ARG in $ARGS; do + case $ARG in + fake-iquery ) + echo " fake-iquery yes;" >>$OPTIONFILE + ;; + forward-only ) + echo " forward only;" >>$OPTIONFILE + ;; + no-fetch-glue ) + echo " fetch-glue no;" >>$OPTIONFILE + ;; + no-recursion ) + echo " recursion no;" >>$OPTIONFILE + ;; + esac + done + rm -f $COMMENTFILE + touch $COMMENTFILE + ;; + primary|primary/* ) + case $CMD in + primary/chaos ) + class="chaos " + ;; + primary/hs ) + class="hesiod " + ;; + esac + set - X $ARGS + shift + if [ $# -eq 2 ]; then + (echo "" + cat $COMMENTFILE + echo "zone \"$1\" ${class}{" + echo " type master;" + echo " file \"$2\";" + echo "};") >>$ZONEFILE + rm -f $COMMENTFILE + touch $COMMENTFILE + fi + ;; + secondary|secondary/* ) + case $CMD in + secondary/chaos ) + class="chaos " + ;; + secondary/hs ) + class="hesiod " + ;; + esac + set - X $ARGS + shift + if [ $# -gt 2 ]; then + ZONE=$1 + shift + PRIMARIES=$1 + while [ $# -gt 2 ]; do + shift + PRIMARIES="$PRIMARIES $1" + done + (echo "" + cat $COMMENTFILE + echo "zone \"$ZONE\" ${class}{" + echo " type slave;" + echo " file \"$2\";" + echo " masters {" + for PRIMARY in $PRIMARIES; do + echo " $PRIMARY;" + done + echo " };" + echo "};") >>$ZONEFILE + rm -f $COMMENTFILE + touch $COMMENTFILE + fi + ;; + stub|stub/* ) + case $CMD in + stub/chaos ) + class="chaos " + ;; + stub/hs ) + class="hesiod " + ;; + esac + set - X $ARGS + shift + if [ $# -gt 2 ]; then + ZONE=$1 + shift + PRIMARIES=$1 + while [ $# -gt 2 ]; do + shift + PRIMARIES="$PRIMARIES $1" + done + (echo "" + cat $COMMENTFILE + echo "zone \"$ZONE\" ${class}{" + echo " type stub;" + echo " file \"$2\";" + echo " masters {" + for PRIMARY in $PRIMARIES; do + echo " $PRIMARY;" + done + echo " };" + echo "};") >>$ZONEFILE + rm -f $COMMENTFILE + touch $COMMENTFILE + fi + ;; + slave ) + cat $COMMENTFILE >>$OPTIONFILE + echo " forward only;" >>$OPTIONFILE + rm -f $COMMENTFILE + touch $COMMENTFILE + ;; + sortlist ) + (cat $COMMENTFILE + echo " topology {" + for ARG in $ARGS; do + case $ARG in + *.0.0.0 ) + echo " $ARG/8;" + ;; + *.0.0 ) + echo " $ARG/16;" + ;; + *.0 ) + echo " $ARG/24;" + ;; + * ) + echo " $ARG;" + ;; + esac + done + echo " };") >>$OPTIONFILE + rm -f $COMMENTFILE + touch $COMMENTFILE + ;; + tcplist | xfrnets ) + (cat $COMMENTFILE + echo " allow-transfer {" + for ARG in $ARGS; do + case $ARG in + *.0.0.0 ) + echo " $ARG/8;" + ;; + *.0.0 ) + echo " $ARG/16;" + ;; + *.0 ) + echo " $ARG/24;" + ;; + * ) + echo " $ARG;" + ;; + esac + done + echo " };") >>$OPTIONFILE + rm -f $COMMENTFILE + touch $COMMENTFILE + ;; + esac +done + +if [ $DUMP -eq 1 ]; then + echo "" + echo "options {" + cat $OPTIONFILE + echo "};" + cat $ZONEFILE $COMMENTFILE + + rm -f $OPTIONFILE $ZONEFILE $COMMENTFILE + rmdir $WORKDIR +fi + +exit 0 diff --git a/contrib/scripts/nanny.pl b/contrib/scripts/nanny.pl new file mode 100644 index 0000000..27a159e --- /dev/null +++ b/contrib/scripts/nanny.pl @@ -0,0 +1,49 @@ +#!/usr/bin/perl + +# 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. + +# A simple nanny to make sure named stays running. + +$pid_file_location = '/var/run/named.pid'; +$nameserver_location = 'localhost'; +$dig_program = 'dig'; +$named_program = 'named'; + +fork() && exit(); + +for (;;) { + $pid = 0; + open(FILE, $pid_file_location) || goto restart; + $pid = <FILE>; + close(FILE); + chomp($pid); + + $res = kill 0, $pid; + + goto restart if ($res == 0); + + $dig_command = + "$dig_program +short . \@$nameserver_location > /dev/null"; + $return = system($dig_command); + goto restart if ($return == 9); + + sleep 30; + next; + + restart: + if ($pid != 0) { + kill 15, $pid; + sleep 30; + } + system ($named_program); + sleep 120; +} diff --git a/contrib/scripts/zone-edit.sh.in b/contrib/scripts/zone-edit.sh.in new file mode 100644 index 0000000..a4e076d --- /dev/null +++ b/contrib/scripts/zone-edit.sh.in @@ -0,0 +1,153 @@ +#!/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. + +dir=/tmp/zone-edit.$$ +mkdir ${dir} || exit 1 +trap "/bin/rm -rf ${dir}" 0 + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +bindir=@bindir@ +sbindir=@sbindir@ + +dig=${bindir}/dig +checkzone=${sbindir}/named-checkzone +nsupdate=${bindir}/nsupdate + +case $# in +0) echo "Usage: zone-edit <zone> [dig options] [ -- nsupdate options ]"; exit 0 ;; +esac + +# What kind of echo are we using? +try=`echo -n ""` +if test "X$try" = "X-n " +then + echo_arg="" + bsc="\\c" +else + echo_arg="-n" + bsc="" +fi + +zone="${1}" +shift +digopts= +while test $# -ne 0 +do + case "${1}" in + --) + shift + break + ;; + *) + digopts="$digopts $1" + shift + ;; + esac +done + +${dig} axfr "$zone" $digopts | +awk '$4 == "RRSIG" || $4 == "NSEC" || $4 == "NSEC3" || $4 == "NSEC3PARAM" { next; } { print; }' > ${dir}/old + +if test -s ${dir}/old +then + ${checkzone} -q -D "$zone" ${dir}/old > ${dir}/ooo +fi + +if test -s ${dir}/ooo +then + cp ${dir}/ooo ${dir}/new + while : + do + if ${VISUAL:-${EDITOR:-/bin/ed}} ${dir}/new + then + if ${checkzone} -q -D "$zone" ${dir}/new > ${dir}/nnn + then + sort ${dir}/ooo > ${dir}/s1 + sort ${dir}/nnn > ${dir}/s2 + comm -23 ${dir}/s1 ${dir}/s2 | + sed 's/^/update delete /' > ${dir}/ccc + comm -13 ${dir}/s1 ${dir}/s2 | + sed 's/^/update add /' >> ${dir}/ccc + if test -s ${dir}/ccc + then + cat ${dir}/ccc | more + while : + do + echo ${echo_arg} "Update (u), Abort (a), Redo (r), Modify (m), Display (d) : $bsc" + read ans + case "$ans" in + u) + ( + echo zone "$zone" + cat ${dir}/ccc + echo send + ) | ${nsupdate} "$@" + break 2 + ;; + a) + break 2 + ;; + d) + cat ${dir}/ccc | more + ;; + r) + cp ${dir}/ooo ${dir}/new + break + ;; + m) + break + ;; + esac + done + else + while : + do + echo ${echo_arg} "Abort (a), Redo (r), Modify (m) : $bsc" + read ans + case "$ans" in + a) + break 2 + ;; + r) + cp ${dir}/ooo ${dir}/new + break + ;; + m) + break + ;; + esac + done + fi + else + while : + do + echo ${echo_arg} "Abort (a), Redo (r), Modify (m) : $bsc" + read ans + case "$ans" in + a) + break 2 + ;; + r) + cp ${dir}/ooo ${dir}/new + break + ;; + m) + break + ;; + esac + done + fi + fi + done +fi |